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,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;