메인페이지
This commit is contained in:
261
src/app/page.tsx
261
src/app/page.tsx
@@ -7,6 +7,9 @@ import SearchIcon from "@/app/svgs/SearchIcon";
|
||||
import { RankIcon1st } from "@/app/components/RankIcon1st";
|
||||
import { RankIcon2nd } from "@/app/components/RankIcon2nd";
|
||||
import { RankIcon3rd } from "@/app/components/RankIcon3rd";
|
||||
import { UserAvatar } from "@/app/components/UserAvatar";
|
||||
import { ImagePlaceholderIcon } from "@/app/components/ImagePlaceholderIcon";
|
||||
import { BoardPanelClient } from "@/app/components/BoardPanelClient";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function Home({ searchParams }: { searchParams: Promise<{ sort?: "recent" | "popular" } | undefined> }) {
|
||||
@@ -60,191 +63,85 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
||||
return `${yyyy}.${mm}.${dd}`;
|
||||
}
|
||||
|
||||
const renderBoardPanel = async (board: { id: string; name: string; slug: string; categoryId: string | null; categoryName: string; mainTypeKey?: string }) => {
|
||||
// 게시판 패널 데이터 수집 함수 (모든 sibling boards 포함)
|
||||
const prepareBoardPanelData = 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 },
|
||||
select: { id: true, name: true, slug: true, mainPageViewType: { select: { key: true } } },
|
||||
orderBy: [{ sortOrder: "asc" }, { name: "asc" }],
|
||||
})
|
||||
: [];
|
||||
const isTextMain = board.mainTypeKey === "main_text";
|
||||
const isSpecialRank = board.mainTypeKey === "main_special_rank";
|
||||
|
||||
const boardsData = [];
|
||||
|
||||
// 특수 랭킹 타입 처리
|
||||
if (isSpecialRank) {
|
||||
const topUsers = await prisma.user.findMany({
|
||||
select: { userId: true, nickname: true, points: true, profileImage: true },
|
||||
where: { status: "active" },
|
||||
orderBy: { points: "desc" },
|
||||
take: 3,
|
||||
for (const sb of siblingBoards) {
|
||||
const isTextMain = sb.mainPageViewType?.key === "main_text";
|
||||
const isSpecialRank = sb.mainPageViewType?.key === "main_special_rank";
|
||||
const isPreview = sb.mainPageViewType?.key === "main_preview";
|
||||
|
||||
const boardMeta = {
|
||||
id: sb.id,
|
||||
name: sb.name,
|
||||
slug: sb.slug,
|
||||
mainTypeKey: sb.mainPageViewType?.key,
|
||||
};
|
||||
|
||||
let specialRankUsers;
|
||||
let previewPosts;
|
||||
let textPosts;
|
||||
|
||||
if (isSpecialRank) {
|
||||
specialRankUsers = await prisma.user.findMany({
|
||||
select: { userId: true, nickname: true, points: true, profileImage: true },
|
||||
where: { status: "active" },
|
||||
orderBy: { points: "desc" },
|
||||
take: 3,
|
||||
});
|
||||
} else if (isPreview) {
|
||||
previewPosts = await prisma.post.findMany({
|
||||
where: { boardId: sb.id, status: "published" },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
createdAt: true,
|
||||
attachments: {
|
||||
where: { type: "image" },
|
||||
orderBy: { sortOrder: "asc" },
|
||||
take: 1,
|
||||
select: { url: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 3,
|
||||
});
|
||||
} else if (isTextMain) {
|
||||
textPosts = await prisma.post.findMany({
|
||||
where: { boardId: sb.id, status: "published" },
|
||||
select: { id: true, title: true, createdAt: true, stat: { select: { recommendCount: true } } },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 7,
|
||||
});
|
||||
}
|
||||
// 기본 타입은 PostList가 자체적으로 API를 호출하므로 데이터 미리 가져오지 않음
|
||||
|
||||
boardsData.push({
|
||||
board: boardMeta,
|
||||
categoryName: board.categoryName || board.name,
|
||||
siblingBoards: siblingBoards.map(sb => ({
|
||||
id: sb.id,
|
||||
name: sb.name,
|
||||
slug: sb.slug,
|
||||
mainTypeKey: sb.mainPageViewType?.key,
|
||||
})),
|
||||
specialRankUsers,
|
||||
previewPosts,
|
||||
textPosts,
|
||||
});
|
||||
|
||||
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">
|
||||
<div className="flex-1 min-h-0 overflow-hidden p-0">
|
||||
<div className="px-[24px] pt-[8px] pb-[16px]">
|
||||
<div className="flex flex-col gap-[16px]">
|
||||
{topUsers.map((user, idx) => {
|
||||
const rank = idx + 1;
|
||||
return (
|
||||
<div key={user.userId} className="flex h-[150px] items-center rounded-[16px] overflow-hidden bg-white">
|
||||
<div className="h-[150px] w-[160px] relative shrink-0 bg-[#d5d5d5] overflow-hidden">
|
||||
<img
|
||||
src={user.profileImage || "https://picsum.photos/seed/profile/200/200"}
|
||||
alt={user.nickname || "프로필"}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute left-[calc(50%+49.25px)] top-0 translate-x-[-50%] w-[31.25px] h-[31.25px] flex items-center justify-center pointer-events-none">
|
||||
<div className="w-[62.5px] h-[62.5px] flex items-center justify-center">
|
||||
<img src="https://picsum.photos/seed/badge/200/200" alt="뱃지" className="w-[51px] h-[53px] object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center gap-[10px] px-[30px] py-[24px] min-w-0">
|
||||
<div className="flex flex-col gap-[12px] min-w-0 flex-1">
|
||||
<div className="flex items-center gap-[12px]">
|
||||
<div className="relative w-[20px] h-[20px] shrink-0">
|
||||
{rank === 1 && <RankIcon1st />}
|
||||
{rank === 2 && <RankIcon2nd />}
|
||||
{rank === 3 && <RankIcon3rd />}
|
||||
</div>
|
||||
<div className="bg-white border border-[#d5d5d5] px-[16px] py-[5px] rounded-[12px] text-[14px] text-[#5c5c5c] shrink-0">
|
||||
{rank}위
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[24px] font-medium text-[#5c5c5c] truncate leading-[22px]">
|
||||
{user.nickname || "익명"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[24px] flex items-center gap-[4px] shrink-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" className="shrink-0">
|
||||
<path d="M8.17377 0.0481988C8.1834 0.0326271 8.19596 0.0201252 8.21037 0.0117499C8.22478 0.00337451 8.24062 -0.000628204 8.25656 7.99922e-05C8.2725 0.000788189 8.28806 0.0061865 8.30194 0.0158187C8.31582 0.0254509 8.3276 0.0390338 8.33629 0.0554196L11.3323 5.55973C11.3575 5.60512 11.4038 5.62472 11.4468 5.60718L15.8683 3.80198C15.9441 3.77104 16.0174 3.85666 15.9963 3.95053L14.2584 16H1.74226L0.00344328 3.95156C-0.00125237 3.93019 -0.0011435 3.90765 0.00375838 3.88635C0.00866025 3.86505 0.0181727 3.84576 0.0312889 3.83054C0.0444051 3.81532 0.0606369 3.80472 0.0782661 3.79988C0.0958953 3.79503 0.114266 3.79612 0.131434 3.80302L4.55889 5.61131C4.59846 5.62678 4.64309 5.61131 4.66835 5.57005L8.17461 0.0481988H8.17377Z" fill="#FFB625"/>
|
||||
</svg>
|
||||
<span className="text-[20px] font-semibold text-[#5c5c5c] leading-[22px]">{user.points.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
<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">
|
||||
<PostList boardId={board.id} sort={sort} />
|
||||
</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: 7,
|
||||
});
|
||||
|
||||
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 { initialBoardId: board.id, boardsData };
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -289,7 +186,13 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
||||
<div className="absolute inset-x-0 top-0 h-[56px] bg-[#d5d5d5] z-0" />
|
||||
<div className="h-[120px] flex items-center justify-center relative z-10">
|
||||
<div className="flex items-center justify-center gap-[8px]">
|
||||
<img src="https://picsum.photos/seed/profile/200/200" alt="프로필" className="w-[120px] h-[120px] rounded-full object-cover" />
|
||||
<UserAvatar
|
||||
src={null}
|
||||
alt="프로필"
|
||||
width={120}
|
||||
height={120}
|
||||
className="rounded-full"
|
||||
/>
|
||||
<div className="w-[62px] h-[62px] rounded-full bg-neutral-200 flex items-center justify-center text-[11px] text-neutral-700">
|
||||
Lv
|
||||
</div>
|
||||
@@ -350,9 +253,9 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{(await Promise.all(firstTwo.map((b) => renderBoardPanel(b)))).map((panel, idx) => (
|
||||
{(await Promise.all(firstTwo.map((b) => prepareBoardPanelData(b)))).map((panelData, idx) => (
|
||||
<div key={firstTwo[idx].id} className="rounded-xl overflow-hidden xl:h-[514px] h-full min-h-0 flex flex-col flex-1">
|
||||
{panel}
|
||||
<BoardPanelClient initialBoardId={panelData.initialBoardId} boardsData={panelData.boardsData} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -367,9 +270,9 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
|
||||
return (
|
||||
<section key={`rest-${i}`} className="min-h-[395px] md:h-[540px] overflow-hidden">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-full min-h-0">
|
||||
{(await Promise.all(pair.map((b) => renderBoardPanel(b)))).map((panel, idx) => (
|
||||
{(await Promise.all(pair.map((b) => prepareBoardPanelData(b)))).map((panelData, idx) => (
|
||||
<div key={pair[idx].id} className="rounded-xl overflow-hidden h-full min-h-0 flex flex-col">
|
||||
{panel}
|
||||
<BoardPanelClient initialBoardId={panelData.initialBoardId} boardsData={panelData.boardsData} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user