폼전송 기능 수정
CI / test (push) Failing after 4m53s
CI / e2e (push) Has been skipped
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-12-07 19:11:06 +09:00
parent 96fa34cd86
commit 243083261f
27 changed files with 1025 additions and 5 deletions
+115
View File
@@ -150,6 +150,7 @@ function EditorPageInner() {
const [projectMessage, setProjectMessage] = useState("");
// 멀티 선택(MVP)을 위한 로컬 선택 상태: 단일 선택(selectedBlockId)과 병행 사용한다.
const [selectedBlockIds, setSelectedBlockIds] = useState<string[]>([]);
const [initializedNewProjectFromQuery, setInitializedNewProjectFromQuery] = useState(false);
const historyLength = useEditorStore((state) => (state as any).history?.length ?? 0);
const futureLength = useEditorStore((state) => (state as any).future?.length ?? 0);
@@ -176,6 +177,75 @@ function EditorPageInner() {
}
}, [initialSlugFromQuery, projectConfig?.slug, updateProjectConfig]);
useEffect(() => {
if (!searchParams) return;
if (initializedNewProjectFromQuery) return;
const newParam = searchParams.get("new");
if (newParam !== "1") return;
if (blocks.length > 0) {
replaceBlocks([]);
}
updateProjectConfig({
title: "새 페이지",
slug: "my-landing",
canvasPreset: "full",
canvasBgColorHex: "#020617",
bodyBgColorHex: "#020617",
headHtml: "",
trackingScript: "",
seoTitle: "",
seoDescription: "",
seoOgImageUrl: "",
seoCanonicalUrl: "",
seoNoIndex: false,
} as ProjectConfig);
resetHistory();
setInitializedNewProjectFromQuery(true);
}, [searchParams, initializedNewProjectFromQuery, blocks.length, replaceBlocks, updateProjectConfig, resetHistory]);
useEffect(() => {
if (!searchParams) return;
const slugParam = searchParams.get("slug");
if (slugParam && slugParam.trim().length > 0) {
// URL 에 slug 쿼리 파라미터가 있으면 기존 프로젝트 편집 중이므로,
// 자동 슬러그 생성은 동작시키지 않는다.
return;
}
const currentSlugRaw = projectConfig?.slug;
const currentSlug = typeof currentSlugRaw === "string" ? currentSlugRaw.trim() : "";
// 초기 상태("my-landing") 또는 비어 있는 경우에만 자동 슬러그를 한 번 생성한다.
if (currentSlug && currentSlug !== "my-landing") {
return;
}
const now = new Date();
const datePart = [
now.getFullYear().toString().padStart(4, "0"),
(now.getMonth() + 1).toString().padStart(2, "0"),
now.getDate().toString().padStart(2, "0"),
].join("");
const timePart = [
now.getHours().toString().padStart(2, "0"),
now.getMinutes().toString().padStart(2, "0"),
now.getSeconds().toString().padStart(2, "0"),
].join("");
const randomPart = Math.random().toString(36).slice(2, 6);
const generatedSlug = `page-${datePart}-${timePart}-${randomPart}`;
updateProjectConfig({ slug: generatedSlug });
}, [searchParams, projectConfig?.slug, updateProjectConfig]);
useEffect(() => {
if (!authChecked) return;
if (!initialSlugFromQuery) return;
@@ -817,6 +887,51 @@ function EditorPageInner() {
}
}, [blocks, projectConfig, authUserId, authChecked]);
useEffect(() => {
if (!authChecked) return;
if (!authUserId) return;
if (typeof window === "undefined") return;
const slugRaw = projectConfig?.slug;
const slug = typeof slugRaw === "string" ? slugRaw.trim() : "";
if (!slug || slug === "my-landing") return;
const titleRaw = projectConfig?.title;
const title = (typeof titleRaw === "string" ? titleRaw.trim() : "") || "제목 없음";
const intervalMs = 20000;
let cancelled = false;
const saveOnce = async () => {
if (cancelled) return;
try {
await fetch("/api/projects", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title,
slug,
contentJson: blocks,
}),
});
} catch {
// 서버 자동 저장 실패는 치명적이지 않으므로 조용히 무시한다.
}
};
const id = window.setInterval(() => {
void saveOnce();
}, intervalMs);
return () => {
cancelled = true;
window.clearInterval(id);
};
}, [authChecked, authUserId, projectConfig?.slug, projectConfig?.title, blocks]);
// 에디터 전역에서 Undo/Redo 단축키(Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z),
// Delete/Backspace 기반 삭제, Cmd/Ctrl+D 기반 복제,
// ArrowUp/ArrowDown을 이용한 선택 블록 이동을 처리한다.