'use client'; import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { Gamepad2, Package, Ticket } from 'lucide-react'; import { fetchApi } from '@/lib/utils/client'; import Loading from '@/app/component/Loading'; import Pagination from '@/app/component/Pagination'; import type { ProductListResponse, ProductListRow, ProductType, SortKey, GameOption, GameListResponse } from '@/types/store'; import { SORT_OPTIONS } from '@/types/store'; const PER_PAGE = 20; const TYPE_BADGE_CLASS: Record = { 1: 'bg-sky-100 text-sky-800 dark:bg-sky-900/40 dark:text-sky-300', 2: 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300' }; const TYPE_LABEL: Record = { 1: '실물', 2: '쿠폰' }; function TypeBadge({ type }: { type: ProductType }) { const Icon = type === 2 ? Ticket : Package; return ( ); } function formatPrice(n: number): string { return n.toLocaleString('ko-KR'); } export default function StorePage() { const router = useRouter(); const search = useSearchParams(); const initialKeyword = search.get('keyword') ?? ''; const initialType = search.get('type') ?? ''; const initialGameID = search.get('gameID') ?? ''; const initialSort = (search.get('sort') as SortKey|null) ?? 'latest'; const initialPage = (() => { const raw = parseInt(search.get('page') ?? '1', 10); return Number.isNaN(raw) || raw < 1 ? 1 : raw; })(); const [products, setProducts] = useState([]); const [games, setGames] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(initialKeyword); const [submittedKeyword, setSubmittedKeyword] = useState(initialKeyword); const [typeFilter, setTypeFilter] = useState(initialType); const [gameIDFilter, setGameIDFilter] = useState(initialGameID); const [sort, setSort] = useState(initialSort); const [page, setPage] = useState(initialPage); // 게임 목록은 mount 시 1회만 fetch useEffect(() => { (async () => { const res = await fetchApi('/api/store/games', { silent: true }); if (res.success && res.data) { setGames(res.data.list); } })(); }, []); const load = useCallback(async () => { setLoading(true); const params = new URLSearchParams(); params.set('page', String(page)); params.set('perPage', String(PER_PAGE)); params.set('sort', sort); if (submittedKeyword) { params.set('keyword', submittedKeyword); } if (typeFilter) { params.set('type', typeFilter); } if (gameIDFilter) { params.set('gameID', gameIDFilter); } const res = await fetchApi(`/api/store/products?${params.toString()}`, { silent: true }); if (res.success && res.data) { setProducts(res.data.list); setTotal(res.data.total); } else { setProducts([]); setTotal(0); } setLoading(false); }, [submittedKeyword, typeFilter, gameIDFilter, sort, page]); // 필터/정렬/게임/검색 키워드 변경 시 1페이지로 리셋 useEffect(() => { setPage(1); }, [sort, typeFilter, gameIDFilter, submittedKeyword]); // page 변경 또는 의존 필터/검색 변경 시 자동 재조회 useEffect(() => { load(); }, [sort, typeFilter, gameIDFilter, submittedKeyword, page]); // eslint-disable-line react-hooks/exhaustive-deps const syncUrl = useCallback((nextPage: number, kw: string = submittedKeyword) => { const params = new URLSearchParams(); if (kw) { params.set('keyword', kw); } if (typeFilter) { params.set('type', typeFilter); } if (gameIDFilter) { params.set('gameID', gameIDFilter); } if (sort !== 'latest') { params.set('sort', sort); } if (nextPage > 1) { params.set('page', String(nextPage)); } router.replace(`/store${params.toString() ? `?${params.toString()}` : ''}`); }, [submittedKeyword, typeFilter, gameIDFilter, sort, router]); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); const trimmed = keyword.trim(); setSubmittedKeyword(trimmed); setPage(1); syncUrl(1, trimmed); }; const handlePageChange = (next: number) => { setPage(next); syncUrl(next); if (typeof window !== 'undefined') { window.scrollTo({ top: 0, behavior: 'smooth' }); } }; return (

상점{' '} ({total.toLocaleString()}개)

{ setKeyword(e.target.value); }} placeholder="상품명 또는 게임 검색" className="w-full sm:flex-1 border rounded px-3 py-2 text-sm bg-white dark:bg-neutral-900" maxLength={100} />
{loading ? ( ) : products.length === 0 ? (
판매 중인 상품이 없습니다.
) : ( <>
{products.map((p) => (
{p.thumbnail ? ( // eslint-disable-next-line @next/next/no-img-element {p.name} ) : ( 이미지 없음 )} {p.stock === 0 && (
SOLD OUT
)}
{p.gameName}
{p.name}
{p.discountType !== 0 && p.salePrice !== p.price ? ( <>
{formatPrice(p.salePrice)}P
{formatPrice(p.price)}P {p.discountType === 1 ? `-${p.discountValue}%` : `-${formatPrice(p.discountValue)}P`}
) : (
{formatPrice(p.price)}P
)}
))}

)}
); }