From b250d4b50cd279d2c9f4fffb086daa09743d0f08 Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 16 Mar 2026 03:51:18 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=99=88=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EA=B0=80=EA=B2=A9/=ED=94=8C=EB=9E=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95,=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EA=B5=AC=EB=8F=85=EC=9E=90=20=ED=86=B5=EA=B3=84/=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=ED=98=84=ED=99=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 홈 카드: 월 4,900원 → 900원~, 플랜명 골드/플래티넘/다이아로 수정 - 관리자 대시보드: 활성 구독자 수 카드 추가 - 관리자 회원 목록: 구독 현황(플랜명, 만료일) 컬럼 추가 Co-Authored-By: Claude Sonnet 4.6 --- app/admin/dashboard/page.tsx | 14 +++++++++++++- app/admin/members/page.tsx | 20 +++++++++++++++++++- app/api/admin/members/route.ts | 6 ++++-- app/api/admin/stats/route.ts | 6 ++++-- app/page.tsx | 6 +++--- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/app/admin/dashboard/page.tsx b/app/admin/dashboard/page.tsx index d86bc75..9a02316 100644 --- a/app/admin/dashboard/page.tsx +++ b/app/admin/dashboard/page.tsx @@ -7,6 +7,7 @@ interface Stats { totalOrders: number; totalRevenue: number; pendingContacts: number; + activeSubscribers: number; monthlyChart: Array<{ month: string; revenue: number }>; } @@ -53,7 +54,7 @@ export default function AdminDashboard() { ) : ( <> {/* 통계 카드 */} -
+
} /> + + + + } + /> = { + lotto_gold: '🥇 골드', + lotto_platinum: '💎 플래티넘', + lotto_diamond: '👑 다이아', +}; + export default function AdminMembersPage() { const [members, setMembers] = useState([]); const [loading, setLoading] = useState(true); @@ -65,6 +72,7 @@ export default function AdminMembersPage() { 이메일 이름 가입일 + 구독 결제 건수 총 결제액 @@ -72,7 +80,7 @@ export default function AdminMembersPage() { {filtered.length === 0 ? ( - + 회원 데이터가 없습니다 @@ -84,6 +92,16 @@ export default function AdminMembersPage() { {new Date(m.created_at).toLocaleDateString('ko-KR')} + + {m.activeSub ? ( +
+ {PLAN_LABELS[m.activeSub.product_id] ?? m.activeSub.product_id} +
{new Date(m.activeSub.expires_at).toLocaleDateString('ko-KR')} 만료
+
+ ) : ( + - + )} + 0 ? 'bg-green-900/40 text-green-400' : 'bg-slate-700 text-slate-500'}`}> {m.orderCount}건 diff --git a/app/api/admin/members/route.ts b/app/api/admin/members/route.ts index 863164f..860abd3 100644 --- a/app/api/admin/members/route.ts +++ b/app/api/admin/members/route.ts @@ -27,12 +27,14 @@ export async function GET() { // 각 회원의 주문 수 + 결제 금액 집계 const enriched = await Promise.all( (profiles ?? []).map(async (p: { id: string; email: string; full_name: string; created_at: string }) => { - const [ordersRes, paymentsRes] = await Promise.all([ + const [ordersRes, paymentsRes, subsRes] = await Promise.all([ supabase.from('orders').select('id', { count: 'exact', head: true }).eq('user_id', p.id).eq('status', 'paid'), supabase.from('payments').select('amount').eq('user_id', p.id).eq('status', 'paid'), + supabase.from('subscriptions').select('product_id, status, expires_at').eq('user_id', p.id).eq('status', 'active').order('created_at', { ascending: false }).limit(1), ]); const totalPaid = (paymentsRes.data ?? []).reduce((s: number, x: { amount: number }) => s + x.amount, 0); - return { ...p, orderCount: ordersRes.count ?? 0, totalPaid }; + const activeSub = subsRes.data?.[0] ?? null; + return { ...p, orderCount: ordersRes.count ?? 0, totalPaid, activeSub }; }) ); diff --git a/app/api/admin/stats/route.ts b/app/api/admin/stats/route.ts index 8a5da4a..7b19106 100644 --- a/app/api/admin/stats/route.ts +++ b/app/api/admin/stats/route.ts @@ -15,18 +15,20 @@ export async function GET() { const supabase = createAdminClient(); // 병렬 쿼리 - const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes] = await Promise.all([ + const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes, subsRes] = await Promise.all([ supabase.from('profiles').select('id', { count: 'exact', head: true }), supabase.from('orders').select('id', { count: 'exact', head: true }).eq('status', 'paid'), supabase.from('payments').select('amount').eq('status', 'paid'), supabase.from('contact_requests').select('id', { count: 'exact', head: true }).eq('status', 'pending'), supabase.from('payments').select('amount, created_at').eq('status', 'paid').order('created_at', { ascending: true }), + supabase.from('subscriptions').select('id', { count: 'exact', head: true }).eq('status', 'active'), ]); const totalMembers = profilesRes.count ?? 0; const totalOrders = ordersRes.count ?? 0; const totalRevenue = (paymentsRes.data ?? []).reduce((sum: number, p: { amount: number }) => sum + p.amount, 0); const pendingContacts = contactsRes.count ?? 0; + const activeSubscribers = subsRes.count ?? 0; // 최근 6개월 월별 수익 집계 const monthly: Record = {}; @@ -47,5 +49,5 @@ export async function GET() { const monthlyChart = Object.entries(monthly).map(([month, revenue]) => ({ month, revenue })); - return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, monthlyChart }); + return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, activeSubscribers, monthlyChart }); } diff --git a/app/page.tsx b/app/page.tsx index bbce250..633b647 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -18,7 +18,7 @@ const techStack = [ const CHECKLIST_MAP: Record = { '로또 번호 추천': [ - '구독 플랜 선택 (기본 / 프리미엄 / 연간)', + '구독 플랜 선택 (골드 900원 / 플래티넘 2,900원 / 다이아 9,900원)', '번호 수신 방법 (이메일 / 텔레그램)', '당첨 보장 없음 — 통계 기반 확률 최적화', '구독 취소는 이메일로 언제든 가능', @@ -160,7 +160,7 @@ export default function Home() {

출현 빈도, 패턴 분석, 핫/콜드 번호 알고리즘으로 매주 최적의 번호 조합을 제공합니다.

- {['출현 빈도 통계 분석', '핫넘버 / 콜드넘버', '매주 5개 조합 제공'].map(f => ( + {['몬테카를로 시뮬레이션 분석', '핫넘버 / 콜드넘버 통계', '골드/플래티넘/다이아 플랜'].map(f => (
{f} @@ -169,7 +169,7 @@ export default function Home() {
- 월 4,900원~ + 월 900원~ 구독형
자세히 보기 →