From b91cfc9efe0a8073e77852a5144865fdab6c872e Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Wed, 31 Dec 2025 19:16:38 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Feature=20:=20=EB=9E=9C?= =?UTF-8?q?=EB=94=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=8C=80?= =?UTF-8?q?=EC=8B=9C=EB=B3=B4=EB=93=9C/=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(landing)/_components/CTASection.tsx | 2 +- src/app/(landing)/_components/HeroSection.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(landing)/_components/CTASection.tsx b/src/app/(landing)/_components/CTASection.tsx index 7432de9..04ee3e6 100644 --- a/src/app/(landing)/_components/CTASection.tsx +++ b/src/app/(landing)/_components/CTASection.tsx @@ -53,7 +53,7 @@ export default function CTASection() { 오늘의 할 일, Slice로 계획해요 router.push("/login")} + onClick={() => router.push("/dashboard")} className="bg-orange-250 cursor-pointer rounded-full px-10 py-3.5 text-base font-semibold text-white shadow-lg transition-all duration-300 hover:bg-[#FF7043] sm:px-16 sm:py-4 sm:text-lg" whileHover={{ scale: 1.05, diff --git a/src/app/(landing)/_components/HeroSection.tsx b/src/app/(landing)/_components/HeroSection.tsx index 8e6932a..dd7ad25 100644 --- a/src/app/(landing)/_components/HeroSection.tsx +++ b/src/app/(landing)/_components/HeroSection.tsx @@ -95,7 +95,7 @@ export default function HeroSection() { 오늘의 할 일, Slice로 계획해요 router.push("/login")} + onClick={() => router.push("/dashboard")} className="bg-orange-250 cursor-pointer rounded-full px-10 py-3.5 text-base font-semibold text-white shadow-lg transition-all duration-300 hover:bg-[#FF7043] sm:px-16 sm:py-4 sm:text-lg" whileHover={{ scale: 1.05, From 02a0a7bdd72cc1b8330cbcb103bc7d0d08908b21 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Wed, 31 Dec 2025 19:36:26 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=92=84=20Style=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20Skeleton=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/_components/NoteDetailSkeleton.tsx | 22 +++++++++---------- .../notes/_components/NoteItemSkeleton.tsx | 6 ++--- src/app/globals.css | 1 - 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/app/(protected)/notes/_components/NoteDetailSkeleton.tsx b/src/app/(protected)/notes/_components/NoteDetailSkeleton.tsx index 55ac21a..4d4e43a 100644 --- a/src/app/(protected)/notes/_components/NoteDetailSkeleton.tsx +++ b/src/app/(protected)/notes/_components/NoteDetailSkeleton.tsx @@ -2,21 +2,21 @@ export default function NoteDetailSkeleton() { return (
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
); diff --git a/src/app/(protected)/notes/_components/NoteItemSkeleton.tsx b/src/app/(protected)/notes/_components/NoteItemSkeleton.tsx index bb96555..a08744c 100644 --- a/src/app/(protected)/notes/_components/NoteItemSkeleton.tsx +++ b/src/app/(protected)/notes/_components/NoteItemSkeleton.tsx @@ -2,11 +2,11 @@ export default function NoteItemSkeleton() { return (
-
+
-
-
+
+
); diff --git a/src/app/globals.css b/src/app/globals.css index 2c0e646..12fff74 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -10,7 +10,6 @@ --color-gray-50: #f5f5f5; --color-gray-80: #e6e6e6; --color-gray-100: #dddddd; - --color-gray-150: #e6e6e6; --color-gray-200: #cccccc; --color-gray-300: #bbbbbb; --color-gray-400: #a4a4a4; From 71ce470f1416ae146177203ee0247c5f6fc8b1e9 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 00:22:04 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=A8=20Feature=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1=20=EC=99=84=EB=A3=8C=20=ED=9B=84?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(protected)/notes/_components/NoteCreateContainer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/(protected)/notes/_components/NoteCreateContainer.tsx b/src/app/(protected)/notes/_components/NoteCreateContainer.tsx index 7b346ef..11bbf8d 100644 --- a/src/app/(protected)/notes/_components/NoteCreateContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteCreateContainer.tsx @@ -50,6 +50,7 @@ export default function NoteCreateContainer({ { onSuccess: (data) => { draftNoteStorage.remove(todoId); + toast.success("노트가 작성되었습니다."); router.replace(`/notes?goalId=${data.goal.id}`); }, onError: (error) => { From 6eeadd45e4b28206ecf0d5b84c44de0a9f223687 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 00:27:54 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20Feature=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20=EC=99=84=EB=A3=8C=20=ED=9B=84?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(protected)/notes/_components/NoteListContainer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/(protected)/notes/_components/NoteListContainer.tsx b/src/app/(protected)/notes/_components/NoteListContainer.tsx index 368232b..ce88f50 100644 --- a/src/app/(protected)/notes/_components/NoteListContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteListContainer.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/navigation"; import EmptyState from "@/components/common/empty-state/EmptyState"; import { EMPTY_MESSAGES } from "@/constants/messages"; import ConfirmModal from "@/components/common/popup-modal/ConfirmModal"; +import { toast } from "@/lib/toast"; import GoalBanner from "./GoalBanner"; import NoteList from "./NoteList"; @@ -40,11 +41,11 @@ export default function NoteListContainer({ goalId }: NoteListContainerProps) { onSuccess: () => { setDeleteNoteId(null); setIsDeleteModalOpen(false); + toast.success("노트가 삭제되었습니다."); }, onError: (error) => { - console.error("삭제 실패:", error); - alert("노트 삭제에 실패했습니다."); + toast.error(error.message); }, }, ); From 84eb405f334c1ec06a3df7810cd22af805987845 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 00:32:12 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=A8=20Feature=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95=20=EC=99=84=EB=A3=8C=20=ED=9B=84?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(protected)/notes/_components/NoteEditContainer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/(protected)/notes/_components/NoteEditContainer.tsx b/src/app/(protected)/notes/_components/NoteEditContainer.tsx index af36bb8..6f72265 100644 --- a/src/app/(protected)/notes/_components/NoteEditContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteEditContainer.tsx @@ -58,6 +58,7 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { { onSuccess: (data) => { draftNoteStorage.remove(todoId); + toast.success("노트가 수정되었습니다."); router.replace(`/notes?goalId=${data.goal.id}`); }, onError: (error) => { From aa8d56b47031f05af2f17424ce169eca018fd058 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 00:33:23 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E2=9C=A8=20Feature=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(protected)/notes/_components/NoteListContainer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/(protected)/notes/_components/NoteListContainer.tsx b/src/app/(protected)/notes/_components/NoteListContainer.tsx index ce88f50..18ca5ec 100644 --- a/src/app/(protected)/notes/_components/NoteListContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteListContainer.tsx @@ -45,7 +45,8 @@ export default function NoteListContainer({ goalId }: NoteListContainerProps) { }, onError: (error) => { - toast.error(error.message); + console.error("삭제 실패:", error); + toast.error("노트 삭제에 실패했습니다."); }, }, ); From 44bdc2f6ba715d3806aab6153a13825142faf35f Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 02:15:31 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=94=A8=20Refactor=20:=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=BC=20=EA=B4=80=EB=A6=AC=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/_components/NoteCreateContainer.tsx | 54 +++-- .../notes/_components/NoteEditContainer.tsx | 52 +++-- .../notes/_components/NoteEditorForm.tsx | 6 +- .../(protected)/notes/_hooks/useNoteForm.ts | 215 +++++++++--------- .../(protected)/notes/_utils/draft-note.ts | 2 +- 5 files changed, 176 insertions(+), 153 deletions(-) diff --git a/src/app/(protected)/notes/_components/NoteCreateContainer.tsx b/src/app/(protected)/notes/_components/NoteCreateContainer.tsx index 11bbf8d..bd19ba5 100644 --- a/src/app/(protected)/notes/_components/NoteCreateContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteCreateContainer.tsx @@ -25,9 +25,15 @@ export default function NoteCreateContainer({ const { data: todo } = useTodoQuery(todoId); const { mutate: createNoteMutation, isPending } = useCreateNoteMutation(); - const form = useNoteForm({ - todoId, - }); + const { + form, + embed, + draft, + loadModal, + changeTitle, + changeContent, + changeLinkUrl, + } = useNoteForm({ todoId }); const handleSubmit = () => { if (!form.title.trim()) { @@ -40,12 +46,14 @@ export default function NoteCreateContainer({ return; } + const trimmedLinkUrl = form.linkUrl?.trim(); + createNoteMutation( { todoId, title: form.title.trim(), content: JSON.stringify(form.content), - ...(form.linkUrl.trim() && { linkUrl: form.linkUrl.trim() }), + ...(trimmedLinkUrl && { linkUrl: trimmedLinkUrl }), }, { onSuccess: (data) => { @@ -80,7 +88,7 @@ export default function NoteCreateContainer({ } @@ -88,16 +96,16 @@ export default function NoteCreateContainer({ } /> - {form.hasDraftNote && ( + {draft.hasNote && (
)} @@ -106,24 +114,24 @@ export default function NoteCreateContainer({ content={form.content} linkUrl={form.linkUrl} linkMetadata={form.linkMetadata} - isEmbedOpen={form.isEmbedOpen} - onChangeTitle={form.handleTitleChange} - onChangeContent={form.handleContentChange} - onChangeLinkUrl={form.handleLinkUrlChange} - onToggleEmbed={form.handleToggleEmbed} - onDeleteLinkPreview={form.handleDeleteLinkPreview} + isEmbedOpen={embed.isOpen} + onChangeTitle={changeTitle} + onChangeContent={changeContent} + onChangeLinkUrl={changeLinkUrl} + onToggleEmbed={embed.toggle} + onDeleteLinkPreview={embed.deletePreview} metaInfo={metaInfo} - hasDraftNote={form.hasDraftNote} - onLoadDraft={form.handleLoadModalOpen} - onCloseDraftCallout={form.handleDraftCalloutClose} + hasDraftNote={draft.hasNote} + onLoadDraft={loadModal.open} + onCloseDraftCallout={draft.closeCallout} />
- {form.isLoadModalOpen && ( + {loadModal.isOpen && ( )} diff --git a/src/app/(protected)/notes/_components/NoteEditContainer.tsx b/src/app/(protected)/notes/_components/NoteEditContainer.tsx index 6f72265..c9daf2e 100644 --- a/src/app/(protected)/notes/_components/NoteEditContainer.tsx +++ b/src/app/(protected)/notes/_components/NoteEditContainer.tsx @@ -24,7 +24,15 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { const todoId = note.todo.id; - const form = useNoteForm({ + const { + form, + embed, + draft, + loadModal, + changeTitle, + changeContent, + changeLinkUrl, + } = useNoteForm({ todoId, isEditMode: true, initialData: { @@ -46,13 +54,15 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { return; } + const trimmedLinkUrl = form.linkUrl?.trim(); + updateNoteMutation( { noteId, data: { title: form.title.trim(), content: JSON.stringify(form.content), - linkUrl: form.linkUrl.trim() || null, + linkUrl: trimmedLinkUrl || null, }, }, { @@ -88,7 +98,7 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { } @@ -96,16 +106,16 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { } /> - {form.hasDraftNote && ( + {draft.hasNote && (
)} @@ -114,24 +124,24 @@ export default function NoteEditContainer({ noteId }: NoteEditContainerProps) { content={form.content} linkUrl={form.linkUrl} linkMetadata={form.linkMetadata} - isEmbedOpen={form.isEmbedOpen} - onChangeTitle={form.handleTitleChange} - onChangeContent={form.handleContentChange} - onChangeLinkUrl={form.handleLinkUrlChange} - onToggleEmbed={form.handleToggleEmbed} - onDeleteLinkPreview={form.handleDeleteLinkPreview} + isEmbedOpen={embed.isOpen} + onChangeTitle={changeTitle} + onChangeContent={changeContent} + onChangeLinkUrl={changeLinkUrl} + onToggleEmbed={embed.toggle} + onDeleteLinkPreview={embed.deletePreview} metaInfo={metaInfo} - hasDraftNote={form.hasDraftNote} - onLoadDraft={form.handleLoadModalOpen} - onCloseDraftCallout={form.handleDraftCalloutClose} + hasDraftNote={draft.hasNote} + onLoadDraft={loadModal.open} + onCloseDraftCallout={draft.closeCallout} />
- {form.isLoadModalOpen && ( + {loadModal.isOpen && ( )} diff --git a/src/app/(protected)/notes/_components/NoteEditorForm.tsx b/src/app/(protected)/notes/_components/NoteEditorForm.tsx index efec160..361b326 100644 --- a/src/app/(protected)/notes/_components/NoteEditorForm.tsx +++ b/src/app/(protected)/notes/_components/NoteEditorForm.tsx @@ -18,7 +18,7 @@ import { NoteLinkPreview } from "./NoteLinkPreview"; interface NoteEditorFormProps { title: string; content: JSONContent | null; - linkUrl: string; + linkUrl: string | null; linkMetadata: LinkMetadata | null; isEmbedOpen: boolean; onChangeTitle: (e: React.ChangeEvent) => void; @@ -72,12 +72,12 @@ export default function NoteEditorForm({ const countWithoutSpace = text.replace(/\s+/g, "").length; const handleOpenLinkModal = () => { - setTempLinkUrl(linkUrl); + setTempLinkUrl(linkUrl ?? ""); setIsLinkModalOpen(true); }; const handleCloseLinkModal = () => { - setTempLinkUrl(linkUrl); + setTempLinkUrl(linkUrl ?? ""); setIsLinkModalOpen(false); }; diff --git a/src/app/(protected)/notes/_hooks/useNoteForm.ts b/src/app/(protected)/notes/_hooks/useNoteForm.ts index 324afbf..76cb531 100644 --- a/src/app/(protected)/notes/_hooks/useNoteForm.ts +++ b/src/app/(protected)/notes/_hooks/useNoteForm.ts @@ -5,17 +5,21 @@ import { LinkMetadata } from "@/api/types/note"; import { toast } from "@/lib/toast"; import { draftNoteStorage } from "@/app/(protected)/notes/_utils/draft-note"; +interface NoteFormData { + title: string; + content: JSONContent | null; + linkUrl: string | null; + linkMetadata: LinkMetadata | null; +} + interface UseNoteFormOptions { todoId: number; isEditMode?: boolean; - initialData?: { - title: string; - content: JSONContent; - linkUrl: string | null; - linkMetadata: LinkMetadata | null; - }; + initialData?: Partial; } +const AUTO_SAVE_INTERVAL = 5 * 60 * 1000; + export function useNoteForm({ todoId, isEditMode = false, @@ -24,20 +28,25 @@ export function useNoteForm({ const { mutate: getLinkMetadataMutation } = useLinkMetadataMutation(); const isInitialized = useRef(false); - const [title, setTitle] = useState(""); - const [content, setContent] = useState(null); - const [linkUrl, setLinkUrl] = useState(""); - const [linkMetadata, setLinkMetadata] = useState(null); + const [form, setForm] = useState({ + title: "", + content: null, + linkUrl: null, + linkMetadata: null, + }); + const [isEmbedOpen, setIsEmbedOpen] = useState(false); const [hasDraftNote, setHasDraftNote] = useState(false); const [isLoadModalOpen, setIsLoadModalOpen] = useState(false); useEffect(() => { if (initialData && !isInitialized.current) { - setTitle(initialData.title); - setContent(initialData.content); - setLinkUrl(initialData.linkUrl ?? ""); - setLinkMetadata(initialData.linkMetadata ?? null); + setForm({ + title: initialData.title ?? "", + content: initialData.content ?? null, + linkUrl: initialData.linkUrl ?? null, + linkMetadata: initialData.linkMetadata ?? null, + }); isInitialized.current = true; } }, [initialData]); @@ -49,131 +58,127 @@ export function useNoteForm({ useEffect(() => { if (isEditMode || !todoId) return; - const interval = setInterval( - () => { - if (title.trim() || content) { - draftNoteStorage.save(todoId, { - title, - content: content || { type: "doc", content: [] }, - linkUrl, - linkMetadata, - }); - setHasDraftNote(true); - toast.success("임시 저장이 완료되었습니다", { hasTime: true }); - } - }, - 5 * 60 * 1000, - ); + const interval = setInterval(() => { + if (form.title.trim() || form.content) { + draftNoteStorage.save(todoId, { + title: form.title, + content: form.content || { type: "doc", content: [] }, + linkUrl: form.linkUrl, + linkMetadata: form.linkMetadata, + }); + setHasDraftNote(true); + toast.success("임시 저장이 완료되었습니다", { hasTime: true }); + } + }, AUTO_SAVE_INTERVAL); return () => clearInterval(interval); - }, [isEditMode, todoId, title, content, linkUrl, linkMetadata]); + }, [isEditMode, todoId, form]); - const handleTitleChange = (e: React.ChangeEvent) => { - setTitle(e.target.value); + const changeTitle = (e: React.ChangeEvent) => { + setForm((prev) => ({ ...prev, title: e.target.value })); }; - const handleContentChange = (newContent: JSONContent) => { - setContent(newContent); + const changeContent = (newContent: JSONContent) => { + setForm((prev) => ({ ...prev, content: newContent })); }; - const handleLinkUrlChange = (url: string) => { - setLinkUrl(url); - + const changeLinkUrl = (url: string) => { if (!url.trim()) { - setLinkMetadata(null); + setForm((prev) => ({ + ...prev, + linkUrl: null, + linkMetadata: null, + })); setIsEmbedOpen(false); return; } + setForm((prev) => ({ ...prev, linkUrl: url })); + try { new URL(url); getLinkMetadataMutation(url, { onSuccess: (data) => { - setLinkMetadata(data); + setForm((prev) => ({ ...prev, linkMetadata: data })); }, onError: (error) => { console.error("링크 메타데이터 가져오기 실패:", error); toast.error("링크 정보를 가져올 수 없습니다."); }, }); - } catch (error) {} - }; - - const handleToggleEmbed = () => { - setIsEmbedOpen(!isEmbedOpen); - }; - - const handleDeleteLinkPreview = () => { - setLinkUrl(""); - setLinkMetadata(null); - setIsEmbedOpen(false); - }; - - const handleDraft = () => { - if (!title.trim() && !content) return; - - draftNoteStorage.save(todoId, { - title, - content: content || { type: "doc", content: [] }, - linkUrl, - linkMetadata, - }); - - setHasDraftNote(true); - toast.success("임시 저장이 완료되었습니다", { hasTime: true }); - }; - - const handleLoadModalOpen = () => { - setIsLoadModalOpen(true); + } catch (error) { + console.error("유효하지 않은 URL:", error); + } }; - const handleLoadModalClose = () => { - setIsLoadModalOpen(false); + const embed = { + isOpen: isEmbedOpen, + toggle: () => setIsEmbedOpen(!isEmbedOpen), + deletePreview: () => { + setForm((prev) => ({ + ...prev, + linkUrl: null, + linkMetadata: null, + })); + setIsEmbedOpen(false); + }, }; - const handleDraftCalloutClose = () => { - setHasDraftNote(false); + const loadModal = { + isOpen: isLoadModalOpen, + open: () => setIsLoadModalOpen(true), + close: () => setIsLoadModalOpen(false), }; - const handleConfirmLoadDraft = () => { - const draftNote = draftNoteStorage.get(todoId); + const draft = { + hasNote: hasDraftNote, + save: () => { + if (!form.title.trim() && !form.content) return; - if (draftNote) { - setTitle(draftNote.title); - setContent(draftNote.content); - setLinkUrl(draftNote.linkUrl); - setLinkMetadata(draftNote.linkMetadata ?? null); - } + draftNoteStorage.save(todoId, { + title: form.title, + content: form.content || { type: "doc", content: [] }, + linkUrl: form.linkUrl, + linkMetadata: form.linkMetadata, + }); - draftNoteStorage.remove(todoId); - setIsLoadModalOpen(false); - setHasDraftNote(false); - }; + setHasDraftNote(true); + toast.success("임시 저장이 완료되었습니다", { hasTime: true }); + }, + load: () => { + const draftNote = draftNoteStorage.get(todoId); + + if (!draftNote) { + loadModal.close(); + return; + } + + setForm({ + title: draftNote.title, + content: draftNote.content, + linkUrl: draftNote.linkUrl, + linkMetadata: draftNote.linkMetadata ?? null, + }); - const getDraftTitle = () => { - const draftNote = draftNoteStorage.get(todoId); - return draftNote?.title.trim() || "제목 없음"; + draftNoteStorage.remove(todoId); + loadModal.close(); + setHasDraftNote(false); + }, + getTitle: () => { + const draftNote = draftNoteStorage.get(todoId); + return draftNote?.title.trim() || "제목 없음"; + }, + closeCallout: () => setHasDraftNote(false), }; return { - title, - content, - linkUrl, - linkMetadata, - isEmbedOpen, - hasDraftNote, - isLoadModalOpen, - handleTitleChange, - handleContentChange, - handleLinkUrlChange, - handleToggleEmbed, - handleDeleteLinkPreview, - handleDraft, - handleLoadModalOpen, - handleLoadModalClose, - handleDraftCalloutClose, - handleConfirmLoadDraft, - getDraftTitle, + form, + embed, + draft, + loadModal, + changeTitle, + changeContent, + changeLinkUrl, }; } diff --git a/src/app/(protected)/notes/_utils/draft-note.ts b/src/app/(protected)/notes/_utils/draft-note.ts index 32f1216..cf0d35a 100644 --- a/src/app/(protected)/notes/_utils/draft-note.ts +++ b/src/app/(protected)/notes/_utils/draft-note.ts @@ -5,7 +5,7 @@ export interface DraftNote { todoId: number; title: string; content: JSONContent; - linkUrl: string; + linkUrl: string | null; savedAt: string; linkMetadata?: LinkMetadata | null; } From 495844dc4790654a94e9cd54b741e5dba462237b Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Thu, 1 Jan 2026 02:32:27 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=92=84=20Style=20:=20HeroSection?= =?UTF-8?q?=EC=97=90=EC=84=9C=20overflow-hidden=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(landing)/_components/HeroSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(landing)/_components/HeroSection.tsx b/src/app/(landing)/_components/HeroSection.tsx index dd7ad25..d28f4c1 100644 --- a/src/app/(landing)/_components/HeroSection.tsx +++ b/src/app/(landing)/_components/HeroSection.tsx @@ -17,7 +17,7 @@ export default function HeroSection() { }, []); return ( -
+