diff --git a/app/api/packs/list-mine/route.ts b/app/api/packs/list-mine/route.ts new file mode 100644 index 0000000..b9602e2 --- /dev/null +++ b/app/api/packs/list-mine/route.ts @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; +import { createServerClient as createSSRClient } from '@supabase/ssr'; +import { createAdminClient } from '@/lib/supabase/admin'; +import { extractPackTier, type PackTier } from '@/lib/pack-assets'; +import { tierIncludes, getPackFilesForTiers } from '@/lib/supabase/pack-files'; + +export const runtime = 'nodejs'; + +export async function GET() { + const cookieStore = await cookies(); + const supabase = createSSRClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + getAll: () => cookieStore.getAll(), + setAll: () => {}, + }, + }, + ); + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return NextResponse.json({ files: [] }); + + const admin = createAdminClient(); + const { data: orders } = await admin + .from('contact_requests') + .select('service, status') + .eq('user_id', user.id) + .eq('status', 'completed'); + + const tiers = new Set(); + for (const o of (orders ?? [])) { + const t = extractPackTier(o.service); + if (t) tierIncludes(t).forEach((x) => tiers.add(x)); + } + + const files = await getPackFilesForTiers(admin, Array.from(tiers)); + return NextResponse.json({ files }); +} diff --git a/app/mypage/page.tsx b/app/mypage/page.tsx index eda9f27..d9eec89 100644 --- a/app/mypage/page.tsx +++ b/app/mypage/page.tsx @@ -6,7 +6,8 @@ import Link from 'next/link'; import { createClient } from '@/lib/supabase/client'; import type { User } from '@supabase/supabase-js'; import TelegramGuideModal from '@/app/components/TelegramGuideModal'; -import { PACK_ASSETS, extractPackTier, type PackTier } from '@/lib/pack-assets'; +import { PACK_TIER_NAMES, extractPackTier, type PackTier } from '@/lib/pack-assets'; +import type { PackFile } from '@/lib/supabase/pack-files'; function buildSajuResultUrl(rec: SajuRecord) { const { birth_year, birth_month, birth_day, birth_hour, gender } = rec.saju_data; @@ -92,6 +93,8 @@ export default function MyPage() { const [linkToken, setLinkToken] = useState(''); const [linking, setLinking] = useState(false); const [linkMessage, setLinkMessage] = useState(''); + const [packFiles, setPackFiles] = useState([]); + const [downloading, setDownloading] = useState(null); // 텔레그램 연동 상태 const [telegramChatId, setTelegramChatId] = useState(null); @@ -158,6 +161,13 @@ export default function MyPage() { setProjects(projData.projects ?? []); } + // 구매한 팩 자료 파일 조회 + const filesRes = await fetch('/api/packs/list-mine'); + if (filesRes.ok) { + const { files } = await filesRes.json(); + setPackFiles(files ?? []); + } + setLoading(false); } init(); @@ -240,6 +250,26 @@ export default function MyPage() { setTelegramLinkState('idle'); }; + async function handleDownload(fileId: string) { + setDownloading(fileId); + try { + const res = await fetch('/api/packs/sign-link', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileId }), + }); + const data = await res.json(); + if (!res.ok || !data.url) { + throw new Error(data.error ?? '링크 발급 실패'); + } + window.location.href = data.url; + } catch (e) { + alert(e instanceof Error ? e.message : '다운로드 준비 중 오류가 발생했습니다'); + } finally { + setDownloading(null); + } + } + const handleLinkProject = async (e: React.FormEvent) => { e.preventDefault(); if (!linkToken.trim()) return; @@ -777,7 +807,6 @@ export default function MyPage() { /> ) : ( packOrders.map(({ order, tier }) => { - const asset = PACK_ASSETS[tier]; const statusLabel = order.status === 'completed' ? '자료 발송 완료' : order.status === 'in_progress' ? '결제 처리 중' : @@ -791,7 +820,7 @@ export default function MyPage() {
-
{asset.name}
+
{PACK_TIER_NAMES[tier]}
{new Date(order.created_at).toLocaleDateString('ko-KR')} 신청
@@ -801,38 +830,65 @@ export default function MyPage() {
-
-
- 📦 자료 패키지 ({asset.files.length}개) -
-
    - {asset.files.map((file, i) => ( -
  • - · - {file} -
  • - ))} -
+ {/* 자료 리스트 — DB가 SSOT */} + {(() => { + const filesForTier = packFiles.filter((pf) => { + if (tier === 'starter') return pf.min_tier === 'starter'; + if (tier === 'pro') return pf.min_tier === 'starter' || pf.min_tier === 'pro'; + return true; // master + }); - -

- 현재는 카톡 1:1로 자료를 보내드립니다. 자동 다운로드는 곧 활성화됩니다. -
- - 카톡 오픈채팅 → - -

-
+ return ( +
+
+ 📦 자료 패키지 ({filesForTier.length}개) +
+ {filesForTier.length === 0 ? ( +

자료 준비 중. 카톡 1:1로 문의해주세요.

+ ) : ( +
    + {filesForTier.map((f) => ( +
  • + {f.label} + {order.status === 'completed' ? ( + + ) : ( + 대기 중 + )} +
  • + ))} +
+ )} + + {order.status === 'completed' && filesForTier.length > 0 && ( +

+ ※ 다운로드 링크는 4시간 동안 유효합니다. +

+ )} + + {order.status !== 'completed' && ( +

+ {order.status === 'in_progress' ? '결제 처리 중. 자료는 결제 확인 후 활성화됩니다.' : '입금 대기 중. 카톡 1:1로 안내드립니다.'} +
+ + 카톡 오픈채팅 → + +

+ )} +
+ ); + })()}
); })