import { useState, useEffect } from 'react'; import { Box, Container, Typography, Card, CardContent, Button, Radio, RadioGroup, FormControlLabel, IconButton, Dialog, Grid, Alert, } from '@mui/material'; import { ArrowBack, ZoomIn, Psychology, Refresh } from '@mui/icons-material'; import { contentApi, ImageInfo } from '@/shared/api/contentApi'; import Image from 'next/image'; // 디자인 시스템 색상 const colors = { pink: '#F472B6', purple: '#C084FC', purpleLight: '#E9D5FF', blue: '#60A5FA', mint: '#34D399', orange: '#FB923C', yellow: '#FBBF24', gray: { 900: '#1A1A1A', 700: '#4A4A4A', 500: '#9E9E9E', 300: '#D9D9D9', 100: '#F5F5F5', }, }; interface ImageStyle { id: 'SIMPLE' | 'FANCY' | 'TRENDY'; name: string; gradient?: string; icon: string; textColor?: string; } const imageStyles: ImageStyle[] = [ { id: 'SIMPLE', name: '스타일 1: 심플', icon: 'celebration', }, { id: 'FANCY', name: '스타일 2: 화려', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', icon: 'auto_awesome', textColor: 'white', }, { id: 'TRENDY', name: '스타일 3: 트렌디', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', icon: 'trending_up', textColor: 'white', }, ]; interface ContentPreviewStepProps { eventId?: string; eventTitle?: string; eventDescription?: string; onNext: (imageStyle: string, images: ImageInfo[]) => void; onSkip: () => void; onBack: () => void; } interface EventCreationData { eventDraftId: string; eventTitle: string; eventDescription: string; industry: string; location: string; trends: string[]; prize: string; } export default function ContentPreviewStep({ eventId: propsEventId, eventTitle: propsEventTitle, eventDescription: propsEventDescription, onNext, onSkip, onBack, }: ContentPreviewStepProps) { const [loading, setLoading] = useState(true); const [selectedStyle, setSelectedStyle] = useState<'SIMPLE' | 'FANCY' | 'TRENDY' | null>(null); const [fullscreenOpen, setFullscreenOpen] = useState(false); const [fullscreenImage, setFullscreenImage] = useState(null); const [generatedImages, setGeneratedImages] = useState>(new Map()); const [error, setError] = useState(null); const [loadingProgress, setLoadingProgress] = useState(0); const [loadingMessage, setLoadingMessage] = useState('이미지 생성 요청 중...'); const [eventData, setEventData] = useState(null); useEffect(() => { // localStorage에서 이벤트 데이터 읽기 const storedData = localStorage.getItem('eventCreationData'); if (storedData) { const data: EventCreationData = JSON.parse(storedData); setEventData(data); // 먼저 이미지 조회 시도 loadImages(data).then((hasImages) => { // 이미지가 없으면 자동으로 생성 if (!hasImages) { console.log('📸 이미지가 없습니다. 자동으로 생성을 시작합니다...'); handleGenerateImagesAuto(data); } }); } else if (propsEventId) { // Props에서 받은 이벤트 데이터 사용 (localStorage 없을 때만) console.log('✅ Using event data from props:', propsEventId); const data: EventCreationData = { eventDraftId: propsEventId, eventTitle: propsEventTitle || '', eventDescription: propsEventDescription || '', industry: '', location: '', trends: [], prize: '', }; setEventData(data); // 이미지 조회 시도 loadImages(data).then((hasImages) => { if (!hasImages) { console.log('📸 이미지가 없습니다. 자동으로 생성을 시작합니다...'); handleGenerateImagesAuto(data); } }); } else { // 이벤트 데이터가 없으면 에러 표시 console.error('❌ No event data available. Cannot proceed.'); setError('이벤트 정보를 찾을 수 없습니다. 이전 단계로 돌아가 주세요.'); setLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsEventId, propsEventTitle, propsEventDescription]); const loadImages = async (data: EventCreationData): Promise => { try { setError(null); console.log('📥 Loading images for event:', data.eventDraftId); const images = await contentApi.getImages(data.eventDraftId); console.log('✅ Images loaded from API:', images.length, images); if (!images || images.length === 0) { console.warn('⚠️ No images found.'); return false; // 이미지 없음 } const imageMap = new Map(); // 각 스타일별로 가장 최신 이미지만 선택 (createdAt 기준) images.forEach((image, index) => { console.log(`📸 Processing image ${index + 1}:`, { id: image.id, eventId: image.eventId, style: image.style, platform: image.platform, cdnUrl: image.cdnUrl?.substring(0, 50) + '...', createdAt: image.createdAt, }); if (image.platform === 'INSTAGRAM') { const existing = imageMap.get(image.style); if (!existing || new Date(image.createdAt) > new Date(existing.createdAt)) { console.log(` ✅ Selected as latest ${image.style} image`); imageMap.set(image.style, image); } else { console.log(` ⏭️ Skipped (older than existing ${image.style} image)`); } } else { console.log(` ⏭️ Skipped (platform: ${image.platform})`); } }); console.log('🎨 Image map created with entries:', { SIMPLE: imageMap.has('SIMPLE') ? 'YES ✅' : 'NO ❌', FANCY: imageMap.has('FANCY') ? 'YES ✅' : 'NO ❌', TRENDY: imageMap.has('TRENDY') ? 'YES ✅' : 'NO ❌', totalSize: imageMap.size, }); console.log('🖼️ Image map details:', Array.from(imageMap.entries()).map(([style, img]) => ({ style, id: img.id, eventId: img.eventId, cdnUrl: img.cdnUrl?.substring(0, 60) + '...', }))); setGeneratedImages(imageMap); console.log('✅ Images loaded successfully!'); return true; // 이미지 있음 } catch (err) { console.error('❌ Load images error:', err); // API 에러는 polling에서 무시 (계속 시도) return false; } }; const handleStyleSelect = (styleId: 'SIMPLE' | 'FANCY' | 'TRENDY') => { setSelectedStyle(styleId); }; const handlePreview = (image: ImageInfo, e: React.MouseEvent) => { e.stopPropagation(); setFullscreenImage(image); setFullscreenOpen(true); }; const handleNext = () => { if (selectedStyle) { const allImages = Array.from(generatedImages.values()); onNext(selectedStyle, allImages); } }; const handleGenerateImagesAuto = async (data: EventCreationData) => { try { setLoading(true); setError(null); setLoadingProgress(0); setLoadingMessage('이미지 생성 요청 중...'); console.log('🎨 Auto-generating images for event:', data.eventDraftId); // 이미지 생성 요청 (202 Accepted 응답만 확인) await contentApi.generateImages({ eventId: data.eventDraftId, eventTitle: data.eventTitle, eventDescription: data.eventDescription, industry: data.industry, location: data.location, trends: data.trends, styles: ['SIMPLE', 'FANCY', 'TRENDY'], platforms: ['INSTAGRAM'], }); console.log('✅ Image generation request accepted (202)'); console.log('⏳ AI 이미지 생성 중... 약 60초 소요됩니다.'); setLoadingProgress(10); setLoadingMessage('AI가 이미지를 생성하고 있어요...'); // 생성 완료까지 대기 (polling) let attempts = 0; const maxAttempts = 30; // 최대 60초 (2초 * 30회) const pollImages = async () => { attempts++; console.log(`🔄 이미지 확인 시도 ${attempts}/${maxAttempts}...`); // 진행률 업데이트 (10% ~ 90%) const progress = Math.min(10 + (attempts / maxAttempts) * 80, 90); setLoadingProgress(progress); // 단계별 메시지 업데이트 if (attempts < 10) { setLoadingMessage('AI가 이미지를 생성하고 있어요...'); } else if (attempts < 20) { setLoadingMessage('스타일을 적용하고 있어요...'); } else { setLoadingMessage('거의 완료되었어요...'); } const hasImages = await loadImages(data); if (hasImages) { console.log('✅ 이미지 생성 완료!'); setLoadingProgress(100); setLoadingMessage('이미지 생성 완료!'); setTimeout(() => setLoading(false), 500); // 100% 잠깐 보여주기 } else if (attempts < maxAttempts) { // 2초 후 다시 시도 setTimeout(pollImages, 2000); } else { console.warn('⚠️ 이미지 생성 시간 초과. "이미지 재생성" 버튼을 클릭하세요.'); setError('이미지 생성이 완료되지 않았습니다. 잠시 후 "이미지 재생성" 버튼을 클릭해주세요.'); setLoading(false); } }; // 첫 번째 확인은 5초 후 시작 (생성 시작 시간 고려) setTimeout(pollImages, 5000); } catch (err) { console.error('❌ Image generation request error:', err); setError('이미지 생성 요청에 실패했습니다.'); setLoading(false); } }; const handleGenerateImages = async () => { if (!eventData) return; handleGenerateImagesAuto(eventData); }; if (loading) { return ( SNS 이미지 생성 {/* 그라데이션 스피너 */} {/* 진행률 바 */} {loadingMessage} {Math.round(loadingProgress)}% {generatedImages.size > 0 ? ( <> 생성된 이미지를 확인하고 있어요
잠시만 기다려주세요! ) : ( <> AI가 이벤트에 맞는 이미지를 생성하고 있어요
약 60초 정도 소요됩니다 )}
{error && ( {error} )}
); } return ( {/* Header */} SNS 이미지 생성 {generatedImages.size > 0 && ( ✨ 생성된 이미지를 확인하고 스타일을 선택하세요 )} 이벤트에 어울리는 스타일을 선택하세요 handleStyleSelect(e.target.value as 'SIMPLE' | 'FANCY' | 'TRENDY')}> {imageStyles.map((style) => ( handleStyleSelect(style.id)} > {/* 스타일 이름 */} {style.name} } label="" sx={{ m: 0 }} /> {/* 이미지 프리뷰 */} {(() => { const hasImage = generatedImages.has(style.id); const imageData = generatedImages.get(style.id); console.log(`🖼️ Rendering ${style.id}:`, { hasImage, imageDataExists: !!imageData, fullCdnUrl: imageData?.cdnUrl, mapSize: generatedImages.size, mapKeys: Array.from(generatedImages.keys()), }); return hasImage && imageData ? ( {style.name} console.log(`✅ ${style.id} image loaded successfully from:`, imageData.cdnUrl)} onError={(e) => { console.error(`❌ ${style.id} image load error:`, e); console.error(` Failed URL:`, imageData.cdnUrl); }} /> ) : ( {style.icon} {eventData?.eventTitle || '이벤트'} {eventData?.prize || '경품'} ); })()} {/* 크게보기 버튼 */} ))} {/* Action Buttons */} {/* Fullscreen Dialog */} setFullscreenOpen(false)} maxWidth={false} PaperProps={{ sx: { bgcolor: 'rgba(0, 0, 0, 0.95)', boxShadow: 'none', maxWidth: '90vw', maxHeight: '90vh', }, }} > setFullscreenOpen(false)} sx={{ position: 'absolute', top: 16, right: 16, bgcolor: 'rgba(255, 255, 255, 0.9)', '&:hover': { bgcolor: 'white' }, }} > close {fullscreenImage && ( {`${fullscreenImage.style} )} ); }