main
This commit is contained in:
@@ -368,7 +368,6 @@ export default function AdminBoardsPage() {
|
||||
<th className="px-3 py-2">쓰기</th>
|
||||
<th className="px-3 py-2">익명</th>
|
||||
<th className="px-3 py-2">비밀댓</th>
|
||||
<th className="px-3 py-2">승인</th>
|
||||
<th className="px-3 py-2">성인</th>
|
||||
<th className="px-3 py-2">대분류 이동</th>
|
||||
<th className="px-3 py-2">활성</th>
|
||||
@@ -501,7 +500,6 @@ function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, ma
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowAnonymousPost} onChange={(e) => { const v = { ...edit, allowAnonymousPost: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowSecretComment} onChange={(e) => { const v = { ...edit, allowSecretComment: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.requiresApproval} onChange={(e) => { const v = { ...edit, requiresApproval: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={!!edit.isAdultOnly} onChange={(e) => { const v = { ...edit, isAdultOnly: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
{allowMove && categories && onMove ? (
|
||||
<td className="px-3 py-2 text-center">
|
||||
|
||||
@@ -5,7 +5,7 @@ export async function PATCH(req: Request, context: { params: Promise<{ id: strin
|
||||
const { id } = await context.params;
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const data: any = {};
|
||||
for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "requiresApproval", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) {
|
||||
for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) {
|
||||
if (k in body) data[k] = body[k];
|
||||
}
|
||||
if ("requiredTags" in body) {
|
||||
|
||||
@@ -16,7 +16,6 @@ export async function GET() {
|
||||
writeLevel: true,
|
||||
allowAnonymousPost: true,
|
||||
allowSecretComment: true,
|
||||
requiresApproval: true,
|
||||
status: true,
|
||||
categoryId: true,
|
||||
mainPageViewTypeId: true,
|
||||
@@ -36,7 +35,6 @@ const createSchema = z.object({
|
||||
writeLevel: z.string().optional(),
|
||||
allowAnonymousPost: z.boolean().optional(),
|
||||
allowSecretComment: z.boolean().optional(),
|
||||
requiresApproval: z.boolean().optional(),
|
||||
status: z.string().optional(),
|
||||
isAdultOnly: z.boolean().optional(),
|
||||
categoryId: z.string().nullable().optional(),
|
||||
|
||||
@@ -20,7 +20,6 @@ export async function GET(req: Request) {
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
requiresApproval: true,
|
||||
allowAnonymousPost: true,
|
||||
isAdultOnly: true,
|
||||
category: { select: { id: true, name: true, slug: true } },
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function GET() {
|
||||
boards: {
|
||||
where: { status: "active" },
|
||||
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
|
||||
select: { id: true, name: true, slug: true, requiresApproval: true },
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ export async function POST(req: Request) {
|
||||
}
|
||||
const { boardId, authorId, title, content, isAnonymous } = parsed.data;
|
||||
const board = await prisma.board.findUnique({ where: { id: boardId } });
|
||||
const requiresApproval = board?.requiresApproval ?? false;
|
||||
// 사진형 보드 필수 이미지 검증: content 내 이미지 링크 최소 1개
|
||||
const isImageOnly = (board?.requiredFields as any)?.imageOnly;
|
||||
const minImages = (board?.requiredFields as any)?.minImages ?? 0;
|
||||
@@ -35,7 +34,7 @@ export async function POST(req: Request) {
|
||||
title,
|
||||
content,
|
||||
isAnonymous: !!isAnonymous,
|
||||
status: requiresApproval ? "hidden" : "published",
|
||||
status: "published",
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ post }, { status: 201 });
|
||||
|
||||
@@ -7,7 +7,7 @@ import { headers } from "next/headers";
|
||||
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;
|
||||
const id = p.id as string;
|
||||
const idOrSlug = p.id as string;
|
||||
const sort = (sp?.sort as "recent" | "popular" | undefined) ?? "recent";
|
||||
// 보드 slug 조회 (새 글 페이지 프리셋 전달)
|
||||
const h = await headers();
|
||||
@@ -16,7 +16,8 @@ export default async function BoardDetail({ params, searchParams }: { params: an
|
||||
const base = process.env.NEXT_PUBLIC_BASE_URL || `${proto}://${host}`;
|
||||
const res = await fetch(new URL("/api/boards", base).toString(), { cache: "no-store" });
|
||||
const { boards } = await res.json();
|
||||
const board = (boards || []).find((b: any) => b.id === id);
|
||||
const board = (boards || []).find((b: any) => b.slug === idOrSlug || b.id === idOrSlug);
|
||||
const id = board?.id as string;
|
||||
const siblingBoards = (boards || []).filter((b: any) => b.category?.id && b.category.id === board?.category?.id);
|
||||
const categoryName = board?.category?.name ?? "";
|
||||
return (
|
||||
@@ -24,14 +25,14 @@ export default async function BoardDetail({ params, searchParams }: { params: an
|
||||
{/* 상단 배너 (서브카테고리 표시) */}
|
||||
<section>
|
||||
<HeroBanner
|
||||
subItems={siblingBoards.map((b: any) => ({ id: b.id, name: b.name, href: `/boards/${b.id}` }))}
|
||||
subItems={siblingBoards.map((b: any) => ({ id: b.id, name: b.name, href: `/boards/${b.slug}` }))}
|
||||
activeSubId={id}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* 검색/필터 툴바 + 리스트 */}
|
||||
<section>
|
||||
<BoardToolbar boardId={id} />
|
||||
<BoardToolbar boardId={board?.slug} />
|
||||
<div className="p-0">
|
||||
<PostList
|
||||
boardId={id}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default async function BoardsPage() {
|
||||
<h1>게시판</h1>
|
||||
<ul style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
{boards?.map((b: any) => (
|
||||
<li key={b.id}><Link href={`/boards/${b.id}`}>{b.name}</Link></li>
|
||||
<li key={b.id}><Link href={`/boards/${b.slug}`}>{b.name}</Link></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@ export function AppHeader() {
|
||||
}, [pathname]);
|
||||
const activeCategorySlug = React.useMemo(() => {
|
||||
if (activeBoardId) {
|
||||
const found = categories.find((c) => c.boards.some((b) => b.id === activeBoardId));
|
||||
const found = categories.find((c) => c.boards.some((b) => b.slug === activeBoardId));
|
||||
return found?.slug ?? null;
|
||||
}
|
||||
if (pathname === "/boards") {
|
||||
@@ -337,7 +337,7 @@ export function AppHeader() {
|
||||
style={idx === categories.length - 1 ? { minWidth: 120 } : undefined}
|
||||
>
|
||||
<Link
|
||||
href={cat.boards?.[0]?.id ? `/boards/${cat.boards[0].id}` : `/boards?category=${cat.slug}`}
|
||||
href={cat.boards?.[0]?.slug ? `/boards/${cat.boards[0].slug}` : `/boards?category=${cat.slug}`}
|
||||
className={`block w-full px-2 py-2 text-sm font-medium transition-colors duration-200 hover:text-neutral-900 whitespace-nowrap ${
|
||||
activeCategorySlug === cat.slug ? "text-neutral-900" : "text-neutral-700"
|
||||
}`}
|
||||
@@ -381,11 +381,11 @@ export function AppHeader() {
|
||||
{cat.boards.map((b) => (
|
||||
<Link
|
||||
key={b.id}
|
||||
href={`/boards/${b.id}`}
|
||||
href={`/boards/${b.slug}`}
|
||||
className={`rounded px-2 py-1 text-sm transition-colors duration-150 hover:bg-neutral-100 hover:text-neutral-900 text-center whitespace-nowrap ${
|
||||
activeBoardId === b.id ? "bg-neutral-100 text-neutral-900 font-medium" : "text-neutral-700"
|
||||
activeBoardId === b.slug ? "bg-neutral-100 text-neutral-900 font-medium" : "text-neutral-700"
|
||||
}`}
|
||||
aria-current={activeBoardId === b.id ? "page" : undefined}
|
||||
aria-current={activeBoardId === b.slug ? "page" : undefined}
|
||||
>
|
||||
{b.name}
|
||||
</Link>
|
||||
@@ -416,7 +416,7 @@ export function AppHeader() {
|
||||
<div className="mb-2 font-semibold text-neutral-800">{cat.name}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{cat.boards.map((b) => (
|
||||
<Link key={b.id} href={`/boards/${b.id}`} onClick={() => setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900">
|
||||
<Link key={b.id} href={`/boards/${b.slug}`} onClick={() => setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900">
|
||||
{b.name}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -7,7 +7,7 @@ type ApiCategory = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
boards: { id: string; name: string; slug: string; requiresApproval: boolean }[];
|
||||
boards: { id: string; name: string; slug: string }[];
|
||||
};
|
||||
|
||||
type PostItem = {
|
||||
@@ -101,7 +101,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro
|
||||
className="shrink-0 text-lg md:text-xl font-bold text-neutral-800 truncate"
|
||||
onClick={() => {
|
||||
const first = selectedCategory?.boards?.[0];
|
||||
if (first?.id) router.push(`/boards/${first.id}`);
|
||||
if (first?.slug) router.push(`/boards/${first.slug}`);
|
||||
}}
|
||||
title={(selectedCategory?.name ?? categoryName ?? "").toString()}
|
||||
>
|
||||
@@ -113,7 +113,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro
|
||||
className="shrink-0 w-6 h-6 rounded-full border border-neutral-300 text-neutral-500 hover:bg-neutral-50 flex items-center justify-center"
|
||||
onClick={() => {
|
||||
const first = selectedCategory?.boards?.[0];
|
||||
if (first?.id) router.push(`/boards/${first.id}`);
|
||||
if (first?.slug) router.push(`/boards/${first.slug}`);
|
||||
}}
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
|
||||
@@ -8,7 +8,7 @@ import { HeroBanner } from "@/app/components/HeroBanner";
|
||||
|
||||
export default function EditPostPage() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = params?.id as string;
|
||||
const id = params?.id as string; // slug 값
|
||||
const router = useRouter();
|
||||
const { show } = useToast();
|
||||
const [form, setForm] = useState<{ title: string; content: string } | null>(null);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { HeroBanner } from "@/app/components/HeroBanner";
|
||||
// 서버 전용 페이지: 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 id = p.id as string; // slug 값이 들어옴
|
||||
const h = await headers();
|
||||
const host = h.get("host") ?? "localhost:3000";
|
||||
const proto = h.get("x-forwarded-proto") ?? "http";
|
||||
|
||||
Reference in New Issue
Block a user