'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { CreditCard, Gamepad2, Package, Ticket, Trash2 } from 'lucide-react'; import { fetchApi } from '@/lib/utils/client'; import PayButton, { type PayButtonState } from '@/app/component/PayButton'; import useAuth from '@/hooks/useAuth'; import useCart from '@/hooks/useCart'; import type { ProductType, ChannelSearchResponse, ChannelSearchRow } from '@/types/store'; 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 CartPage() { const router = useRouter(); const { loginCheck } = useAuth(); const { items, channel, updateQuantity, removeItem, setChannel, clear, totalCount, totalPrice } = useCart(); const [payState] = useState('idle'); const [submitError, setSubmitError] = useState(null); const [channelModalOpen, setChannelModalOpen] = useState(false); const [optedOut, setOptedOut] = useState(false); const [pendingCheckout, setPendingCheckout] = useState(false); const requireChannel = items.some(i => i.requireDonationChannel); const handleCheckout = () => { if (!loginCheck()) { return; } if (items.length === 0) { setSubmitError('장바구니가 비어있습니다.'); return; } setSubmitError(null); // 후원 채널 확인 절차 강제 — 채널 미선택 시 (필수이거나 아직 선택안함 미체크면) 모달 먼저 오픈 if (!channel && (requireChannel || !optedOut)) { setPendingCheckout(true); setChannelModalOpen(true); return; } // 실제 결제는 /checkout 영수증 페이지에서 진행 router.push('/checkout'); }; return (

장바구니{' '} ({totalCount.toLocaleString()}개)

{items.length === 0 ? (

장바구니가 비어있습니다.

상점 둘러보기
) : (
{/* 좌측: 상품 라인 리스트 */}
{items.map((item) => (
{item.thumbnail ? ( // eslint-disable-next-line @next/next/no-img-element {item.name} ) : ( 이미지 없음 )}
{item.gameName}
{item.name}
{ const v = parseInt(e.target.value || '1', 10); updateQuantity(item.productID, isNaN(v) ? 1 : v); }} className='w-12 text-center bg-white dark:bg-neutral-900 outline-none border-0 appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none' />
{formatPrice(item.price)}P × {item.quantity}
{formatPrice(item.price * item.quantity)}P
))}
쇼핑 계속하기 〉
{/* 우측: 결제 패널 */}

주문 요약

상품 수 {totalCount}개
상품 금액 {formatPrice(totalPrice)}P
{channel && ( )}
결제 금액 {formatPrice(totalPrice)}P
{submitError && (
{submitError}
)} } onClick={handleCheckout} disabled={items.length === 0} />

* 결제 시 캐시 잔액에서 차감됩니다. (토큰 사용 불가)

)} {channelModalOpen && ( { setChannelModalOpen(false); setPendingCheckout(false); }} onSelect={(ch) => { setChannel(ch); setOptedOut(false); setChannelModalOpen(false); if (pendingCheckout) { setPendingCheckout(false); router.push('/checkout'); } }} onSkip={() => { setChannel(null); setOptedOut(true); setChannelModalOpen(false); if (pendingCheckout) { setPendingCheckout(false); router.push('/checkout'); } }} /> )}
); } function ChannelSelectModal({ onClose, onSelect, onSkip, requireChannel }: { onClose: () => void; onSelect: (ch: ChannelSearchRow) => void; onSkip: () => void; requireChannel: boolean; }) { const [keyword, setKeyword] = useState(''); const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { const handle = setTimeout(async () => { setLoading(true); const params = new URLSearchParams(); if (keyword.trim()) { params.set('keyword', keyword.trim()); } else { params.set('random', 'true'); } params.set('page', '1'); params.set('perPage', '5'); const res = await fetchApi(`/api/store/channels/search?${params.toString()}`, { silent: true }); if (res.success && res.data) { setChannels(res.data.list); } setLoading(false); }, 300); return () => { clearTimeout(handle); }; }, [keyword]); return (
{ e.stopPropagation(); }} >

후원 채널 선택

{ setKeyword(e.target.value); }} placeholder='채널명, 핸들, 후원 코드 검색' className='w-full border border-neutral-300 dark:border-neutral-700 rounded px-3 py-2 text-sm bg-white dark:bg-neutral-900' maxLength={100} /> {!keyword.trim() ? (

* 채널이 무작위 순서로 표시됩니다.

) : (

* 후원 코드는 정확히 입력해야 합니다.

)}
{loading ? (
검색 중...
) : channels.length === 0 ? (
채널이 없습니다.
) : ( channels.map((ch) => ( )) )}
{requireChannel ? (
후원 채널 선택이 필수인 상품입니다.
) : (
)}
); }