'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' | 'saju' | 'lotto' | '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 { product_id: string; created_at: string; expires_at: string; } 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); // ํ™œ์„ฑ ๊ตฌ๋… ์กฐํšŒ (paid ์ƒํƒœ์˜ lotto ํ”Œ๋žœ) const LOTTO_PLANS = ['lotto_gold', 'lotto_platinum', 'lotto_diamond']; const { data: subs } = await supabase .from('orders') .select('product_id, created_at') .eq('user_id', user.id) .eq('status', 'paid') .in('product_id', LOTTO_PLANS) .order('created_at', { ascending: false }); if (subs && subs.length > 0) { const activeSubs: ActiveSubscription[] = subs.map((s) => { const createdAt = new Date(s.created_at); const expiresAt = new Date(createdAt); expiresAt.setDate(expiresAt.getDate() + 31); return { product_id: s.product_id, created_at: s.created_at, expires_at: expiresAt.toISOString() }; }); setActiveSubscriptions(activeSubs); } // ๋กœ๋˜ ํžˆ์Šคํ† ๋ฆฌ ์กฐํšŒ 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 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 tabs: { key: Tab; label: string; count?: number }[] = [ { key: 'profile', label: '๋‚ด ์ •๋ณด' }, { key: 'saju', label: '์‚ฌ์ฃผ ๊ธฐ๋ก', count: sajuRecords.length }, { key: 'lotto', label: '๐ŸŽฐ ๋กœ๋˜ ๊ธฐ๋ก', count: lottoHistory.length }, { key: 'payments', label: '๊ฒฐ์ œ ๋‚ด์—ญ', count: payments.length }, { key: 'orders', label: '์˜๋ขฐ ๋‚ด์—ญ', count: orders.length }, ]; 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' })}
{/* ๊ตฌ๋… ์ค‘์ธ ์„œ๋น„์Šค */} {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()) / (1000 * 60 * 60 * 24))); const isExpired = daysLeft === 0; return (
{info?.emoji ?? '๐ŸŽŸ'}
๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒœ {info?.label ?? sub.product_id}
{new Date(sub.created_at).toLocaleDateString('ko-KR')} ๊ตฌ๋… ์‹œ์ž‘
{isExpired ? ( ๋งŒ๋ฃŒ๋จ ) : ( <>
D-{daysLeft}
{expiresDate.toLocaleDateString('ko-KR')} ๋งŒ๋ฃŒ
)}
); })}
)} {/* ํ…”๋ ˆ๊ทธ๋žจ ์—ฐ๋™ ์นด๋“œ */}

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

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

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

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

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

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

๋น ๋ฅธ ๋ฉ”๋‰ด

์‚ฌ์ฃผ ๋ถ„์„
์ƒˆ ์‚ฌ์ฃผ ๋ณด๊ธฐ
๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒœ
๊ตฌ๋…์ž ์ „์šฉ
์™ธ์ฃผ ์˜๋ขฐ
ํ”„๋กœ์ ํŠธ ๋ฌธ์˜
)} {/* ๋กœ๋˜ ๋ฒˆํ˜ธ ๊ธฐ๋ก */} {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} โ†’
); }