// Creart Checkout — shared modal that asks for customer info, saves the // order to local storage, and then opens WhatsApp with the full order details. // Exposes: window.openCheckout(payload) -> Promise (() => { const { useState, useEffect, useRef } = React; const WA_DEFAULT = '+50431969913'; // Mount point let mountEl = document.getElementById('__checkout-root'); if (!mountEl) { mountEl = document.createElement('div'); mountEl.id = '__checkout-root'; document.body.appendChild(mountEl); } // External hooks for imperative open let resolver = null; let openExt = null; const L = { es: { title: 'CONFIRMÁ TU PEDIDO', sub: 'Necesitamos tus datos y dirección para que te llegue la pieza.', step_info: 'TUS DATOS', step_review: 'REVISÁ', name: 'Nombre completo', name_ph: 'Ej: María López', phone: 'Teléfono / WhatsApp', phone_ph: 'Ej: 9876 5432', addr: 'Dirección (calle, # casa, colonia)', addr_ph: 'Ej: Calle Las Rosas, casa 24, Col. Trejo', city: 'Ciudad / Depto.', city_ph: 'Ej: San Pedro Sula', notes: 'Notas (opcional)', notes_ph: 'Detalles, referencia, hora preferida...', cancel: 'CANCELAR', back: '← ATRÁS', next: 'SIGUIENTE →', send: 'CONFIRMAR Y MANDAR A WHATSAPP', req: 'Requerido', summary: 'RESUMEN', order_no: 'Pedido', product: 'Producto', price: 'Precio', ship_to: 'Enviar a', legal: 'Al confirmar, guardamos tu pedido y abrimos WhatsApp con todos los detalles para coordinar el envío.', remember: 'Recordar mis datos en este dispositivo', }, en: { title: 'CONFIRM YOUR ORDER', sub: 'We need your info and address so the piece reaches you.', step_info: 'YOUR INFO', step_review: 'REVIEW', name: 'Full name', name_ph: 'e.g. Maria Lopez', phone: 'Phone / WhatsApp', phone_ph: 'e.g. 9876 5432', addr: 'Address (street, # house, neighborhood)', addr_ph: 'e.g. Calle Las Rosas, house 24, Col. Trejo', city: 'City / State', city_ph: 'e.g. San Pedro Sula', notes: 'Notes (optional)', notes_ph: 'Details, reference, preferred time...', cancel: 'CANCEL', back: '← BACK', next: 'NEXT →', send: 'CONFIRM AND SEND TO WHATSAPP', req: 'Required', summary: 'SUMMARY', order_no: 'Order', product: 'Product', price: 'Price', ship_to: 'Ship to', legal: 'Confirming will save your order and open WhatsApp with all the details to coordinate shipping.', remember: 'Remember my info on this device', }, }; const Field = ({ label, required, children, error }) => ( ); const baseInput = { width: '100%', background: 'var(--bg)', color: 'var(--ink)', border: '2.5px solid var(--ink)', padding: '11px 12px', fontFamily: 'var(--fbody)', fontSize: 14, outline: 'none', boxShadow: '3px 3px 0 var(--ink)', }; const CheckoutModal = () => { const [open, setOpen] = useState(false); const [payload, setPayload] = useState(null); const [step, setStep] = useState(1); const [name, setName] = useState(''); const [phone, setPhone] = useState(''); const [addr, setAddr] = useState(''); const [city, setCity] = useState('San Pedro Sula'); const [notes, setNotes] = useState(''); const [remember, setRemember] = useState(true); const [touched, setTouched] = useState({}); const [sending, setSending] = useState(false); // Expose imperative opener openExt = (p) => { const saved = window.CreartOrders.getClient(); setName(saved.name || ''); setPhone(saved.phone || ''); setAddr(saved.addr || ''); setCity(saved.city || 'San Pedro Sula'); setNotes(''); setTouched({}); setStep(1); setPayload(p); setOpen(true); }; useEffect(() => { const onKey = (e) => { if (e.key === 'Escape' && open) cancel(); }; document.addEventListener('keydown', onKey); if (open) document.body.style.overflow = 'hidden'; else document.body.style.overflow = ''; return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = ''; }; }, [open]); if (!open || !payload) return null; const lang = payload.lang || 'es'; const t = L[lang]; const validInfo = name.trim().length >= 2 && phone.trim().length >= 6 && addr.trim().length >= 4; const close = (orderResult) => { if (resolver) { resolver(orderResult); resolver = null; } setOpen(false); setPayload(null); }; const cancel = () => close(null); const onNext = () => { setTouched({ name: true, phone: true, addr: true }); if (validInfo) setStep(2); }; const submit = async () => { if (!validInfo) { setStep(1); setTouched({ name: true, phone: true, addr: true }); return; } if (remember) { window.CreartOrders.setClient({ name: name.trim(), phone: phone.trim(), addr: addr.trim(), city: city.trim() }); } // ── Subir diseño a Supabase Storage si es base64 ───────────────────── setSending(true); let designImageUrl = payload.designImage || null; let designName = payload.designName || null; if ( payload.designImage && payload.designImage.startsWith('data:') && window.supabase && window.CREART_CONFIG?.SUPABASE_URL && !window.CREART_CONFIG.SUPABASE_URL.includes('TU_PROYECTO') ) { try { const cfg = window.CREART_CONFIG; if (!window.CreartDB) window.CreartDB = window.supabase.createClient(cfg.SUPABASE_URL, cfg.SUPABASE_ANON_KEY); const sbUp = window.CreartDB; const ext = payload.designImage.split(';')[0].split('/')[1] || 'png'; const tempId = `upload-${Date.now()}-${Math.floor(Math.random() * 9999)}`; const fileName = `${tempId}/diseno.${ext}`; // Convertir base64 → Blob const fetchRes = await fetch(payload.designImage); const blob = await fetchRes.blob(); const { error: uploadError } = await sbUp.storage .from('designs') .upload(fileName, blob, { contentType: blob.type, upsert: false }); if (!uploadError) { const { data: urlData } = sbUp.storage.from('designs').getPublicUrl(fileName); designImageUrl = urlData.publicUrl; designName = payload.designName || `diseno.${ext}`; } else { console.warn('[Checkout] upload error, pedido se guarda sin imagen:', uploadError); designImageUrl = null; designName = payload.designName || null; } } catch (uploadErr) { console.warn('[Checkout] fallo al subir, pedido se guarda sin imagen:', uploadErr); designImageUrl = null; designName = payload.designName || null; } } else if (payload.designImage?.startsWith('data:')) { // Sin Supabase configurado o supabase.js no cargado → no guardar base64 en DB designImageUrl = null; } let order; try { order = await window.CreartOrders.add({ customer: { name: name.trim(), phone: phone.trim(), addr: addr.trim(), city: city.trim() }, notes: notes.trim() || null, product: payload.product || null, options: payload.options || null, designImage: designImageUrl, designName: designName, source: payload.source || 'web', lang, }); } catch (addErr) { console.error('[Checkout] error al guardar pedido:', addErr); setSending(false); alert(lang === 'es' ? 'Hubo un error al guardar el pedido. Intentá de nuevo o contactanos por WhatsApp.' : 'There was an error saving your order. Please try again or contact us via WhatsApp.'); return; } setSending(false); // Build WA message with full order details const lines = []; lines.push(lang === 'es' ? '¡Yo! Quiero hacer este pedido:' : 'Yo! I want this order:'); lines.push(''); lines.push(`#${order.id}`); if (payload.product?.name) lines.push(`${lang === 'es' ? 'Producto' : 'Product'}: ${payload.product.name}`); if (payload.product?.sku) lines.push(`SKU: ${payload.product.sku}`); if (payload.product?.price) lines.push(`${lang === 'es' ? 'Precio' : 'Price'}: ${payload.product.price}`); if (payload.options) { Object.entries(payload.options).forEach(([k, v]) => { if (v) lines.push(`${k}: ${v}`); }); } lines.push(''); lines.push(lang === 'es' ? '— Cliente —' : '— Customer —'); lines.push(name.trim()); lines.push(phone.trim()); lines.push(`${addr.trim()}, ${city.trim()}`); if (notes.trim()) lines.push(`${lang === 'es' ? 'Notas' : 'Notes'}: ${notes.trim()}`); if (payload.designImage) { lines.push(''); lines.push(lang === 'es' ? '(Te paso el diseño por acá enseguida ↓)' : '(Sending the design here right after ↓)'); } const msg = lines.join('\n'); const wa = (payload.wa || WA_DEFAULT).replace(/\D/g, ''); const waUrl = `https://wa.me/${wa}?text=${encodeURIComponent(msg)}`; // Open WA window.open(waUrl, '_blank', 'noopener'); close(order); }; const sticker = (text, color, rot) => ( {text} ); const renderProductBlurb = () => { if (!payload.product) return null; return (
{(payload.product.img || payload.designImage) && (
)}
{payload.product.name}
{payload.product.sku ? `${payload.product.sku} · ` : ''}{payload.product.price}
{payload.options && (
{Object.entries(payload.options).filter(([_, v]) => v).map(([k, v]) => `${k}: ${v}`).join(' · ')}
)}
); }; return (
e.stopPropagation()} style={{ background: 'var(--paper)', border: '4px solid var(--ink)', boxShadow: '10px 10px 0 var(--red)', maxWidth: 560, width: '100%', maxHeight: '92vh', overflowY: 'auto', position: 'relative', animation: 'coIn 0.22s ease both', }}> {/* Tape strips */}
{step}/2 · {step === 1 ? t.step_info : t.step_review}
{sticker(`#${(payload.product?.sku || 'CUSTOM')}`, 'var(--yellow)', '-4deg')}

{t.title}

{t.sub}

{renderProductBlurb()} {step === 1 && (
setName(e.target.value)} onBlur={() => setTouched(s => ({ ...s, name: true }))} placeholder={t.name_ph} style={baseInput} /> setPhone(e.target.value)} onBlur={() => setTouched(s => ({ ...s, phone: true }))} placeholder={t.phone_ph} style={baseInput} type="tel" />