ㅇㅇ
This commit is contained in:
@@ -1,17 +1,18 @@
|
|||||||
배너 디테일
|
|
||||||
카드 디테일
|
|
||||||
메인 디테일 프리뷰, 글, 스페셜_랭크
|
|
||||||
|
|
||||||
기본 리스트 , 글이 없습니다.
|
|
||||||
글쓰기
|
|
||||||
글뷰, 댓글 +리스트
|
|
||||||
|
|
||||||
|
메인 게시판 일반
|
||||||
|
메인 게시판 프리뷰
|
||||||
|
메인 게시판 스페셜랭크
|
||||||
|
|
||||||
|
기본 리스트
|
||||||
스페셜_랭크
|
스페셜_랭크
|
||||||
스페셜_출석
|
스페셜_출석
|
||||||
스페셜_제휴업체
|
스페셜_제휴업체
|
||||||
스페셜_제휴업체지도
|
스페셜_제휴업체지도
|
||||||
|
|
||||||
|
게시글 뷰 + 댓글
|
||||||
|
|
||||||
로그인관련
|
로그인관련
|
||||||
|
회원가입 페이지
|
||||||
회원쪽지
|
회원쪽지
|
||||||
링크로들어오면 보이고 거기서 페이지이동하면안보이게
|
링크로들어오면 보이고 거기서 페이지 이동하면 안보이게
|
||||||
@@ -199,9 +199,10 @@ async function upsertBoards(admin, categoryMap) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const created = [];
|
const created = [];
|
||||||
// 특수 랭킹 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨)
|
// 특수 랭킹/텍스트 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨)
|
||||||
const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } });
|
const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } });
|
||||||
const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } });
|
const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } });
|
||||||
|
const mainText = await prisma.boardViewType.findUnique({ where: { key: "main_text" } });
|
||||||
|
|
||||||
for (const b of boards) {
|
for (const b of boards) {
|
||||||
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
||||||
@@ -233,7 +234,10 @@ async function upsertBoards(admin, categoryMap) {
|
|||||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||||
readLevel: b.readLevel || undefined,
|
readLevel: b.readLevel || undefined,
|
||||||
categoryId: category ? category.id : undefined,
|
categoryId: category ? category.id : undefined,
|
||||||
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
// 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지
|
||||||
|
...(b.slug === "ranking"
|
||||||
|
? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {})
|
||||||
|
: (mainText ? { mainPageViewTypeId: mainText.id } : {})),
|
||||||
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
@@ -244,7 +248,10 @@ async function upsertBoards(admin, categoryMap) {
|
|||||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||||
readLevel: b.readLevel || undefined,
|
readLevel: b.readLevel || undefined,
|
||||||
categoryId: category ? category.id : undefined,
|
categoryId: category ? category.id : undefined,
|
||||||
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
// 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지
|
||||||
|
...(b.slug === "ranking"
|
||||||
|
? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {})
|
||||||
|
: (mainText ? { mainPageViewTypeId: mainText.id } : {})),
|
||||||
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -382,6 +389,20 @@ async function seedBanners() {
|
|||||||
await prisma.banner.createMany({ data: items });
|
await prisma.banner.createMany({ data: items });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function seedMainpageVisibleBoards(boards) {
|
||||||
|
const SETTINGS_KEY = "mainpage_settings";
|
||||||
|
const setting = await prisma.setting.findUnique({ where: { key: SETTINGS_KEY } });
|
||||||
|
const current = setting ? JSON.parse(setting.value) : {};
|
||||||
|
const wantSlugs = new Set(["notice", "free", "ranking"]);
|
||||||
|
const visibleBoardIds = boards.filter((b) => wantSlugs.has(b.slug)).map((b) => b.id);
|
||||||
|
const next = { ...current, visibleBoardIds };
|
||||||
|
await prisma.setting.upsert({
|
||||||
|
where: { key: SETTINGS_KEY },
|
||||||
|
update: { value: JSON.stringify(next) },
|
||||||
|
create: { key: SETTINGS_KEY, value: JSON.stringify(next) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await upsertRoles();
|
await upsertRoles();
|
||||||
const admin = await upsertAdmin();
|
const admin = await upsertAdmin();
|
||||||
@@ -390,6 +411,7 @@ async function main() {
|
|||||||
await createRandomUsers(100);
|
await createRandomUsers(100);
|
||||||
await removeNonPrimaryBoards();
|
await removeNonPrimaryBoards();
|
||||||
const boards = await upsertBoards(admin, categoryMap);
|
const boards = await upsertBoards(admin, categoryMap);
|
||||||
|
await seedMainpageVisibleBoards(boards);
|
||||||
await createPostsForAllBoards(boards, 100, admin);
|
await createPostsForAllBoards(boards, 100, admin);
|
||||||
await seedPartnerShops();
|
await seedPartnerShops();
|
||||||
await seedBanners();
|
await seedBanners();
|
||||||
|
|||||||
152
src/app/page.tsx
152
src/app/page.tsx
@@ -18,28 +18,146 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
|||||||
const showPartnerShops: boolean = parsed.showPartnerShops ?? true;
|
const showPartnerShops: boolean = parsed.showPartnerShops ?? true;
|
||||||
const visibleBoardIds: string[] = Array.isArray(parsed.visibleBoardIds) ? parsed.visibleBoardIds : [];
|
const visibleBoardIds: string[] = Array.isArray(parsed.visibleBoardIds) ? parsed.visibleBoardIds : [];
|
||||||
|
|
||||||
// 보드 메타데이터 (이름 표시용)
|
// 보드 메타데이터 (메인뷰 타입 포함)
|
||||||
const boardsMeta = visibleBoardIds.length
|
const boardsMeta = visibleBoardIds.length
|
||||||
? await prisma.board.findMany({ where: { id: { in: visibleBoardIds } }, select: { id: true, name: true } })
|
? await prisma.board.findMany({
|
||||||
|
where: { id: { in: visibleBoardIds } },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
category: { select: { id: true, name: true } },
|
||||||
|
mainPageViewType: { select: { key: true } },
|
||||||
|
},
|
||||||
|
})
|
||||||
: [];
|
: [];
|
||||||
const idToMeta = new Map(boardsMeta.map((b) => [b.id, b] as const));
|
const idToMeta = new Map(
|
||||||
|
boardsMeta.map((b) => [
|
||||||
|
b.id,
|
||||||
|
{
|
||||||
|
id: b.id,
|
||||||
|
name: b.name,
|
||||||
|
slug: b.slug,
|
||||||
|
categoryId: b.category?.id ?? null,
|
||||||
|
categoryName: b.category?.name ?? "",
|
||||||
|
mainTypeKey: b.mainPageViewType?.key,
|
||||||
|
},
|
||||||
|
] as const)
|
||||||
|
);
|
||||||
const orderedBoards = visibleBoardIds
|
const orderedBoards = visibleBoardIds
|
||||||
.map((id) => idToMeta.get(id))
|
.map((id) => idToMeta.get(id))
|
||||||
.filter((v): v is { id: string; name: string } => Boolean(v));
|
.filter((v): v is any => Boolean(v));
|
||||||
const firstTwo = orderedBoards.slice(0, 2);
|
const firstTwo = orderedBoards.slice(0, 2);
|
||||||
const restBoards = orderedBoards.slice(2);
|
const restBoards = orderedBoards.slice(2);
|
||||||
|
|
||||||
const renderBoardPanel = (board: { id: string; name: string }) => (
|
function formatDateYmd(d: Date) {
|
||||||
<div key={board.id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col bg-white">
|
const yyyy = d.getFullYear();
|
||||||
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||||
|
const dd = String(d.getDate()).padStart(2, "0");
|
||||||
|
return `${yyyy}.${mm}.${dd}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderBoardPanel = async (board: { id: string; name: string; slug: string; categoryId: string | null; categoryName: string; mainTypeKey?: string }) => {
|
||||||
|
// 같은 카테고리의 소분류(게시판) 탭 목록
|
||||||
|
const siblingBoards = board.categoryId
|
||||||
|
? await prisma.board.findMany({
|
||||||
|
where: { categoryId: board.categoryId },
|
||||||
|
select: { id: true, name: true, slug: true },
|
||||||
|
orderBy: [{ sortOrder: "asc" }, { name: "asc" }],
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const isTextMain = board.mainTypeKey === "main_text";
|
||||||
|
if (!isTextMain) {
|
||||||
|
return (
|
||||||
|
<div key={board.id} className="h-full min-h-0 flex flex-col">
|
||||||
|
<div className="content-stretch flex gap-[30px] items-start w-full mb-2">
|
||||||
|
<div className="flex items-center gap-[8px]">
|
||||||
|
<div className="text-[30px] text-[#5c5c5c] leading-[30px]">{board.categoryName || board.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-[8px]">
|
||||||
|
{siblingBoards.map((sb) => (
|
||||||
|
<Link
|
||||||
|
key={sb.id}
|
||||||
|
href={`/boards/${sb.slug}`}
|
||||||
|
className={`px-[16px] py-[8px] rounded-[14px] text-[14px] ${
|
||||||
|
sb.id === board.id ? "bg-[#5c5c5c] text-white border border-[#5c5c5c]" : "bg-white text-[#5c5c5c] border border-[#d5d5d5]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{sb.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col bg-white">
|
||||||
<div className="px-3 py-2 border-b border-neutral-200 flex items-center justify-between">
|
<div className="px-3 py-2 border-b border-neutral-200 flex items-center justify-between">
|
||||||
<Link href={`/boards/${board.id}`} className="text-lg md:text-xl font-bold text-neutral-800 truncate">{board.name}</Link>
|
<Link href={`/boards/${board.slug}`} className="text-lg md:text-xl font-bold text-neutral-800 truncate">{board.name}</Link>
|
||||||
<Link href={`/boards/${board.id}`} className="text-xs px-3 py-1 rounded-full border border-neutral-300 text-neutral-700 hover:bg-neutral-100">더보기</Link>
|
<Link href={`/boards/${board.slug}`} className="text-xs px-3 py-1 rounded-full border border-neutral-300 text-neutral-700 hover:bg-neutral-100">더보기</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-h-0 overflow-hidden p-0">
|
<div className="flex-1 min-h-0 overflow-hidden p-0">
|
||||||
<PostList boardId={board.id} sort={sort} />
|
<PostList boardId={board.id} sort={sort} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const posts = await prisma.post.findMany({
|
||||||
|
where: { boardId: board.id, status: "published" },
|
||||||
|
select: { id: true, title: true, createdAt: true, stat: { select: { recommendCount: true } } },
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
take: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={board.id} className="h-full min-h-0 flex flex-col">
|
||||||
|
<div className="content-stretch flex gap-[30px] items-start w-full mb-2">
|
||||||
|
<div className="flex items-center gap-[8px]">
|
||||||
|
<div className="text-[30px] text-[#5c5c5c] leading-[30px]">{board.categoryName || board.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-[8px]">
|
||||||
|
{siblingBoards.map((sb) => (
|
||||||
|
<Link
|
||||||
|
key={sb.id}
|
||||||
|
href={`/boards/${sb.slug}`}
|
||||||
|
className={`px-[16px] py-[8px] rounded-[14px] text-[14px] ${
|
||||||
|
sb.id === board.id ? "bg-[#5c5c5c] text-white border border-[#5c5c5c]" : "bg-white text-[#5c5c5c] border border-[#d5d5d5]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{sb.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col bg-white">
|
||||||
|
<div className="px-3 py-2 border-b border-neutral-200 flex items-center justify-between">
|
||||||
|
<Link href={`/boards/${board.slug}`} className="text-lg md:text-xl font-bold text-neutral-800 truncate">{board.name}</Link>
|
||||||
|
<Link href={`/boards/${board.slug}`} className="text-xs px-3 py-1 rounded-full border border-neutral-300 text-neutral-700 hover:bg-neutral-100">더보기</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-h-0 overflow-hidden p-0">
|
||||||
|
<div className="bg-white px-[24px] pt-[8px] pb-[16px]">
|
||||||
|
<ul className="min-h-[326px]">
|
||||||
|
{posts.map((p) => (
|
||||||
|
<li key={p.id} className="border-b border-[#ededed] h-[56px] pl-0 pr-[24px] pt-[16px] pb-[16px]">
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center gap-[4px] h-[24px] overflow-hidden">
|
||||||
|
<div className="relative w-[16px] h-[16px] shrink-0">
|
||||||
|
<div className="absolute inset-0 rounded-full bg-[#f45f00] opacity-0" />
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center text-[10px] text-white font-extrabold select-none">n</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-[16px] leading-[22px] text-[#5c5c5c] truncate max-w-[calc(100vw-280px)]">{p.title}</span>
|
||||||
|
<span className="text-[16px] text-[#f45f00] font-bold">+{p.stat?.recommendCount ?? 0}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[12px] leading-[12px] tracking-[-0.24px] text-[#8c8c8c]">{formatDateYmd(new Date(p.createdAt))}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -77,7 +195,7 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
|||||||
|
|
||||||
{/* 1행: 프로필 + 선택된 보드 2개 (최대 2개) */}
|
{/* 1행: 프로필 + 선택된 보드 2개 (최대 2개) */}
|
||||||
{(firstTwo.length > 0) && (
|
{(firstTwo.length > 0) && (
|
||||||
<section className="min-h-[514px] overflow-hidden">
|
<section className="min-h-[410px] overflow-hidden">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:[grid-template-columns:1fr_2fr_2fr] gap-4 h-full min-h-0">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:[grid-template-columns:1fr_2fr_2fr] gap-4 h-full min-h-0">
|
||||||
<div className="hidden xl:grid relative overflow-hidden rounded-xl bg-white px-[25px] py-[34px] grid-rows-[120px_120px_1fr] gap-y-[32px] h-full w-full md:min-w-[350px]">
|
<div className="hidden xl:grid relative overflow-hidden rounded-xl bg-white px-[25px] py-[34px] grid-rows-[120px_120px_1fr] gap-y-[32px] h-full w-full md:min-w-[350px]">
|
||||||
<div className="absolute inset-x-0 top-0 h-[56px] bg-[#d5d5d5] z-0" />
|
<div className="absolute inset-x-0 top-0 h-[56px] bg-[#d5d5d5] z-0" />
|
||||||
@@ -144,9 +262,9 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{firstTwo.map((b) => (
|
{(await Promise.all(firstTwo.map((b) => renderBoardPanel(b)))).map((panel, idx) => (
|
||||||
<div key={b.id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col">
|
<div key={firstTwo[idx].id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col">
|
||||||
{renderBoardPanel(b)}
|
{panel}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -156,14 +274,14 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
|||||||
{/* 나머지 보드: 2개씩 다음 열로 렌더링 */}
|
{/* 나머지 보드: 2개씩 다음 열로 렌더링 */}
|
||||||
{restBoards.length > 0 && (
|
{restBoards.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{Array.from({ length: Math.ceil(restBoards.length / 2) }).map((_, i) => {
|
{Array.from({ length: Math.ceil(restBoards.length / 2) }).map(async (_, i) => {
|
||||||
const pair = restBoards.slice(i * 2, i * 2 + 2);
|
const pair = restBoards.slice(i * 2, i * 2 + 2);
|
||||||
return (
|
return (
|
||||||
<section key={`rest-${i}`} className="min-h-[514px] md:h-[620px] overflow-hidden">
|
<section key={`rest-${i}`} className="min-h-[410px] md:h-[510px] overflow-hidden">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-full min-h-0">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-full min-h-0">
|
||||||
{pair.map((b) => (
|
{(await Promise.all(pair.map((b) => renderBoardPanel(b)))).map((panel, idx) => (
|
||||||
<div key={b.id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col">
|
<div key={pair[idx].id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col">
|
||||||
{renderBoardPanel(b)}
|
{panel}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user