diff --git a/src/app/boards/[id]/page.tsx b/src/app/boards/[id]/page.tsx index e098736..2dd8501 100644 --- a/src/app/boards/[id]/page.tsx +++ b/src/app/boards/[id]/page.tsx @@ -1,6 +1,7 @@ import { PostList } from "@/app/components/PostList"; import { headers } from "next/headers"; +// Next 15: params/searchParams가 Promise가 될 수 있어 안전 언랩 처리합니다. export default async function BoardDetail({ params, searchParams }: { params: any; searchParams: any }) { const p = params?.then ? await params : params; const sp = searchParams?.then ? await searchParams : searchParams; diff --git a/src/app/boards/page.tsx b/src/app/boards/page.tsx index 41edc89..b29b30a 100644 --- a/src/app/boards/page.tsx +++ b/src/app/boards/page.tsx @@ -1,6 +1,7 @@ import { headers } from "next/headers"; export default async function BoardsPage() { + // 서버에서 상대경로 fetch가 실패하므로 요청 헤더로 절대 URL을 구성합니다. const h = await headers(); const host = h.get("host") ?? "localhost:3000"; const proto = h.get("x-forwarded-proto") ?? "http"; diff --git a/src/app/components/AppHeader.tsx b/src/app/components/AppHeader.tsx index 1dc42c8..99c686f 100644 --- a/src/app/components/AppHeader.tsx +++ b/src/app/components/AppHeader.tsx @@ -1,4 +1,5 @@ "use client"; +// 클라이언트 훅(useState/useEffect)을 사용하여 세션 표시/로그아웃을 처리합니다. import { ThemeToggle } from "@/app/components/ThemeToggle"; import { SearchBar } from "@/app/components/SearchBar"; import { Button } from "@/app/components/ui/Button"; @@ -6,6 +7,7 @@ import React from "react"; export function AppHeader() { const [user, setUser] = React.useState<{ nickname: string } | null>(null); + // 헤더 마운트 시 세션 존재 여부를 조회해 로그인/로그아웃 UI를 제어합니다. React.useEffect(() => { fetch("/api/auth/session") .then((r) => r.json()) diff --git a/src/app/posts/[id]/page.tsx b/src/app/posts/[id]/page.tsx index a850382..7bf78cf 100644 --- a/src/app/posts/[id]/page.tsx +++ b/src/app/posts/[id]/page.tsx @@ -1,9 +1,10 @@ import { notFound } from "next/navigation"; import { headers } from "next/headers"; -import React, { use } from "react"; -export default async function PostDetail({ params }: { params: Promise<{ id: string }> }) { - const { id } = use(params); +// 서버 전용 페이지: params가 Promise일 수 있어 안전 언랩 후 절대 URL로 fetch합니다. +export default async function PostDetail({ params }: { params: any }) { + const p = params?.then ? await params : params; + const id = p.id as string; const h = await headers(); const host = h.get("host") ?? "localhost:3000"; const proto = h.get("x-forwarded-proto") ?? "http"; diff --git a/src/app/posts/new/page.tsx b/src/app/posts/new/page.tsx index 89bd7ea..e6faf0d 100644 --- a/src/app/posts/new/page.tsx +++ b/src/app/posts/new/page.tsx @@ -1,17 +1,33 @@ "use client"; +// 클라이언트 라우터/검색파라미터 훅으로 새 글 작성 폼을 제어합니다. import { useState } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useToast } from "@/app/components/ui/ToastProvider"; import { UploadButton } from "@/app/components/UploadButton"; import { Editor } from "@/app/components/Editor"; -export default function NewPostPage({ searchParams }: { searchParams?: { boardId?: string; boardSlug?: string } }) { +export default function NewPostPage() { const router = useRouter(); const { show } = useToast(); - const [form, setForm] = useState({ boardId: searchParams?.boardId ?? "", title: "", content: "" }); + const sp = useSearchParams(); + const initialBoardId = sp.get("boardId") ?? ""; + const boardSlug = sp.get("boardSlug") ?? undefined; + const [form, setForm] = useState({ boardId: initialBoardId, title: "", content: "" }); const [loading, setLoading] = useState(false); async function submit() { try { + if (!form.boardId.trim()) { + show("boardId가 비어 있습니다"); + return; + } + if (!form.title.trim()) { + show("제목을 입력하세요"); + return; + } + if (!form.content.trim()) { + show("내용을 입력하세요"); + return; + } setLoading(true); const r = await fetch("/api/posts", { method: "POST", @@ -19,11 +35,15 @@ export default function NewPostPage({ searchParams }: { searchParams?: { boardId body: JSON.stringify({ ...form }), }); const data = await r.json(); - if (!r.ok) throw new Error(JSON.stringify(data)); + if (!r.ok) { + const fe = (data?.error?.fieldErrors ?? {}) as Record; + const msg = data?.error?.message || Object.values(fe)[0]?.[0] || "작성 실패"; + throw new Error(msg); + } show("작성되었습니다"); router.push(`/posts/${data.post.id}`); } catch (e) { - show("작성 실패"); + show(e instanceof Error ? e.message : "작성 실패"); } finally { setLoading(false); } @@ -37,7 +57,7 @@ export default function NewPostPage({ searchParams }: { searchParams?: { boardId setForm((f) => ({ ...f, content: `${f.content}\n![image](${url})` }))} - {...(searchParams?.boardSlug ? require("@/lib/photoPresets").getPhotoPresetBySlug(searchParams.boardSlug) : {})} + {...(boardSlug ? require("@/lib/photoPresets").getPhotoPresetBySlug(boardSlug) : {})} />