오류 수정
CI / test (push) Failing after 5m41s
CI / e2e (push) Has been skipped
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-12-12 18:04:31 +09:00
parent 6804665b95
commit 4840a530b6
205 changed files with 1802 additions and 9887 deletions
+87 -63
View File
@@ -10,7 +10,8 @@ import { createFaqTemplateBlocks } from "@/app/editor/templates/faqTemplate";
import { createPricingTemplateBlocks } from "@/app/editor/templates/pricingTemplate";
import { createTestimonialsTemplateBlocks } from "@/app/editor/templates/testimonialsTemplate";
import { getEditorTemplatesMessages } from "@/features/i18n/messages/editorTemplates";
import { DEFAULT_LOCALE } from "@/features/i18n/locale";
import { getEditorBlockDefaultsMessages } from "@/features/i18n/messages/editorBlockDefaults";
import { DEFAULT_LOCALE, type AppLocale } from "@/features/i18n/locale";
// 블록 타입 정의: 텍스트/버튼/이미지/비디오/섹션/구분선/리스트/폼 블록/폼 요소 블록
export type BlockType =
@@ -739,27 +740,27 @@ export interface EditorState {
future: Block[][];
projectConfig: ProjectConfig;
updateProjectConfig: (partial: Partial<ProjectConfig>) => void;
addTextBlock: () => void;
addButtonBlock: () => void;
addTextBlock: (locale?: AppLocale | null) => void;
addButtonBlock: (locale?: AppLocale | null) => void;
addImageBlock: () => void;
addVideoBlock: () => void;
addDividerBlock: () => void;
addListBlock: () => void;
addListBlock: (locale?: AppLocale | null) => void;
addSectionBlock: () => void;
addFormBlock: () => void;
addFormInputBlock: () => void;
addFormSelectBlock: () => void;
addFormCheckboxBlock: () => void;
addFormRadioBlock: () => void;
addHeroTemplateSection: () => void;
addFeaturesTemplateSection: () => void;
addCtaTemplateSection: () => void;
addFaqTemplateSection: () => void;
addPricingTemplateSection: () => void;
addTestimonialsTemplateSection: () => void;
addBlogTemplateSection: () => void;
addTeamTemplateSection: () => void;
addFooterTemplateSection: () => void;
addFormBlock: (locale?: AppLocale | null) => void;
addFormInputBlock: (locale?: AppLocale | null) => void;
addFormSelectBlock: (locale?: AppLocale | null) => void;
addFormCheckboxBlock: (locale?: AppLocale | null) => void;
addFormRadioBlock: (locale?: AppLocale | null) => void;
addHeroTemplateSection: (locale?: AppLocale | null) => void;
addFeaturesTemplateSection: (locale?: AppLocale | null) => void;
addCtaTemplateSection: (locale?: AppLocale | null) => void;
addFaqTemplateSection: (locale?: AppLocale | null) => void;
addPricingTemplateSection: (locale?: AppLocale | null) => void;
addTestimonialsTemplateSection: (locale?: AppLocale | null) => void;
addBlogTemplateSection: (locale?: AppLocale | null) => void;
addTeamTemplateSection: (locale?: AppLocale | null) => void;
addFooterTemplateSection: (locale?: AppLocale | null) => void;
selectListItem: (itemId: string | null) => void;
indentSelectedListItem: (blockId: string) => void;
outdentSelectedListItem: (blockId: string) => void;
@@ -819,6 +820,8 @@ const getNextFormFieldName = (blocks: Block[], prefix: string): string => {
return `${prefix}-${max + 1}`;
};
const isKoLocale = (locale?: AppLocale | null): boolean => (locale ?? DEFAULT_LOCALE) === "ko";
// 에디터 스토어 생성 함수 (테스트 및 앱에서 공유 사용)
// set/get 은 zustand 내부 구현에 의해 주입되므로, 여기서는 any 로 완화해 사용한다.
const createEditorState = (set: any, get: any): EditorState => {
@@ -837,7 +840,7 @@ const createEditorState = (set: any, get: any): EditorState => {
history: [],
future: [],
projectConfig: {
title: "새 페이지",
title: "New page",
slug: "my-landing",
canvasPreset: "full",
canvasBgColorHex: "#020617",
@@ -862,7 +865,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 텍스트 블록 추가: 기본 텍스트와 함께 생성 후 선택 상태로 만든다
addTextBlock: () => {
addTextBlock: (locale?: AppLocale | null) => {
const id = createId();
const { selectedBlockId, blocks } = get();
@@ -883,11 +886,12 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const newBlock: Block = {
id,
type: "text",
props: {
text: "새 텍스트",
text: blockDefaults.text.defaultText,
align: "left",
size: "base",
},
@@ -903,7 +907,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Hero 템플릿 섹션 추가: 섹션 1개와 기본 텍스트/버튼 블록들을 첫 컬럼에 배치한다
addHeroTemplateSection: () => {
addHeroTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -916,7 +920,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createHeroTemplateBlocks({
sectionId,
createId,
@@ -942,7 +946,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Features 템플릿 섹션 추가: 3컬럼(4/4/4) 섹션과 각 컬럼의 제목/설명 텍스트를 생성한다
addFeaturesTemplateSection: () => {
addFeaturesTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -955,7 +959,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createFeaturesTemplateBlocks({
sectionId,
createId,
@@ -981,7 +985,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Blog 템플릿 섹션 추가: 3컬럼 포스트 카드(제목/요약)를 생성한다
addBlogTemplateSection: () => {
addBlogTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -994,7 +998,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createBlogTemplateBlocks({
sectionId,
createId,
@@ -1020,7 +1024,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Team 템플릿 섹션 추가: 3컬럼 팀 카드(이름/역할/소개)를 생성한다
addTeamTemplateSection: () => {
addTeamTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1033,7 +1037,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createTeamTemplateBlocks({
sectionId,
createId,
@@ -1059,7 +1063,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Footer 템플릿 섹션 추가: 링크/카피라이트 텍스트가 포함된 1컬럼 섹션을 생성한다
addFooterTemplateSection: () => {
addFooterTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1072,7 +1076,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createFooterTemplateBlocks({
sectionId,
createId,
@@ -1098,7 +1102,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// CTA 템플릿 섹션 추가: 텍스트와 버튼이 포함된 1컬럼 섹션을 생성한다
addCtaTemplateSection: () => {
addCtaTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1111,7 +1115,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createCtaTemplateBlocks({
sectionId,
createId,
@@ -1137,7 +1141,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// FAQ 템플릿 섹션 추가: 질문/답변 쌍이 수직으로 나열된 섹션을 생성한다
addFaqTemplateSection: () => {
addFaqTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1150,7 +1154,7 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createFaqTemplateBlocks({
sectionId,
createId,
@@ -1176,7 +1180,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Pricing 템플릿 섹션 추가: 3컬럼 요금제 카드(플랜 이름/가격/설명)를 생성한다
addPricingTemplateSection: () => {
addPricingTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1189,9 +1193,11 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createPricingTemplateBlocks({
sectionId,
createId,
messages: tpl.pricing,
});
pushHistoryImpl(set, get);
@@ -1213,7 +1219,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// Testimonials 템플릿 섹션 추가: 3컬럼 후기 카드(본문/작성자)를 생성한다
addTestimonialsTemplateSection: () => {
addTestimonialsTemplateSection: (locale?: AppLocale | null) => {
const { selectedBlockId, blocks } = get();
let sectionId = createId();
let replacingSectionId: string | null = null;
@@ -1226,9 +1232,11 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const tpl = getEditorTemplatesMessages(locale);
const { blocks: templateBlocks, lastSelectedId } = createTestimonialsTemplateBlocks({
sectionId,
createId,
messages: tpl.testimonials,
});
pushHistoryImpl(set, get);
@@ -1276,7 +1284,7 @@ const createEditorState = (set: any, get: any): EditorState => {
type: "image",
props: {
src: "",
alt: "이미지 설명",
alt: "Image description",
align: "center",
widthMode: "auto",
borderRadius: "md",
@@ -1340,11 +1348,12 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 폼 입력 블록 추가: 루트에 기본 label/formFieldName 과 함께 생성 후 선택 상태로 만든다
addFormInputBlock: () => {
addFormInputBlock: (locale?: AppLocale | null) => {
const id = createId();
const { blocks } = get();
const label = "입력 필드";
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const label = blockDefaults.formInput.label;
const formFieldName = getNextFormFieldName(blocks, "text");
const newBlock: Block = {
@@ -1380,15 +1389,17 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 폼 셀렉트 블록 추가: 루트에 기본 label/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
addFormSelectBlock: () => {
addFormSelectBlock: (locale?: AppLocale | null) => {
const id = createId();
const { blocks } = get();
const label = "선택 필드";
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const selectDefaults = blockDefaults.formSelect;
const label = selectDefaults.label;
const formFieldName = getNextFormFieldName(blocks, "select");
const options: FormSelectOption[] = [
{ label: "옵션 1", value: "option_1" },
{ label: "옵션 2", value: "option_2" },
{ label: selectDefaults.option1Label, value: "option_1" },
{ label: selectDefaults.option2Label, value: "option_2" },
];
const newBlock: Block = {
@@ -1422,15 +1433,17 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 폼 체크박스 블록 추가: 루트에 기본 groupLabel/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
addFormCheckboxBlock: () => {
addFormCheckboxBlock: (locale?: AppLocale | null) => {
const id = createId();
const { blocks } = get();
const groupLabel = "체크박스";
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const checkboxDefaults = blockDefaults.formCheckbox;
const groupLabel = checkboxDefaults.groupLabel;
const formFieldName = getNextFormFieldName(blocks, "checkbox");
const options: FormCheckboxOption[] = [
{ label: "체크 1", value: "check_1" },
{ label: "체크 2", value: "check_2" },
{ label: checkboxDefaults.option1Label, value: "check_1" },
{ label: checkboxDefaults.option2Label, value: "check_2" },
];
const newBlock: Block = {
@@ -1465,15 +1478,17 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 폼 라디오 그룹 블록 추가: 루트에 기본 groupLabel/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
addFormRadioBlock: () => {
addFormRadioBlock: (locale?: AppLocale | null) => {
const id = createId();
const { blocks } = get();
const groupLabel = "라디오 그룹";
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const radioDefaults = blockDefaults.formRadio;
const groupLabel = radioDefaults.groupLabel;
const formFieldName = getNextFormFieldName(blocks, "radio");
const options: FormRadioOption[] = [
{ label: "옵션 A", value: "option_a" },
{ label: "옵션 B", value: "option_b" },
{ label: radioDefaults.optionALabel, value: "option_a" },
{ label: radioDefaults.optionBLabel, value: "option_b" },
];
const newBlock: Block = {
@@ -1549,7 +1564,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 리스트 블록 추가: 기본 항목/정렬과 함께 생성 후 선택 상태로 만든다
addListBlock: () => {
addListBlock: (locale?: AppLocale | null) => {
const id = createId();
const { selectedBlockId, blocks } = get();
@@ -1570,15 +1585,18 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const itemText = blockDefaults.list.firstItemText;
const newBlock: Block = {
id,
type: "list",
props: {
items: ["리스트 아이템 1"],
items: [itemText],
itemsTree: [
{
id: `${id}_item_1`,
text: "리스트 아이템 1",
text: itemText,
children: [],
},
],
@@ -1624,28 +1642,31 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 폼 블록 추가: 기본 contact 폼 설정과 함께 생성 후 선택 상태로 만든다
addFormBlock: () => {
addFormBlock: (locale?: AppLocale | null) => {
const id = createId();
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const formDefaults = blockDefaults.form;
const fields: FormFieldConfig[] = [
{
id: `${id}_field_name`,
name: "name",
label: "이름",
label: formDefaults.nameLabel,
type: "text",
required: true,
},
{
id: `${id}_field_email`,
name: "email",
label: "이메일",
label: formDefaults.emailLabel,
type: "email",
required: true,
},
{
id: `${id}_field_message`,
name: "message",
label: "메시지",
label: formDefaults.messageLabel,
type: "textarea",
required: true,
},
@@ -1657,8 +1678,8 @@ const createEditorState = (set: any, get: any): EditorState => {
props: {
kind: "contact",
submitTarget: "internal",
successMessage: "성공적으로 전송되었습니다.",
errorMessage: "전송 중 오류가 발생했습니다.",
successMessage: formDefaults.successMessage,
errorMessage: formDefaults.errorMessage,
fields,
fieldIds: [],
submitButtonId: null,
@@ -1675,7 +1696,7 @@ const createEditorState = (set: any, get: any): EditorState => {
},
// 버튼 블록 추가: 기본 라벨/링크와 함께 생성 후 선택 상태로 만든다
addButtonBlock: () => {
addButtonBlock: (locale?: AppLocale | null) => {
const id = createId();
const { selectedBlockId, blocks } = get();
@@ -1695,11 +1716,14 @@ const createEditorState = (set: any, get: any): EditorState => {
}
}
}
const blockDefaults = getEditorBlockDefaultsMessages(locale);
const newBlock: Block = {
id,
type: "button",
props: {
label: "버튼",
label: blockDefaults.button.label,
href: "#",
align: "left",
size: "md",