'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 && }
)}
); }