123
This commit is contained in:
@@ -75,6 +75,7 @@ export default async function MyPage({ searchParams }: { searchParams: Promise<{
|
||||
const tabs = [
|
||||
{ key: "posts", label: "내가 쓴 게시글", count: postsCount },
|
||||
{ key: "comments", label: "내가 쓴 댓글", count: commentsCount },
|
||||
{ key: "points", label: "포인트 히스토리", count: await prisma.pointTransaction.count({ where: { userId: currentUser.userId } }) },
|
||||
{ key: "messages-received", label: "받은 쪽지함", count: receivedMessagesCount },
|
||||
{ key: "messages-sent", label: "보낸 쪽지함", count: sentMessagesCount },
|
||||
];
|
||||
@@ -86,26 +87,26 @@ export default async function MyPage({ searchParams }: { searchParams: Promise<{
|
||||
<HeroBanner />
|
||||
</section>
|
||||
|
||||
{/* 프로필 섹션 */}
|
||||
<section className="bg-white rounded-[24px] px-[108px] py-[23px]">
|
||||
<div className="flex gap-[64px] items-center justify-center">
|
||||
{/* 프로필 섹션 (모바일 대응) */}
|
||||
<section className="bg-white rounded-[16px] px-4 py-4 md:rounded-[24px] md:px-[108px] md:py-[23px]">
|
||||
<div className="flex flex-col md:flex-row gap-4 md:gap-[64px] items-center justify-center">
|
||||
{/* 좌측: 프로필 이미지, 닉네임, 레벨/등급/포인트 */}
|
||||
<div className="flex flex-col gap-[16px] items-center">
|
||||
<div className="flex flex-col gap-[16px] items-center">
|
||||
{/* 프로필 이미지 */}
|
||||
<div className="relative w-[161px] h-[162px]">
|
||||
<div className="relative w-[112px] h-[112px] md:w-[161px] md:h-[162px]">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="w-full h-full" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="80" cy="80" r="79" stroke="#8c8c8c" strokeWidth="2" fill="none" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="absolute inset-[10px] flex items-center justify-center">
|
||||
<div className="absolute inset-[8px] md:inset-[10px] flex items-center justify-center">
|
||||
<UserAvatar
|
||||
src={currentUser.profileImage || null}
|
||||
alt={currentUser.nickname || "프로필"}
|
||||
width={140}
|
||||
height={140}
|
||||
className="rounded-full"
|
||||
className="rounded-full w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,36 +121,36 @@ export default async function MyPage({ searchParams }: { searchParams: Promise<{
|
||||
</div>
|
||||
</div>
|
||||
{/* 레벨/등급/포인트 정보 - 가로 배치 */}
|
||||
<div className="flex gap-[16px] items-center justify-center">
|
||||
<div className="flex gap-3 md:gap-[16px] items-center justify-center">
|
||||
<div className="flex items-center gap-[4px] min-w-0">
|
||||
<ProfileLabelIcon width={14} height={14} className="shrink-0" />
|
||||
<span className="text-[11px] text-[#8c8c8c] font-[700] shrink-0">레벨</span>
|
||||
<span className="text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">Lv.{currentUser.level || 1}</span>
|
||||
<span className="text-[13px] md:text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">Lv.{currentUser.level || 1}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-[4px] min-w-0">
|
||||
<ProfileLabelIcon width={14} height={14} className="shrink-0" />
|
||||
<span className="text-[11px] text-[#8c8c8c] font-[700] shrink-0">등급</span>
|
||||
<span className="text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">{getGradeName(currentUser.grade)}</span>
|
||||
<span className="text-[13px] md:text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">{getGradeName(currentUser.grade)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-[4px] min-w-0">
|
||||
<ProfileLabelIcon width={14} height={14} className="shrink-0" />
|
||||
<span className="text-[11px] text-[#8c8c8c] font-[700] shrink-0">포인트</span>
|
||||
<span className="text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">{currentUser.points.toLocaleString()}</span>
|
||||
<span className="text-[13px] md:text-[14px] text-[#5c5c5c] font-[700] min-w-0 truncate">{currentUser.points.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 구분선 */}
|
||||
<div className="w-[8px] h-[162px] bg-[#d5d5d5] shrink-0"></div>
|
||||
{/* 구분선 (모바일 숨김) */}
|
||||
<div className="hidden md:block w-[8px] h-[162px] bg-[#d5d5d5] shrink-0"></div>
|
||||
|
||||
{/* 우측: 등급 배지 및 포인트 진행 상황 */}
|
||||
<div className="flex flex-col items-center justify-center w-[161px] shrink-0">
|
||||
<div className="w-[125px] h-[125px] flex items-center justify-center mb-[16px]">
|
||||
<div className="flex flex-col items-center justify-center w-[120px] md:w-[161px] shrink-0">
|
||||
<div className="w-[96px] h-[96px] md:w-[125px] md:h-[125px] flex items-center justify-center mb-[16px]">
|
||||
<GradeIcon grade={currentUser.grade} width={125} height={125} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-[16px] items-center">
|
||||
<div className="text-[20px] font-bold text-[#5c5c5c]">{getGradeName(currentUser.grade)}</div>
|
||||
<div className="flex items-start gap-[8px] text-[20px] font-bold">
|
||||
<div className="text-[16px] md:text-[20px] font-bold text-[#5c5c5c]">{getGradeName(currentUser.grade)}</div>
|
||||
<div className="flex items-start gap-[8px] text-[16px] md:text-[20px] font-bold">
|
||||
<span className="text-[#5c5c5c] truncate">{(currentUser.points / 1000000).toFixed(1)}M</span>
|
||||
<span className="text-[#8c8c8c] shrink-0">/ {(nextGradePoints / 1000000).toFixed(1)}M</span>
|
||||
</div>
|
||||
@@ -158,31 +159,31 @@ export default async function MyPage({ searchParams }: { searchParams: Promise<{
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 탭 버튼 */}
|
||||
<section className="h-[67px]">
|
||||
<div className="bg-white rounded-[24px] p-[10px] flex gap-[10px] items-center">
|
||||
{/* 탭 버튼 (모바일 가로 스크롤) */}
|
||||
<section className="mt-4">
|
||||
<div className="bg-white rounded-[16px] p-2 md:rounded-[24px] md:p-[10px] flex gap-2 md:gap-[10px] items-center overflow-x-auto scrollbar-thin-x">
|
||||
{tabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.key}
|
||||
href={`/my-page?tab=${tab.key}`}
|
||||
className={`flex-1 h-[46px] rounded-[16px] flex items-center justify-center gap-[8px] ${
|
||||
className={`shrink-0 md:flex-1 h-10 md:h-[46px] rounded-[16px] flex items-center justify-center gap-[8px] px-3 ${
|
||||
activeTab === tab.key
|
||||
? "bg-[#5c5c5c] text-white"
|
||||
: "bg-transparent text-[#5c5c5c]"
|
||||
}`}
|
||||
>
|
||||
<span className={`text-[16px] ${activeTab === tab.key ? "font-medium" : "font-normal"}`}>
|
||||
<span className={`text-[14px] md:text-[16px] ${activeTab === tab.key ? "font-medium" : "font-normal"}`}>
|
||||
{tab.label}
|
||||
</span>
|
||||
<div
|
||||
className={`h-[20px] px-[8px] py-[4px] rounded-[14px] ${
|
||||
className={`inline-flex items-center justify-center h-[20px] px-[8px] rounded-[14px] ${
|
||||
activeTab === tab.key
|
||||
? "bg-white"
|
||||
: "border border-[#707070]"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`text-[10px] font-semibold ${
|
||||
className={`text-[10px] leading-none font-semibold ${
|
||||
activeTab === tab.key ? "text-[#707070]" : "text-[#707070]"
|
||||
}`}
|
||||
>
|
||||
@@ -204,11 +205,55 @@ export default async function MyPage({ searchParams }: { searchParams: Promise<{
|
||||
variant="board"
|
||||
/>
|
||||
)}
|
||||
{activeTab === "comments" && (
|
||||
<div className="bg-white rounded-[24px] p-4">
|
||||
<div className="text-center text-neutral-500 py-10">댓글 기능은 준비 중입니다.</div>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "comments" && (async () => {
|
||||
const pageSize = 20;
|
||||
const comments = await prisma.comment.findMany({
|
||||
where: { authorId: currentUser.userId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
select: { id: true, postId: true, content: true, createdAt: true },
|
||||
});
|
||||
return (
|
||||
<div className="bg-white rounded-[24px] p-4">
|
||||
<ul className="divide-y divide-neutral-200">
|
||||
{comments.map((c) => (
|
||||
<li key={c.id} className="py-3 flex items-center justify-between gap-4">
|
||||
<Link href={`/posts/${c.postId}`} className="text-sm text-neutral-800 hover:underline truncate max-w-[70%]">
|
||||
{c.content.slice(0, 80)}{c.content.length > 80 ? "…" : ""}
|
||||
</Link>
|
||||
<span className="text-xs text-neutral-500 shrink-0">{new Date(c.createdAt).toLocaleString()}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{activeTab === "points" && (async () => {
|
||||
const pageSize = 20;
|
||||
const txns = await prisma.pointTransaction.findMany({
|
||||
where: { userId: currentUser.userId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
select: { id: true, amount: true, reason: true, createdAt: true },
|
||||
});
|
||||
return (
|
||||
<div className="bg-white rounded-[24px] p-4">
|
||||
<ul className="divide-y divide-neutral-200">
|
||||
{txns.map((t) => (
|
||||
<li key={t.id} className="py-3 flex items-center justify-between gap-4">
|
||||
<div className="text-sm text-neutral-800 truncate max-w-[60%]">{t.reason}</div>
|
||||
<div className={`text-sm font-semibold ${t.amount >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{t.amount >= 0 ? `+${t.amount}` : `${t.amount}`}
|
||||
</div>
|
||||
<span className="text-xs text-neutral-500 shrink-0">{new Date(t.createdAt).toLocaleString()}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{activeTab === "messages-received" && (
|
||||
<div className="bg-white rounded-[24px] p-4">
|
||||
<div className="text-center text-neutral-500 py-10">받은 쪽지함 기능은 준비 중입니다.</div>
|
||||
|
||||
Reference in New Issue
Block a user