'use client' import { useState, useEffect, useRef } from 'react' import type { CatalogProduct } from '@/types/products' import type { MockupTemplate } from '@/types/mockup' interface Props { product: CatalogProduct onClose: () => void } interface Metafield { namespace: string key: string value: string type: string } async function urlToBase64(url: string): Promise { const res = await fetch(`/api/images/proxy?url=${encodeURIComponent(url)}`) if (!res.ok) throw new Error('Impossible de charger l\'image') const blob = await res.blob() return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve((reader.result as string).split(',')[1]) reader.onerror = reject reader.readAsDataURL(blob) }) } export function MockupModal({ product, onClose }: Props) { const [templates, setTemplates] = useState([]) const [families, setFamilies] = useState([]) const [metafields, setMetafields] = useState([]) const [loadingMeta, setLoadingMeta] = useState(true) const [selectedTemplate, setSelectedTemplate] = useState(null) const [family, setFamily] = useState('') const [placement, setPlacement] = useState('') const [selectedMeta, setSelectedMeta] = useState([]) const [cutoutB64, setCutoutB64] = useState(null) const [selectedImageUrl, setSelectedImageUrl] = useState(null) const [loadingImage, setLoadingImage] = useState(false) const [instruction, setInstruction] = useState('') const [generating, setGenerating] = useState(false) const [resultB64, setResultB64] = useState(null) const [uploading, setUploading] = useState(false) const [uploadedUrl, setUploadedUrl] = useState(null) const [error, setError] = useState(null) const fileRef = useRef(null) const templateFileRef = useRef(null) const [uploadingTemplate, setUploadingTemplate] = useState(false) useEffect(() => { Promise.all([ fetch('/api/mockup/templates').then(r => r.json()), fetch('/api/sheets/tabs').then(r => r.json()), fetch(`/api/mockup/product-meta/${product.shopifyId}`).then(r => r.json()), product.ref ? fetch(`/api/products/sheet-info?ref=${encodeURIComponent(product.ref)}`).then(r => r.ok ? r.json() : null) : Promise.resolve(null), ]).then(([tplData, tabsData, metaData, sheetData]) => { setTemplates(tplData as MockupTemplate[]) const tabs: string[] = (tabsData.tabs ?? []).filter((t: string) => !['COLLECTIONS', 'LIVRAISONS', 'ECOPART', 'RECAP'].some(x => t.toUpperCase().includes(x)) ) setFamilies(tabs) const sheetFamille = sheetData?.product?.famille setFamily(sheetFamille && tabs.includes(sheetFamille) ? sheetFamille : tabs[0] ?? '') setMetafields(metaData.metafields ?? []) }).finally(() => setLoadingMeta(false)) }, [product.shopifyId, product.ref]) const isCarrelage = family.toLowerCase().includes('carrelage') const filteredTemplates = templates.filter(t => { if (t.family !== family && t.family !== 'Tous') return false if (isCarrelage && placement && t.placement && t.placement !== placement) return false return true }) const dimensionStr = selectedMeta.length > 0 ? selectedMeta.map(key => { const mf = metafields.find(m => `${m.namespace}.${m.key}` === key) return mf ? `${mf.key}: ${mf.value}` : '' }).filter(Boolean).join(', ') : undefined const selectExistingImage = async (url: string) => { setSelectedImageUrl(url) setCutoutB64(null) setLoadingImage(true) setError(null) try { const b64 = await urlToBase64(url) setCutoutB64(b64) } catch { setError('Impossible de charger cette image') setSelectedImageUrl(null) } finally { setLoadingImage(false) } } const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setSelectedImageUrl(null) const reader = new FileReader() reader.onload = ev => setCutoutB64((ev.target?.result as string).split(',')[1]) reader.readAsDataURL(file) } const uploadTemplate = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setUploadingTemplate(true) const form = new FormData() form.append('name', file.name.replace(/\.[^.]+$/, '')) form.append('family', family || 'Tous') form.append('description', '') form.append('image', file) const res = await fetch('/api/mockup/templates', { method: 'POST', body: form }) const tpl = await res.json() as MockupTemplate setTemplates(prev => [...prev, tpl]) setSelectedTemplate(tpl.id) setUploadingTemplate(false) } const toggleMeta = (key: string) => { setSelectedMeta(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key]) } const generate = async () => { if (!selectedTemplate || !cutoutB64) return setGenerating(true) setError(null) setResultB64(null) try { const res = await fetch('/api/mockup/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: selectedTemplate, productImageBase64: cutoutB64, productName: product.title, family: family || 'Tous', placement: placement || undefined, dimensions: dimensionStr, instruction: instruction || undefined, }), }) const data = await res.json() if (!res.ok) throw new Error(data.error) setResultB64(data.imageBase64) } catch (e) { setError(e instanceof Error ? e.message : 'Erreur') } finally { setGenerating(false) } } const upload = async () => { if (!resultB64) return setUploading(true) try { const res = await fetch('/api/images/upload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ shopifyProductId: product.shopifyId, imageBase64: resultB64, filename: `${product.ref}-mockup.jpg` }), }) const data = await res.json() if (!res.ok) throw new Error(data.error) setUploadedUrl(data.url) } catch (e) { setError(e instanceof Error ? e.message : 'Erreur upload') } finally { setUploading(false) } } return (
{ if (e.target === e.currentTarget) onClose() }}>
{/* Header */}

Créer un mockup

{product.title} {product.ref}

{/* 1. Image de base */}

1. Image du produit

{product.images.length > 0 && (

Images existantes

{product.images.map((url, i) => ( ))}
)}

Ou uploader une image découpée (PNG)

{cutoutB64 && !loadingImage && (

✓ Image prête

)}
{/* 2. Famille + placement */}

2. Famille

{families.length > 0 ? (
{isCarrelage && (

Placement

{[{ val: 'sol', label: 'Sol' }, { val: 'mur', label: 'Mur (faïence)' }].map(({ val, label }) => ( ))}
)}
) : (

{loadingMeta ? 'Chargement…' : 'Aucune famille disponible'}

)}
{/* 3. Caractéristiques (metafields) */} {metafields.length > 0 && (

3. Caractéristiques à inclure dans le prompt

{metafields.map(mf => { const key = `${mf.namespace}.${mf.key}` return ( ) })}
{dimensionStr && (

Inclus dans le prompt : {dimensionStr}

)}
)} {/* 4. Template */}

{metafields.length > 0 ? '4.' : '3.'} Image de référence (template)

{filteredTemplates.length === 0 ? (

Aucun template pour cette famille. Uploadez-en un ci-dessus.

) : (
{filteredTemplates.map(t => ( ))}
)}
{/* Generate */} {error &&

{error}

} {/* Result */} {resultB64 && (
Mockup
setInstruction(e.target.value)} placeholder="Instruction pour regénérer (optionnel)…" className="flex-1 bg-slate-700 border border-slate-600 rounded px-2 py-1.5 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500" />
{uploadedUrl ? (

✓ Uploadé sur Shopify

) : ( )}
)}
) }