'use client';
// D3 레벨/보상 위젯 — 현재 등급·Exp·다음 등급 진행 + 오늘 획득(액션별 캡 잔여).
// GET /api/members/me/level + GET /api/rewards/me/today (둘 다 JWT).
import '@/app/styles/reward.scss';
import { useEffect, useState } from 'react';
import Image from 'next/image';
import { fetchApi } from '@/lib/utils/client';
import Loading from '@/app/component/Loading';
import NavTabs from '../navTabs';
import type { MemberLevelResponse, RewardsTodayResponse } from '@/types/reward';
function LevelCard({ level }: { level: MemberLevelResponse })
{
// 다음 등급 요구치 대비 현재 Exp 비율 (0~100). 현재등급 요구치는 응답에 없으므로 요구치 대비로 근사.
const hasNext = level.nextGradeRequiredExp != null && level.expToNextGrade != null;
const remaining = level.expToNextGrade ?? 0;
const percent = hasNext && level.nextGradeRequiredExp! > 0
? Math.min(100, Math.max(0, Math.round((level.exp / level.nextGradeRequiredExp!) * 100)))
: 100;
return (
{level.currentGradeImage && (
)}
{level.currentGradeName ?? '등급 없음'}
경험치 {level.exp.toLocaleString()} XP
{hasNext ? (
<>
다음 등급 {level.nextGradeName}까지 {remaining.toLocaleString()} XP 남음
>
) : (
최고 등급에 도달했습니다.
)}
);
}
function RewardsToday({ rewards }: { rewards: RewardsTodayResponse })
{
return (
오늘 획득 ({rewards.rewardDate})
+{rewards.totalExp.toLocaleString()} XP · +{rewards.totalPoint.toLocaleString()} 토큰
{rewards.actions.map((a) => {
// 캡 잔여: count/exp/point 중 정의된 것을 보여준다. 전부 null 이면 무제한.
const parts: string[] = [];
if (a.remainingCount != null) {
parts.push(`횟수 ${a.remainingCount.toLocaleString()}회`);
}
if (a.remainingExp != null) {
parts.push(`XP ${a.remainingExp.toLocaleString()}`);
}
if (a.remainingPoint != null) {
parts.push(`토큰 ${a.remainingPoint.toLocaleString()}`);
}
const isFull = (a.remainingCount === 0) || (a.remainingExp === 0) || (a.remainingPoint === 0);
const capText = parts.length > 0 ? `남은 캡: ${parts.join(' · ')}` : '캡 없음';
return (
{a.actionName}
{a.count.toLocaleString()}회 · +{a.expEarned.toLocaleString()} XP{a.pointEarned > 0 ? ` · +${a.pointEarned.toLocaleString()} 토큰` : ''}
{isFull ? '오늘 한도 도달' : capText}
);
})}
);
}
export default function LevelPage()
{
const [level, setLevel] = useState(null);
const [rewards, setRewards] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
(async () => {
setLoading(true);
const [levelRes, rewardsRes] = await Promise.all([
fetchApi('/api/members/me/level', { silent: true }),
fetchApi('/api/rewards/me/today', { silent: true })
]);
if (cancelled) {
return;
}
if (levelRes.success && levelRes.data) {
setLevel(levelRes.data);
}
if (rewardsRes.success && rewardsRes.data) {
setRewards(rewardsRes.data);
}
setLoading(false);
})();
return () => {
cancelled = true;
};
}, []);
return (
<>
레벨 · 보상
활동(글·댓글·추천·채팅·출석)으로 경험치(XP)와 토큰을 얻어 등급이 오릅니다. 액션별로 하루 획득 한도가 있습니다.
{loading ? (
) : (
{level ? (
) : (
레벨 정보를 불러올 수 없습니다.
)}
{rewards &&
}
)}
>
);
}