Initial deployment setup
Deploy staging / deploy (push) Failing after 6s

This commit is contained in:
root committed 2026-08-31 16:45:24 +09:00
commit 33453ecc55
3475 files changed
+850363

No files matched your search

@@ -0,0 +1,369 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { ChannelInfo } from '@/entities/channel';
import type { FieldInfo } from '@/entities/field';
import type { CreateChannelStepKey } from './create-channel-type';
import {
CREATE_CHANNEL_MAIN_STEP_LIST,
CREATE_CHANNEL_STEP_MAP,
FIRST_CREATE_CHANNEL_STEP,
LAST_CREATE_CHANNEL_STEP,
} from './create-channel-type';
const DEFAULT_FIELDS: FieldInfo[] = [
{
format: 'number',
property: 'READ_ONLY',
status: 'ACTIVE',
name: 'ID',
key: 'id',
description: '',
order: 0,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'date',
property: 'READ_ONLY',
status: 'ACTIVE',
name: 'Created',
key: 'createdAt',
description: '',
order: 1,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'date',
property: 'READ_ONLY',
status: 'ACTIVE',
name: 'Updated',
key: 'updatedAt',
description: '',
order: 2,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'multiSelect',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'Issue',
key: 'issues',
description: '',
options: [],
order: 3,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'title',
key: 'title',
description: '제목',
order: 4,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'contents',
key: 'contents',
description: '내용',
order: 5,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'select',
property: 'EDITABLE',
status: 'ACTIVE',
name: '중요도',
key: 'priority',
description: '관리자가 지정하는 피드백 중요도입니다.',
options: [
{ key: 'LOW', name: '낮음' },
{ key: 'MEDIUM', name: '보통' },
{ key: 'HIGH', name: '높음' },
{ key: 'CRITICAL', name: '긴급' },
],
order: 6,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'IP',
key: 'IP',
description: '사용자 ip주소',
order: 7,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'MAC_address',
key: 'MAC_address',
description: '사용자 MAC주소',
order: 8,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'select',
property: 'EDITABLE',
status: 'ACTIVE',
name: 'Category',
key: 'Category',
description: '문의 구분',
options: [
{ key: 'GENERAL_QNA', name: '일반문의' },
{ key: 'ERROR_QNA', name: '오류문의' },
{ key: 'IMPROVEMENT_QNA', name: '개선문의' },
],
order: 9,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: '작성자',
key: 'requester_id',
description: '',
order: 10,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: '테넌트ID',
key: 'requester_tenant_id',
description: '',
order: 11,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: 'e-mail',
key: 'requester_email',
description: '',
order: 12,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: '이름',
key: 'requester_name',
description: '',
order: 13,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: '부서',
key: 'requester_department',
description: '',
order: 14,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: 'is_secret',
key: 'is_secret',
description: '',
order: 15,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'select',
property: 'EDITABLE',
status: 'ACTIVE',
name: '피드백 상태',
key: 'feedback_status',
description: '피드백 상태',
options: [
{ key: 'INIT', name: '신규' },
{ key: 'ON_REVIEW', name: '검토중' },
{ key: 'DETAILED_REVIEW', name: '정밀검토중' },
{ key: 'IN_PROGRESS', name: '진행중' },
{ key: 'RESOLVED', name: '완료' },
{ key: 'PENDING', name: '진행하지 않음' },
],
order: 16,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
{
format: 'text',
property: 'READ_ONLY',
status: 'ACTIVE',
name: '담당자',
key: 'assignee',
description: '관리자가 지정한 피드백 담당자입니다.',
order: 17,
aiFieldTargetKeys: null,
aiFieldTemplateId: null,
aiFieldAutoProcessing: null,
},
];
interface Input {
channelInfo: ChannelInfo;
fields: FieldInfo[];
fieldPreview: null;
}
interface Step {
key: CreateChannelStepKey;
index: number;
}
interface State {
editingStepIndex: number | null;
currentStep: Step;
input: Input;
}
interface Action {
jumpStepByIndex: (index: number) => void;
jumpStepByKey: (key: CreateChannelStepKey) => void;
prevStep: () => void;
nextStep: () => void;
reset: () => void;
onChangeInput: <T extends keyof Input>(key: T, value: Input[T]) => void;
setEditingStepIndex: (index: number) => void;
}
export const CREATE_CHANNEL_DEFAULT_INPUT: Input = {
channelInfo: { name: '', description: '', feedbackSearchMaxDays: 365 },
fields: DEFAULT_FIELDS,
fieldPreview: null,
};
const DEFAULT_STATE: State = {
editingStepIndex: null,
currentStep: {
key: 'channel-info',
index: 0,
},
input: {
channelInfo: { name: '', description: '', feedbackSearchMaxDays: 365 },
fields: DEFAULT_FIELDS,
fieldPreview: null,
},
};
export const useCreateChannelStore = create<State & Action>()(
persist(
(set) => ({
...DEFAULT_STATE,
setEditingStepIndex(index) {
set({ editingStepIndex: index });
},
onChangeInput: <T extends keyof Input>(key: T, value: Input[T]) => {
set(({ input }) => ({ input: { ...input, [key]: value } }));
},
jumpStepByIndex(index) {
set({
currentStep: {
index,
key: CREATE_CHANNEL_MAIN_STEP_LIST[index] ?? 'channel-info',
},
});
},
jumpStepByKey(key) {
set({ currentStep: { index: CREATE_CHANNEL_STEP_MAP[key], key } });
},
nextStep() {
set(({ currentStep }) => ({
currentStep: {
index: Math.min(currentStep.index + 1, LAST_CREATE_CHANNEL_STEP),
key:
CREATE_CHANNEL_MAIN_STEP_LIST[currentStep.index + 1] ??
'channel-info',
},
editingStepIndex: Math.min(
currentStep.index + 1,
LAST_CREATE_CHANNEL_STEP,
),
}));
},
prevStep() {
set(({ currentStep }) => ({
currentStep: {
index: Math.max(currentStep.index - 1, FIRST_CREATE_CHANNEL_STEP),
key:
CREATE_CHANNEL_MAIN_STEP_LIST[currentStep.index - 1] ??
'channel-info',
},
}));
},
reset() {
set({ ...DEFAULT_STATE });
},
}),
{
name: 'create-channel',
version: 5,
migrate: () => DEFAULT_STATE,
},
),
);
@@ -0,0 +1,36 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export const CREATE_CHANNEL_STEP_KEY_LIST = [
'channel-info',
'field',
'field-preview',
] as const;
export const CREATE_CHANNEL_MAIN_STEP_LIST: CreateChannelStepKey[] = [
'channel-info',
'field',
];
export const CREATE_CHANNEL_STEP_MAP: Record<CreateChannelStepKey, number> = {
'channel-info': 0,
field: 1,
'field-preview': 1,
};
export const LAST_CREATE_CHANNEL_STEP = CREATE_CHANNEL_STEP_KEY_LIST.length;
export const FIRST_CREATE_CHANNEL_STEP = 0;
export type CreateChannelStepKey =
(typeof CREATE_CHANNEL_STEP_KEY_LIST)[number];
@@ -0,0 +1,57 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { Trans } from 'next-i18next';
import type { CreateChannelStepKey } from './create-channel-type';
import InputChannelInfoStep from './ui/input-channel-info-step.ui';
import InputFieldPreviewStep from './ui/input-field-preview-step.ui';
import InputFieldStep from './ui/input-field-step.ui';
export const CREATE_CHANNEL_COMPONENTS: Record<
CreateChannelStepKey,
React.ReactNode
> = {
'channel-info': <InputChannelInfoStep />,
field: <InputFieldStep />,
'field-preview': <InputFieldPreviewStep />,
};
export const CREATE_CHANNEL_STEPPER_TEXT: Record<
CreateChannelStepKey,
React.ReactNode
> = {
'channel-info': <Trans i18nKey="channel-setting-menu.channel-info" />,
field: <Trans i18nKey="channel-setting-menu.field-mgmt" />,
'field-preview': <Trans i18nKey="main.setting.field-mgmt.preview" />,
};
export const CREATE_CHANNEL_HELP_TEXT: Record<
CreateChannelStepKey,
React.ReactNode
> = {
'channel-info': <Trans i18nKey="help-card.channel-info" />,
field: <Trans i18nKey="help-card.field" />,
'field-preview': <Trans i18nKey="help-card.field-preview" />,
};
export const CREATE_CHANNEL_REQUIRED_STEP: Record<
CreateChannelStepKey,
boolean
> = {
'channel-info': true,
field: false,
'field-preview': false,
};
@@ -0,0 +1,17 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export * from './ui';
export * from './create-channel-model';
@@ -0,0 +1,167 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useMemo } from 'react';
import { useRouter } from 'next/router';
import { useOverlay } from '@toss/use-overlay';
import { useTranslation } from 'next-i18next';
import {
Button,
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
toast,
} from '@ufb/react';
import { ErrorCode } from '@ufb/shared';
import {
CreateInputTemplate,
isObjectEqual,
Path,
useAllChannels,
useOAIMutation,
useWarnIfSavedChanges,
} from '@/shared';
import { isDefaultField } from '@/entities/field';
import {
CREATE_CHANNEL_DEFAULT_INPUT,
useCreateChannelStore,
} from '../create-channel-model';
import { CREATE_CHANNEL_MAIN_STEP_LIST } from '../create-channel-type';
import {
CREATE_CHANNEL_HELP_TEXT,
CREATE_CHANNEL_REQUIRED_STEP,
CREATE_CHANNEL_STEPPER_TEXT,
} from '../create-channel.constant';
interface IProps extends React.PropsWithChildren {
actionButton?: React.ReactNode;
validate?: () => Promise<boolean> | boolean;
disableNextBtn?: boolean;
isLoading?: boolean;
onClickBack?: () => void;
}
const CreateChannelInputTemplate: React.FC<IProps> = (props) => {
const {
children,
actionButton,
validate,
disableNextBtn,
isLoading,
onClickBack,
} = props;
const { t } = useTranslation();
const { currentStep, nextStep, prevStep, input, reset, jumpStepByKey } =
useCreateChannelStore();
const router = useRouter();
const projectId = Number(router.query.projectId);
const overlay = useOverlay();
const { refetch } = useAllChannels(projectId);
const openCreateChannelError = () => {
overlay.open(({ isOpen, close }) => (
<Dialog open={isOpen} onOpenChange={close}>
<DialogContent>
<DialogHeader>{t('text.guide')}</DialogHeader>
<DialogBody>
<DialogDescription>
{t('main.create-channel.guide.invalid-channel')}
</DialogDescription>
</DialogBody>
<DialogFooter>
<DialogClose>{t('v2.button.cancel')}</DialogClose>
<Button onClick={() => jumpStepByKey('channel-info')}>
{t('v2.button.confirm')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
));
};
const { mutate } = useOAIMutation({
method: 'post',
path: '/api/admin/projects/{projectId}/channels',
pathParams: { projectId },
queryOptions: {
async onSuccess(data) {
await refetch();
await router.replace({
pathname: Path.CREATE_CHANNEL_COMPLETE,
query: { projectId, channelId: data?.id },
});
reset();
},
onError(error) {
if (error.code === ErrorCode.Channel.ChannelAlreadyExists) {
openCreateChannelError();
} else {
toast.error(error.message);
}
},
},
});
const isDefaultInput = useMemo(
() => isObjectEqual(CREATE_CHANNEL_DEFAULT_INPUT, input),
[input],
);
useWarnIfSavedChanges(
!isDefaultInput,
Path.CREATE_CHANNEL_COMPLETE.replace(
'[projectId]',
router.query.projectId as string,
),
);
const onComplete = () => {
mutate({
...input.channelInfo,
fields: input.fields.filter((v) => !isDefaultField(v)),
});
};
return (
<CreateInputTemplate
currentStepIndex={currentStep.index}
lastStep={CREATE_CHANNEL_MAIN_STEP_LIST.length - 1}
onNext={nextStep}
onPrev={prevStep}
title={CREATE_CHANNEL_STEPPER_TEXT[currentStep.key]}
actionButton={actionButton}
onComplete={onComplete}
validate={validate}
disableNextBtn={disableNextBtn ?? isLoading}
helpText={CREATE_CHANNEL_HELP_TEXT[currentStep.key]}
onClickBack={onClickBack}
isRequiredStep={CREATE_CHANNEL_REQUIRED_STEP[currentStep.key]}
>
{children}
</CreateInputTemplate>
);
};
export default CreateChannelInputTemplate;
@@ -0,0 +1,70 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { CreationLayout, CreationStepper, Path } from '@/shared';
import { useCreateChannelStore } from '../create-channel-model';
import { CREATE_CHANNEL_MAIN_STEP_LIST } from '../create-channel-type';
import {
CREATE_CHANNEL_COMPONENTS,
CREATE_CHANNEL_STEPPER_TEXT,
} from '../create-channel.constant';
interface IProps {}
const CreateChannel: React.FC<IProps> = () => {
const { currentStep } = useCreateChannelStore();
const { t } = useTranslation();
const router = useRouter();
return (
<CreationLayout
title={t('main.create-channel.title')}
leftPanel={
<CreationStepper
steps={CREATE_CHANNEL_MAIN_STEP_LIST}
currentStepIndex={currentStep.index}
stepTitle={CREATE_CHANNEL_STEPPER_TEXT}
/>
}
leftBottom={
<div className="flex justify-end">
<Image
src={`/assets/images/create-channel-${currentStep.key}.svg`}
alt="Create Project"
className="align"
width={320}
height={320}
/>
</div>
}
onClickGoBack={() =>
router.push({
pathname: Path.PROJECT_MAIN,
query: { projectId: router.query.projectId },
})
}
>
{CREATE_CHANNEL_COMPONENTS[currentStep.key]}
</CreationLayout>
);
};
export default CreateChannel;
@@ -0,0 +1,16 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export { default as CreateChannel } from './create-channel.ui';
@@ -0,0 +1,75 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { zodResolver } from '@hookform/resolvers/zod';
import { FormProvider, useForm } from 'react-hook-form';
import { useAllChannels } from '@/shared';
import { EMPTY_FUNCTION } from '@/shared/utils/empty-function';
import type { ChannelInfo } from '@/entities/channel';
import { ChannelInfoForm, channelInfoSchema } from '@/entities/channel';
import { useCreateChannelStore } from '../create-channel-model';
import CreateChannelInputTemplate from './create-channel-input-template.ui';
interface IProps {}
const InputChannelInfoStep: React.FC<IProps> = () => {
const { onChangeInput, input, setEditingStepIndex } = useCreateChannelStore();
const router = useRouter();
const projectId = Number(router.query.projectId);
const { data } = useAllChannels(projectId);
const methods = useForm<ChannelInfo>({
resolver: zodResolver(channelInfoSchema),
defaultValues: input.channelInfo,
});
useEffect(() => {
const subscription = methods.watch((values) => {
const newValues = channelInfoSchema.safeParse(values);
if (!newValues.data) return;
onChangeInput('channelInfo', newValues.data);
setEditingStepIndex(0);
});
return () => subscription.unsubscribe();
}, []);
return (
<CreateChannelInputTemplate
validate={async () => {
const isValid = await methods.trigger();
await methods.handleSubmit(EMPTY_FUNCTION)();
const isDuplicated = data?.items.some(
(v) => v.name === methods.getValues('name'),
);
if (isDuplicated) {
methods.setError('name', { message: 'Duplicated name' });
return false;
}
return isValid;
}}
>
<FormProvider {...methods}>
<ChannelInfoForm type="create" />
</FormProvider>
</CreateChannelInputTemplate>
);
};
export default InputChannelInfoStep;
@@ -0,0 +1,36 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { PreviewFieldTable } from '@/entities/field';
import { useCreateChannelStore } from '../create-channel-model';
import CreateChannelInputTemplate from './create-channel-input-template.ui';
interface IProps {}
const InputFieldPreviewStep: React.FC<IProps> = () => {
const { input, jumpStepByKey } = useCreateChannelStore();
return (
<CreateChannelInputTemplate onClickBack={() => jumpStepByKey('field')}>
<PreviewFieldTable
fields={input.fields.filter((v) => v.status === 'ACTIVE')}
/>
</CreateChannelInputTemplate>
);
};
export default InputFieldPreviewStep;
@@ -0,0 +1,114 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useOverlay } from '@toss/use-overlay';
import { useTranslation } from 'next-i18next';
import { Icon, ToggleGroup, ToggleGroupItem } from '@ufb/react';
import { FieldSettingSheet } from '@/entities/field';
import type { FieldInfo } from '@/entities/field';
import FieldTable from '@/entities/field/ui/field-table.ui';
import { useCreateChannelStore } from '../create-channel-model';
import CreateChannelInputTemplate from './create-channel-input-template.ui';
interface IProps {}
const InputFieldStep: React.FC<IProps> = () => {
const { input, onChangeInput, jumpStepByKey } = useCreateChannelStore();
const fields = input.fields;
const { t } = useTranslation();
const overlay = useOverlay();
const createField = (field: FieldInfo) => {
onChangeInput(
'fields',
input.fields.concat({ ...field, order: fields.length }),
);
};
const updateField = (input: { index: number; field: FieldInfo }) => {
const { field, index } = input;
onChangeInput(
'fields',
fields.map((v, i) => (i === index ? field : v)),
);
};
const deleteField = ({ index }: { index: number }) => {
onChangeInput(
'fields',
input.fields.filter((_, i) => i !== index),
);
};
const openFieldFormSheet = (input?: { index: number; field: FieldInfo }) => {
overlay.open(({ close, isOpen }) => (
<FieldSettingSheet
isOpen={isOpen}
close={close}
onSubmit={(newField) => {
if (input) {
updateField({ index: input.index, field: newField });
return;
}
createField(newField);
}}
fieldRows={fields}
data={input?.field}
onClickDelete={
input ?
() => {
deleteField({ index: input.index });
close();
}
: undefined
}
/>
));
};
return (
<CreateChannelInputTemplate
actionButton={
<ToggleGroup type="single" value="">
<ToggleGroupItem value="item-1" onClick={() => openFieldFormSheet()}>
<Icon name="RiAddLine" />
{t('v2.button.name.add', { name: 'Field' })}
</ToggleGroupItem>
<ToggleGroupItem
value="item-2"
onClick={() => jumpStepByKey('field-preview')}
>
<Icon name="RiEyeLine" />
{t('v2.text.preview')}
</ToggleGroupItem>
</ToggleGroup>
}
>
<FieldTable
fields={input.fields}
onClickRow={(index, field) => openFieldFormSheet({ index, field })}
disableFilter
/>
</CreateChannelInputTemplate>
);
};
export default InputFieldStep;