'use client'; import '@/app/styles/reward.scss'; import { useCallback, useEffect, useState } from 'react'; import { fetchApi } from '@/lib/utils/client'; import Loading from '@/app/component/Loading'; import Pagination from '@/app/component/Pagination'; import NavTabs from '../navTabs'; import { ItemKind, ITEM_KIND_LABEL, ITEM_KIND_DESC, DIRECT_USE_KINDS } from '@/types/account/items'; import type { MemberItemRow as ConsumableItemRow, MemberItemsResponse as ConsumableItemsResponse, UseItemResult } from '@/types/account/items'; import { EquippedBadge, NicknameText } from '@/components/community/EquippedBadges'; import { EQUIP_SLOT_ORDER, EQUIP_SLOT_LABEL, EQUIP_SLOT_DESC, ITEM_KIND_LABEL as STATUS_KIND_LABEL, parseEffectPayload } from '@/types/reward'; import type { MemberItemRow as StatusItemRow, MemberItemsResponse as StatusItemsResponse, EquipResponse, EquipSlotValue, EquippedItem } from '@/types/reward'; const PER_PAGE = 20; // StatusItemRow → EquippedItem 변환 (배지/닉네임 미리보기용) function toEquippedItem(row: StatusItemRow): EquippedItem { return { slot: row.equipSlot!, kind: row.kind, productName: row.productName, effectPayload: row.effectPayload, thumbnail: row.thumbnail }; } function formatExpiry(expiresAt: string|null): string { if (!expiresAt) { return '기간 제한 없음'; } const d = new Date(expiresAt); return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')} 만료`; } // ── 지위 아이템 (슬롯별 장착 관리) ── function StatusItemsSection() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [busyID, setBusyID] = useState(null); const load = useCallback(async () => { setLoading(true); const res = await fetchApi('/api/members/me/items?page=1&perPage=100', { silent: true }); if (res.success && res.data) { // 지위 아이템(슬롯 보유)만 취급 — 소모품은 아래 별도 섹션 setItems(res.data.list.filter(c => c.isStatusItem && c.equipSlot != null)); } else { setItems([]); } setLoading(false); }, []); useEffect(() => { load(); }, [load]); const toggleEquip = async (item: StatusItemRow) => { setBusyID(item.id); const action = item.isEquipped ? 'unequip' : 'equip'; const res = await fetchApi(`/api/members/me/items/${item.id}/${action}`, { method: 'POST', silent: true }); setBusyID(null); if (res.success) { await load(); } else { alert(res.message || '장착 상태 변경에 실패했습니다.'); } }; if (loading) { return ; } if (items.length === 0) { return (
보유한 지위 아이템이 없습니다. 상점에서 배지·닉네임 이펙트·댓글 이펙트를 구매할 수 있습니다.
); } // 슬롯별 그룹 const bySlot = new Map(); for (const slot of EQUIP_SLOT_ORDER) { bySlot.set(slot, []); } for (const item of items) { const arr = bySlot.get(item.equipSlot!) ?? []; arr.push(item); bySlot.set(item.equipSlot!, arr); } return (
{EQUIP_SLOT_ORDER.map((slot) => { const slotItems = bySlot.get(slot) ?? []; if (slotItems.length === 0) { return null; } return (

{EQUIP_SLOT_LABEL[slot]}

{EQUIP_SLOT_DESC[slot]} 슬롯당 1개만 장착됩니다.

    {slotItems.map((item) => { const equipped = toEquippedItem(item); return (
  • {item.thumbnail ? ( // eslint-disable-next-line @next/next/no-img-element {item.productName} ) : ( 🏅 )}
    {item.productName}
    {STATUS_KIND_LABEL[item.kind]} · {formatExpiry(item.expiresAt)} {item.giftFromName && · 선물 (from {item.giftFromName})}
    {/* 미리보기 — 배지 / 닉네임 이펙트 실제 렌더 */}
    {parseEffectPayload(item.effectPayload) || item.thumbnail ? ( ) : null}
  • ); })}
); })}
); } // ── 소모품 아이템 (기존 — 사용/자동소모) ── function ConsumableItemsSection() { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const [busyID, setBusyID] = useState(null); const load = useCallback(async () => { setLoading(true); const res = await fetchApi(`/api/member/me/items?page=${page}&perPage=${PER_PAGE}`, { silent: true }); if (res.success && res.data) { setItems(res.data.list); setTotal(res.data.total); } else { setItems([]); setTotal(0); } setLoading(false); }, [page]); useEffect(() => { load(); }, [load]); const useItem = async (item: ConsumableItemRow) => { if (item.kind === ItemKind.PostReset && !confirm('신고·댓글이 없는 예전 게시글이 모두 삭제됩니다. 계속하시겠어요?')) { return; } setBusyID(item.id); const res = await fetchApi(`/api/member/me/items/${item.id}/use`, { method: 'POST', silent: true }); setBusyID(null); if (res.success && res.data) { alert(res.data.message || '아이템을 사용했습니다.'); await load(); } else { alert(res.message || '아이템 사용에 실패했습니다.'); } }; if (loading) { return ; } if (items.length === 0) { return
보유한 소모품이 없습니다.
; } return ( <>
미사용 {total.toLocaleString()}개
    {items.map((item) => { const directUse = DIRECT_USE_KINDS.includes(item.kind); return (
  • {item.thumbnail ? ( // eslint-disable-next-line @next/next/no-img-element {item.productName} ) : ( 🎁 )}
    {ITEM_KIND_LABEL[item.kind] ?? item.productName} {item.kind === ItemKind.ExpBooster && item.durationDays > 0 && ( ({item.durationDays}일) )}
    {ITEM_KIND_DESC[item.kind]}
    {directUse ? ( ) : ( 기능 이용 시 사용 )}
  • ); })}

); } export default function MemberItemsPage() { const [tab, setTab] = useState<'status'|'consumable'>('status'); return ( <>

아이템

지위 아이템(배지·닉네임/댓글 이펙트)은 슬롯별로 장착하고, 소모품은 사용하거나 해당 기능 이용 시 자동으로 사용됩니다.

{tab === 'status' ? : }
); }