diff --git a/app/api/studio/tracks/route.ts b/app/api/studio/tracks/route.ts index 9688ea1..b200993 100644 --- a/app/api/studio/tracks/route.ts +++ b/app/api/studio/tracks/route.ts @@ -41,7 +41,7 @@ export async function GET() { // 세션 클라이언트로 본인 것만(RLS music_select_own) const { data, error } = await supabase .from('music_tracks') - .select('id, title, story, lyrics, style, audio_url, task_id, created_at') + .select('id, title, story, lyrics, style, audio_url, task_id, created_at, video_status, video_url') .order('created_at', { ascending: false }); if (error) return NextResponse.json({ error: error.message }, { status: 500 }); diff --git a/app/components/BankTransferModal.tsx b/app/components/BankTransferModal.tsx index 331fa5d..2600a8a 100644 --- a/app/components/BankTransferModal.tsx +++ b/app/components/BankTransferModal.tsx @@ -19,6 +19,7 @@ interface Props { product: { id: string; name: string; price: number }; isOpen: boolean; onClose: () => void; + musicTrackId?: string; } type AuthState = 'checking' | 'guest' | 'user'; @@ -29,7 +30,7 @@ interface SuccessInfo { reused: boolean; } -export default function BankTransferModal({ product, isOpen, onClose }: Props) { +export default function BankTransferModal({ product, isOpen, onClose, musicTrackId }: Props) { const [authState, setAuthState] = useState('checking'); const [depositorName, setDepositorName] = useState(''); const [agreed, setAgreed] = useState(false); @@ -98,7 +99,11 @@ export default function BankTransferModal({ product, isOpen, onClose }: Props) { const res = await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ productId: product.id, depositorName: name }), + body: JSON.stringify({ + productId: product.id, + depositorName: name, + ...(musicTrackId ? { musicTrackId } : {}), + }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { @@ -122,7 +127,7 @@ export default function BankTransferModal({ product, isOpen, onClose }: Props) { setSubmitting(false); } }, - [depositorName, agreed, submitting, product.id], + [depositorName, agreed, submitting, product.id, musicTrackId], ); if (!isOpen) return null; diff --git a/app/mypage/page.tsx b/app/mypage/page.tsx index bb5eff9..f62328a 100644 --- a/app/mypage/page.tsx +++ b/app/mypage/page.tsx @@ -6,6 +6,7 @@ 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 BankTransferModal from '@/app/components/BankTransferModal'; import { KAKAO_OPENCHAT_URL } from '@/lib/contact'; import { findCard } from '@/lib/tarot/cards'; import { @@ -75,6 +76,8 @@ type MusicTrackRow = { story: string | null; audio_url: string | null; created_at: string; + video_status?: 'none' | 'requested' | 'in_progress' | 'delivered'; + video_url?: string | null; }; // AI 기록 탭 — 사주·타로·음악 병합 렌더용 판별 유니언 @@ -167,6 +170,9 @@ function MyPageContent() { const [sajuRecords, setSajuRecords] = useState([]); const [musicTracks, setMusicTracks] = useState([]); const [expandedAiCards, setExpandedAiCards] = useState>(new Set()); + // 음악 영상화 신청 — 계좌이체 모달에 전달할 상품·대상 트랙 + const [videoProduct, setVideoProduct] = useState<{ id: string; name: string; price: number } | null>(null); + const [videoModalTrackId, setVideoModalTrackId] = useState(null); const loadProjects = useCallback(async () => { try { @@ -202,6 +208,23 @@ function MyPageContent() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // 음악 트랙 영상화 신청 — 상품 정보 로드 후 계좌이체 모달 오픈 + const handleRequestVideo = useCallback(async (trackId: string) => { + try { + const res = await fetch('/api/studio/video-product'); + if (!res.ok) { + console.warn('영상화 상품을 불러오지 못했습니다', res.status); + return; + } + const data = await res.json(); + if (!data?.product) return; + setVideoProduct(data.product); + setVideoModalTrackId(trackId); + } catch (e) { + console.warn('영상화 상품 조회 중 오류', e); + } + }, []); + useEffect(() => { async function init() { const { data: { user } } = await supabase.auth.getUser(); @@ -965,7 +988,11 @@ function MyPageContent() { onToggle={() => toggleAiCard(item.data.id)} /> ) : ( - + ) )} @@ -973,6 +1000,17 @@ function MyPageContent() { )} + + {/* 음악 영상화 신청 — 계좌이체 모달 (영상화 상품 로드 완료 시에만 오픈) */} + { + setVideoModalTrackId(null); + loadAiRecords(); + }} + /> ); } @@ -1594,8 +1632,14 @@ function TarotAiCard({ ); } -// AI 기록 — 음악 카드. 제목·날짜 + 스토리 요약 + 오디오 플레이어(생성 완료 시). -function MusicAiCard({ track }: { track: MusicTrackRow }) { +// AI 기록 — 음악 카드. 제목·날짜 + 스토리 요약 + 오디오 플레이어(생성 완료 시) + 영상화 신청/상태. +function MusicAiCard({ + track, + onRequestVideo, +}: { + track: MusicTrackRow; + onRequestVideo: (trackId: string) => void; +}) { return (
@@ -1624,14 +1668,68 @@ function MusicAiCard({ track }: { track: MusicTrackRow }) { )} {track.audio_url ? ( -