fix: Next15 호환 업데이트 및 인증/게시판 기능 개선\n\n- 헤더를 클라이언트 컴포넌트로 전환, 세션 표시/로그아웃 추가\n- /api/auth/session GET 추가, 로그인/회원가입 페이지 연결\n- 서버 컴포넌트에서 params/searchParams 안전 언랩 적용\n- 서버 fetch 절대 URL 구성(헤더 기반)으로 500/URL 오류 해결\n- 새 글 페이지 useSearchParams로 전환 및 폼 검증/에러 표시 추가\n- 회원가입 폼 fieldErrors 표시 및 a11y 속성 보완\n- Partner.name @unique 추가 및 시드 정상화
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<string, string[]>;
|
||||
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
|
||||
<UploadButton
|
||||
multiple
|
||||
onUploaded={(url) => setForm((f) => ({ ...f, content: `${f.content}\n` }))}
|
||||
{...(searchParams?.boardSlug ? require("@/lib/photoPresets").getPhotoPresetBySlug(searchParams.boardSlug) : {})}
|
||||
{...(boardSlug ? require("@/lib/photoPresets").getPhotoPresetBySlug(boardSlug) : {})}
|
||||
/>
|
||||
<button disabled={loading} onClick={submit}>{loading ? "저장 중..." : "등록"}</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user