"use client"; import React, { useState } from "react"; import { Button } from "@/components/ui/Button"; import { Download, Copy, ChevronDown, ChevronUp } from "lucide-react"; import { downloadImage, copyToClipboard } from "@/lib/utils/export"; import { getImageUrl, getImageUrlFallback, formatRelativeDate } from "@/lib/utils/formatters"; import { toast } from "react-hot-toast"; import type { GenerateResponse, MatrixGenerateResponse } from "@/types/api"; interface AdPreviewProps { ad: GenerateResponse | MatrixGenerateResponse; } export const AdPreview: React.FC = ({ ad }) => { const [imageErrors, setImageErrors] = useState>({}); const [isBodyStoryExpanded, setIsBodyStoryExpanded] = useState(false); // Debug: Log image details to verify uniqueness React.useEffect(() => { if (ad.images && ad.images.length > 1) { console.log(`AdPreview: Displaying ${ad.images.length} images`); console.log(`📅 Generation timestamp: ${ad.created_at || 'N/A'}`); ad.images.forEach((img, idx) => { console.log(` Image ${idx + 1}:`, { filename: img.filename, image_url: img.image_url?.substring(0, 50) + '...', seed: img.seed, }); }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ad.id, ad.images?.length, ad.created_at]); // Use stable dependencies: id, length, and timestamp const handleDownloadImage = async ( imageUrl: string | null | undefined, filename: string | null | undefined, r2Url?: string | null ) => { if (!imageUrl && !filename && !r2Url && !ad.id) { toast.error("No image URL available"); return; } try { const url = getImageUrl(imageUrl, filename, r2Url); // Use proxy endpoint with ad ID to avoid CORS issues await downloadImage(url, filename || `ad-${ad.id}.png`, ad.id); toast.success("Image downloaded"); } catch (error) { toast.error("Failed to download image"); } }; const handleCopyText = async (text: string, label: string) => { try { await copyToClipboard(text); toast.success(`${label} copied`); } catch (error) { toast.error("Failed to copy"); } }; const handleImageError = (index: number, image: { image_url?: string | null; filename?: string | null; r2_url?: string | null }) => { if (!imageErrors[index]) { const { fallback } = getImageUrlFallback(image.image_url, image.filename, image.r2_url); if (fallback) { setImageErrors((prev) => ({ ...prev, [index]: true })); } } }; return (
{/* Images */} {ad.images && ad.images.length > 0 && (
{ad.images.length > 1 && (

Images ({ad.images.length})

{ad.created_at && ( Generated {formatRelativeDate(ad.created_at)} )}
)} {ad.images.length === 1 ? ( // Single image - full width with better styling
{(() => { const image = ad.images[0]; const { primary, fallback } = getImageUrlFallback(image.image_url, image.filename, image.r2_url); const imageUrl = imageErrors[0] ? fallback : (primary || fallback); return imageUrl ? (
{ad.headline handleImageError(0, image)} /> {ad.created_at && (
{formatRelativeDate(ad.created_at)}
)}
Download Image
) : (

No image

); })()} {ad.images[0].error && (

Error: {ad.images[0].error}

)}
) : ( // Multiple images - grid layout
{ad.images.map((image, index) => { const { primary, fallback } = getImageUrlFallback(image.image_url, image.filename, image.r2_url); const imageUrl = imageErrors[index] ? fallback : (primary || fallback); // Use unique key based on filename or URL to prevent duplicate rendering const uniqueKey = image.filename || image.image_url || `image-${index}`; return (
{imageUrl ? (
{`Ad handleImageError(index, image)} /> {/* Image number badge */}
Image {index + 1}
Download
) : (

No image

)} {image.error && (

Error: {image.error}

)} {/* Image metadata footer */}
{image.seed && `Seed: ${image.seed}`} {image.filename && ( {image.filename.split('_').pop()?.split('.')[0]} )}
); })}
)}
)} {/* Ad Copy Section */}
{/* Left Column - Main Copy */}
{/* Title */} {ad.title && (

Title

Copy Title

{ad.title}

)} {/* Description */} {ad.description && (

Description

Copy Description

{ad.description}

)} {/* Body Story */} {ad.body_story && (

Body Story

Copy Story

{ad.body_story}

{ad.body_story.split('\n').length > 3 || ad.body_story.length > 200 ? ( ) : null}
)}
{/* Right Column - CTA and Strategy */}
{/* CTA */} {ad.cta && (

Call to Action

Copy CTA

{ad.cta}

)} {/* Psychological Angle */}

🧠 Psychological Angle

Copy Angle

{ad.psychological_angle}

{ad.why_it_works && (

💡 Why It Works

{ad.why_it_works}

)}
{/* Matrix Details */} {"matrix" in ad && ad.matrix && (

Matrix Details

Angle

{ad.matrix.angle.name}

{ad.matrix.angle.trigger}

Concept

{ad.matrix.concept.name}

{ad.matrix.concept.structure}

)}
); };