'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { createClient } from '@/lib/supabase/client'; import type { User } from '@supabase/supabase-js'; import TelegramGuideModal from '@/app/components/TelegramGuideModal'; function buildSajuResultUrl(rec: SajuRecord) { const { birth_year, birth_month, birth_day, birth_hour, gender } = rec.saju_data; if (!birth_year || !birth_month || !birth_day) return '/saju/input'; let url = `/saju/result?year=${birth_year}&month=${birth_month}&day=${birth_day}&gender=${gender}&calendarType=solar`; if (birth_hour != null) url += `&hour=${birth_hour}`; return url; } type Tab = 'profile' | 'subscription' | 'lotto' | 'saju' | 'payments' | 'orders'; type TelegramLinkState = 'idle' | 'generating' | 'waiting' | 'disconnecting'; interface SajuRecord { id: number; created_at: string; saju_data: { birth_year: number; birth_month: number; birth_day: number; birth_hour?: number; gender: string; }; interpretation: string | null; is_paid: boolean; } interface Payment { id: string; created_at: string; amount: number; status: string; product_name: string; } interface Order { id: string; created_at: string; service: string; message: string; status: string; } interface LottoHistoryItem { id: number; numbers: number[]; source: string; plan_id: string; created_at: string; } interface ActiveSubscription { id: string; product_id: string; status: string; auto_renew: boolean; started_at: string; expires_at: string; cancelled_at: string | null; } const PLAN_LABELS: Record = { lotto_gold: { label: '๊ณจ๋“œ', emoji: '๐Ÿฅ‡', color: 'amber' }, lotto_platinum: { label: 'ํ”Œ๋ž˜ํ‹ฐ๋„˜', emoji: '๐Ÿ’Ž', color: 'sky' }, lotto_diamond: { label: '๋‹ค์ด์•„', emoji: '๐Ÿ‘‘', color: 'violet' }, }; export default function MyPage() { const router = useRouter(); const supabase = createClient(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [tab, setTab] = useState('profile'); const [sajuRecords, setSajuRecords] = useState([]); const [payments, setPayments] = useState([]); const [orders, setOrders] = useState([]); const [lottoHistory, setLottoHistory] = useState([]); const [activeSubscriptions, setActiveSubscriptions] = useState([]); // ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๋™ ์ƒํƒœ const [telegramChatId, setTelegramChatId] = useState(null); const [telegramLinkState, setTelegramLinkState] = useState('idle'); const [telegramDeepLink, setTelegramDeepLink] = useState(''); const [telegramLinkExpiry, setTelegramLinkExpiry] = useState(''); const [showTelegramGuide, setShowTelegramGuide] = useState(false); useEffect(() => { async function init() { const { data: { user } } = await supabase.auth.getUser(); if (!user) { router.push('/login'); return; } setUser(user); // ์‚ฌ์ฃผ ๊ธฐ๋ก ์กฐํšŒ (ํ…Œ์ด๋ธ” ์žˆ์„ ๋•Œ ๋™์ž‘) const { data: saju } = await supabase .from('saju_records') .select('*') .eq('user_id', user.id) .order('created_at', { ascending: false }) .limit(20); setSajuRecords(saju || []); // ๊ฒฐ์ œ ๋‚ด์—ญ ์กฐํšŒ const { data: pay } = await supabase .from('payments') .select('*') .eq('user_id', user.id) .order('created_at', { ascending: false }) .limit(20); setPayments(pay || []); // ์˜๋ขฐ ๋‚ด์—ญ ์กฐํšŒ const { data: ord } = await supabase .from('contact_requests') .select('*') .eq('user_id', user.id) .order('created_at', { ascending: false }) .limit(20); setOrders(ord || []); // ํ…”๋ ˆ๊ทธ๋žจ chat_id ์กฐํšŒ const { data: profile } = await supabase .from('profiles') .select('telegram_chat_id') .eq('id', user.id) .maybeSingle(); setTelegramChatId(profile?.telegram_chat_id ?? null); // ๊ตฌ๋… ๋ชฉ๋ก ์กฐํšŒ (subscriptions ํ…Œ์ด๋ธ”) const subRes = await fetch('/api/subscription'); if (subRes.ok) { const subData = await subRes.json(); setActiveSubscriptions(subData.subscriptions ?? []); } // ๋กœ๋˜ ํžˆ์Šคํ† ๋ฆฌ ์กฐํšŒ const { data: history } = await supabase .from('lotto_history') .select('id, numbers, source, plan_id, created_at') .eq('user_id', user.id) .order('created_at', { ascending: false }) .limit(50); setLottoHistory(history ?? []); setLoading(false); } init(); }, []); const handleLogout = async () => { await supabase.auth.signOut(); router.push('/'); router.refresh(); }; // โ”€โ”€ ๊ตฌ๋… ํ•ด์ง€ โ”€โ”€ const handleCancelSubscription = async (subId: string) => { if (!confirm('๊ตฌ๋…์„ ํ•ด์ง€ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?\n๋งŒ๋ฃŒ์ผ๊นŒ์ง€๋Š” ์„œ๋น„์Šค๋ฅผ ๊ณ„์† ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.')) return; const res = await fetch(`/api/subscription/${subId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'cancel' }), }); if (res.ok) { setActiveSubscriptions((prev) => prev.map((s) => s.id === subId ? { ...s, status: 'cancelled', auto_renew: false, cancelled_at: new Date().toISOString() } : s) ); } else { alert('ํ•ด์ง€ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.'); } }; // โ”€โ”€ ์ž๋™๊ฐฑ์‹  ํ† ๊ธ€ โ”€โ”€ const handleToggleAutoRenew = async (subId: string) => { const res = await fetch(`/api/subscription/${subId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'toggle_autorenew' }), }); if (res.ok) { const data = await res.json(); setActiveSubscriptions((prev) => prev.map((s) => s.id === subId ? { ...s, auto_renew: data.auto_renew } : s) ); } }; // โ”€โ”€ ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๊ฒฐ โ”€โ”€ const handleTelegramConnect = async () => { setTelegramLinkState('generating'); try { const res = await fetch('/api/telegram/connect', { method: 'POST' }); if (!res.ok) throw new Error('API_ERROR'); const data = await res.json(); setTelegramDeepLink(data.deepLink); setTelegramLinkExpiry(new Date(data.expiresAt).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })); setTelegramLinkState('waiting'); // 15๋ถ„ ํ›„ ์ž๋™์œผ๋กœ idle ๋ณต๊ท€ setTimeout(() => setTelegramLinkState('idle'), 15 * 60 * 1000); } catch { setTelegramLinkState('idle'); alert('์—ฐ๊ฒฐ ์ฝ”๋“œ ๋ฐœ๊ธ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.'); } }; // ์—ฐ๊ฒฐ ํ›„ ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ (๋ฒ„ํŠผ ํด๋ฆญ ์‹œ) const handleTelegramRefresh = async () => { const { data: profile } = await supabase .from('profiles') .select('telegram_chat_id') .eq('id', user!.id) .maybeSingle(); const chatId = profile?.telegram_chat_id ?? null; setTelegramChatId(chatId); if (chatId) setTelegramLinkState('idle'); }; // โ”€โ”€ ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๊ฒฐ ํ•ด์ œ โ”€โ”€ const handleTelegramDisconnect = async () => { if (!confirm('ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๊ฒฐ์„ ํ•ด์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) return; setTelegramLinkState('disconnecting'); try { await fetch('/api/telegram/connect', { method: 'DELETE' }); setTelegramChatId(null); setTelegramDeepLink(''); } catch { alert('์—ฐ๊ฒฐ ํ•ด์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'); } setTelegramLinkState('idle'); }; if (loading) { return (
); } if (!user) return null; const activeSubs = activeSubscriptions.filter((s) => s.status === 'active' || s.status === 'cancelled'); const tabs: { key: Tab; label: string; count?: number }[] = [ { key: 'profile', label: '๋‚ด ์ •๋ณด' }, { key: 'subscription', label: '๊ตฌ๋… ๊ด€๋ฆฌ', count: activeSubs.length || undefined }, { key: 'lotto', label: '๋กœ๋˜ ๊ธฐ๋ก', count: lottoHistory.length || undefined }, { key: 'saju', label: '์‚ฌ์ฃผ ๊ธฐ๋ก', count: sajuRecords.length || undefined }, { key: 'payments', label: '๊ฒฐ์ œ ๋‚ด์—ญ', count: payments.length || undefined }, { key: 'orders', label: '์˜๋ขฐ ๋‚ด์—ญ', count: orders.length || undefined }, ]; return (
{/* ํ…”๋ ˆ๊ทธ๋žจ ๊ฐ€์ด๋“œ ๋ชจ๋‹ฌ */} {showTelegramGuide && ( setShowTelegramGuide(false)} /> )} {/* ํ—ค๋” */}
{user.email?.[0].toUpperCase()}
{user.email}
๊ฐ€์ž…์ผ: {new Date(user.created_at).toLocaleDateString('ko-KR')}
{/* ํƒญ */}
{tabs.map((t) => ( ))}
{/* ํƒญ ์ฝ˜ํ…์ธ  */} {/* ๋‚ด ์ •๋ณด */} {tab === 'profile' && (

๊ณ„์ • ์ •๋ณด

์ด๋ฉ”์ผ {user.email}
๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ• {user.app_metadata?.provider === 'google' ? 'Google' : '์ด๋ฉ”์ผ'}
๊ฐ€์ž…์ผ {new Date(user.created_at).toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' })}
{/* ๊ตฌ๋… ์ค‘์ธ ์„œ๋น„์Šค - ์š”์•ฝ (ํƒญ์œผ๋กœ ์œ ๋„) */} {activeSubs.length > 0 && (
{PLAN_LABELS[activeSubs[0].product_id]?.emoji ?? '๐ŸŽŸ'}
๋กœ๋˜ {PLAN_LABELS[activeSubs[0].product_id]?.label} ๊ตฌ๋… ์ค‘
{Math.max(0, Math.ceil((new Date(activeSubs[0].expires_at).getTime() - Date.now()) / 86400000))}์ผ ํ›„ ๋งŒ๋ฃŒ {activeSubs[0].status === 'cancelled' && ' ยท ํ•ด์ง€ ์˜ˆ์ •'}
)} {/* ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๋™ ์นด๋“œ */}

ํ…”๋ ˆ๊ทธ๋žจ ์•Œ๋ฆผ ์—ฐ๋™ ํ”Œ๋ž˜ํ‹ฐ๋„˜ ยท ๋‹ค์ด์•„ ์ „์šฉ

{telegramChatId ? ( /* โ”€โ”€ ์—ฐ๊ฒฐ๋จ โ”€โ”€ */
์—ฐ๊ฒฐ๋จ
Chat ID: {telegramChatId}
) : telegramLinkState === 'waiting' ? ( /* โ”€โ”€ ์—ฐ๊ฒฐ ๋Œ€๊ธฐ ์ค‘ โ”€โ”€ */

๐Ÿ“ฑ ์•„๋ž˜ ์ˆœ์„œ๋กœ ์ง„ํ–‰ํ•˜์„ธ์š”

  1. ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡์„ ์—ฝ๋‹ˆ๋‹ค
  2. ํ…”๋ ˆ๊ทธ๋žจ์—์„œ ์‹œ์ž‘ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฆ…๋‹ˆ๋‹ค
  3. ๋ด‡์ด "์—ฐ๊ฒฐ ์™„๋ฃŒ" ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋ฉด ์ƒˆ๋กœ๊ณ ์นจ์„ ๋ˆŒ๋Ÿฌ์ฃผ์„ธ์š”

โฑ ์œ ํšจ์‹œ๊ฐ„: {telegramLinkExpiry}๊นŒ์ง€

ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ์—ด๊ธฐ
) : ( /* โ”€โ”€ ๋ฏธ์—ฐ๊ฒฐ โ”€โ”€ */
์—ฐ๊ฒฐ ์•ˆ ๋จ
ํ…”๋ ˆ๊ทธ๋žจ์œผ๋กœ ๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๋กœ ๋ฐ›์•„๋ณด์„ธ์š”
)}

๋น ๋ฅธ ๋ฉ”๋‰ด

์‚ฌ์ฃผ ๋ถ„์„
์ƒˆ ์‚ฌ์ฃผ ๋ณด๊ธฐ
๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒœ
๊ตฌ๋…์ž ์ „์šฉ
์™ธ์ฃผ ์˜๋ขฐ
ํ”„๋กœ์ ํŠธ ๋ฌธ์˜
)} {/* ๊ตฌ๋… ๊ด€๋ฆฌ */} {tab === 'subscription' && (
{activeSubscriptions.length === 0 ? ( ) : ( activeSubscriptions.map((sub) => { const info = PLAN_LABELS[sub.product_id]; const expiresDate = new Date(sub.expires_at); const daysLeft = Math.max(0, Math.ceil((expiresDate.getTime() - Date.now()) / 86400000)); const isExpired = sub.status === 'expired'; const isCancelled = sub.status === 'cancelled'; const isActive = sub.status === 'active'; return (
{/* ํ—ค๋” */}
{info?.emoji ?? '๐ŸŽŸ'}
๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒœ {info?.label ?? sub.product_id}
{new Date(sub.started_at).toLocaleDateString('ko-KR')} ์‹œ์ž‘
{isActive ? '์ด์šฉ ์ค‘' : isCancelled ? 'ํ•ด์ง€ ์˜ˆ์ •' : '๋งŒ๋ฃŒ๋จ'}
{/* ๋งŒ๋ฃŒ ์ •๋ณด */}
๋งŒ๋ฃŒ์ผ
{expiresDate.toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' })}
๋‚จ์€ ๊ธฐ๊ฐ„
{isExpired ? '๋งŒ๋ฃŒ๋จ' : `D-${daysLeft}`}
{/* ์ž๋™๊ฐฑ์‹  ํ† ๊ธ€ */} {!isExpired && (
์ž๋™ ๊ฐฑ์‹ 
{sub.auto_renew ? '๋งŒ๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค' : '๋งŒ๋ฃŒ ์‹œ ์ž๋™ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค'}
)} {/* ํ•ด์ง€ ์ทจ์†Œ ๋ฒ„ํŠผ */} {isCancelled && (
ํ•ด์ง€ ์‹ ์ฒญ๋จ ยท {expiresDate.toLocaleDateString('ko-KR')}๊นŒ์ง€ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. {sub.cancelled_at && ` (ํ•ด์ง€์ผ: ${new Date(sub.cancelled_at).toLocaleDateString('ko-KR')})`}
)} {/* ์•ก์…˜ ๋ฒ„ํŠผ */}
๋ฒˆํ˜ธ ์ถ”์ฒœ๋ฐ›๊ธฐ {isActive && ( )}
); }) )} {/* ๊ตฌ๋… ํ”Œ๋žœ ์ด๋™ */}
)} {/* ๋กœ๋˜ ๋ฒˆํ˜ธ ๊ธฐ๋ก */} {tab === 'lotto' && (
{lottoHistory.length === 0 ? ( ) : (
์ด {lottoHistory.length}๊ฐœ ์กฐํ•ฉ ์ƒ์„ฑ
{lottoHistory.map((item) => { const info = PLAN_LABELS[item.plan_id]; return (
{item.numbers.map((n) => { const color = n <= 10 ? 'bg-yellow-400 text-yellow-900' : n <= 20 ? 'bg-blue-500 text-white' : n <= 30 ? 'bg-red-500 text-white' : n <= 40 ? 'bg-slate-500 text-white' : 'bg-green-500 text-white'; return ( {n} ); })}
{item.source === 'nas' ? 'NAS ์ถ”์ฒœ' : '๋กœ์ปฌ ์ƒ์„ฑ'} {info?.emoji} {info?.label} {new Date(item.created_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
); })}
)}
)} {/* ์‚ฌ์ฃผ ๊ธฐ๋ก */} {tab === 'saju' && (
{sajuRecords.length === 0 ? ( ) : (
{sajuRecords.map((rec) => (
{new Date(rec.created_at).toLocaleDateString('ko-KR')}
{rec.saju_data?.birth_year ?? '?'}๋…„{' '} {rec.saju_data?.birth_month ?? '?'}์›”{' '} {rec.saju_data?.birth_day ?? '?'}์ผ์ƒ
{rec.saju_data?.gender === 'male' ? '๋‚จ์„ฑ' : '์—ฌ์„ฑ'} {rec.saju_data?.birth_hour != null ? ` ยท ${rec.saju_data.birth_hour}์‹œ์ƒ` : ''}
{rec.is_paid ? '์œ ๋ฃŒ' : '๋ฌด๋ฃŒ'}
{rec.interpretation && (

{rec.interpretation.replace(/[#*]/g, '').substring(0, 80)}...

)} {rec.is_paid && rec.interpretation ? 'AI ํ•ด์„ ๋‹ค์‹œ ๋ณด๊ธฐ โ†’' : '๊ฒฐ๊ณผ ๋ณด๊ธฐ โ†’'}
))}
)}
)} {/* ๊ฒฐ์ œ ๋‚ด์—ญ */} {tab === 'payments' && (
{payments.length === 0 ? ( ) : (
{payments.map((p, i) => ( ))}
์„œ๋น„์Šค ๊ธˆ์•ก ์ƒํƒœ ์ผ์‹œ
{p.product_name} โ‚ฉ{p.amount?.toLocaleString()} {p.status === 'paid' ? '๊ฒฐ์ œ์™„๋ฃŒ' : p.status} {new Date(p.created_at).toLocaleDateString('ko-KR')}
)}
)} {/* ์˜๋ขฐ ๋‚ด์—ญ */} {tab === 'orders' && (
{orders.length === 0 ? ( ) : (
{orders.map((o) => (
{o.service}
{o.status === 'completed' ? '์™„๋ฃŒ' : o.status === 'in_progress' ? '์ง„ํ–‰์ค‘' : '๋Œ€๊ธฐ์ค‘'}

{o.message}

{new Date(o.created_at).toLocaleDateString('ko-KR')}
))}
)}
)}
); } function EmptyState({ icon, title, desc, linkHref, linkLabel, }: { icon: string; title: string; desc: string; linkHref: string; linkLabel: string; }) { return (
{icon}
{title}
{desc}
{linkLabel} โ†’
); }