/* ============================================================ AfricaSpares — Cart Context (shared state) Used by: PartCard → CartPanel → CheckoutModal ============================================================ */ const CartCtx = React.createContext({ cart: [], addItem: () => {}, removeItem: () => {}, updateQty: () => {}, clearCart: () => {} }); const useCart = () => React.useContext(CartCtx); function CartProvider({ children }) { const [cart, setCart] = React.useState(() => { try { const s = localStorage.getItem("gg_as_cart"); return s ? JSON.parse(s) : []; } catch { return []; } }); const save = (items) => { setCart(items); try { localStorage.setItem("gg_as_cart", JSON.stringify(items)); } catch {} }; const addItem = (part) => { setCart(prev => { const exists = prev.find(i => i.sku === part.sku); const next = exists ? prev.map(i => i.sku === part.sku ? { ...i, qty: i.qty + 1 } : i) : [...prev, { ...part, qty: 1 }]; try { localStorage.setItem("gg_as_cart", JSON.stringify(next)); } catch {} return next; }); }; const removeItem = (sku) => save(cart.filter(i => i.sku !== sku)); const updateQty = (sku, qty) => { if (qty < 1) { removeItem(sku); return; } save(cart.map(i => i.sku === sku ? { ...i, qty } : i)); }; const clearCart = () => save([]); const total = cart.reduce((s, i) => s + i.price * i.qty, 0); return ( {children} ); } /* ─── Cart icon button (used in page header) ─────────────── */ function CartButton({ onClick }) { const { cart } = useCart(); const count = cart.reduce((s, i) => s + i.qty, 0); return ( ); } /* ─── Cart slide-in panel ────────────────────────────────── */ function CartPanel({ open, onClose, onCheckout }) { const { cart, removeItem, updateQty, total } = useCart(); if (!open) return null; return (
e.stopPropagation()} style={{ width: "min(480px,100vw)", background: "var(--paper)", display: "flex", flexDirection: "column", height: "100%" }}> {/* Header */}
{L("Your Cart", "Votre Panier")} ({cart.reduce((s,i)=>s+i.qty,0)} {L("items","articles")})
{/* Items */}
{cart.length === 0 ? (

{L("Your cart is empty", "Votre panier est vide")}

{L("Add parts from the catalogue", "Ajoutez des pièces depuis le catalogue")}

) : ( cart.map(item => (
{/* Image */}
{item.img ? {item.name} : }
{item.name}
{item.sku} · {item.fit}
{/* Qty */}
{item.qty}
{(item.price * item.qty).toLocaleString("fr-FR")} XOF
)) )}
{/* Footer */} {cart.length > 0 && (
{L("Subtotal", "Sous-total")} {total.toLocaleString("fr-FR")} XOF

{L("Shipping and taxes calculated at checkout", "Livraison et taxes calculées à la commande")}

)}
); } /* ─── Checkout modal ─────────────────────────────────────── */ const PAYMENT_METHODS = [ { id: "card", label: L("Bank card", "Carte bancaire"), icon: "💳", color: "#1A1F71", sub: "Visa · Mastercard · GIM-UEMOA", full: true }, { id: "orange_money", label: "Orange Money", icon: "📱", color: "#FF7900", sub: "+227 XX XX XX XX" }, { id: "airtel_money", label: "Airtel Money", icon: "📱", color: "#E40000", sub: "+227 XX XX XX XX" }, { id: "bank", label: L("Bank Transfer", "Virement bancaire"), icon: "🏦", color: "#1F4E79", sub: "BSIC / Ecobank / BIA" }, { id: "cash", label: L("Cash on delivery", "Paiement à la livraison"), icon: "💵", color: "#2D6A4F", sub: L("Niamey only", "Niamey uniquement") }, ]; function CheckoutModal({ open, onClose }) { const { cart, total, clearCart } = useCart(); const [step, setStep] = React.useState(1); // 1=info, 2=payment, 3=confirm const [method, setMethod] = React.useState("card"); const [form, setForm] = React.useState({ name: "", phone: "", email: "", address: "", notes: "", _hp: "" }); const [placed, setPlaced] = React.useState(false); const [sending, setSending] = React.useState(false); const [error, setError] = React.useState(""); const [orderId, setOrderId] = React.useState(""); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const fInp = { width: "100%", border: "1px solid var(--line)", borderRadius: 3, padding: "9px 12px", fontSize: 14.5, background: "var(--paper-0)", outline: "none", fontFamily: "inherit", color: "var(--ink)", boxSizing: "border-box" }; const lbl = { display: "block", fontSize: 11.5, fontFamily: "var(--fsemi)", fontWeight: 700, textTransform: "uppercase", letterSpacing: ".1em", color: "var(--muted)", marginBottom: 5 }; const placeOrder = async () => { if (form._hp) { setOrderId("AS-DEMO"); setPlaced(true); return; } // honeypot — silently no-op for bots if (!window.sb) { setError(L("Connection unavailable — please call us instead.", "Connexion indisponible \u2014 merci de nous appeler directement.")); return; } setSending(true); setError(""); const contact = { name: form.name, phone: form.phone, email: form.email || null, address: form.address, notes: form.notes || null }; const items = cart.map((i) => ({ product_id: i.id || null, name_fr: i.name_fr || i.name, name_en: i.name_en || i.name, sku: i.sku, qty: i.qty, price_xof: i.price, subtotal_xof: i.price * i.qty, })); // Goes through a database function rather than a plain insert + select-back: // anon only has permission to create orders, not read them, so we can't ask // Supabase to hand the new row back to us directly. The function inserts the // order and its items together and just returns the reference we need. const { data: ref, error: rpcErr } = await window.sb.rpc("create_public_order", { p_contact: contact, p_total_xof: total, p_payment_method: method, p_items: items, }); setSending(false); if (rpcErr || !ref) { setError(L("Something went wrong — please try again or call us.", "Une erreur est survenue \u2014 merci de r\u00e9essayer ou de nous appeler.")); return; } setOrderId(ref); if (window.ggNotify) window.ggNotify("order", { ref, total_xof: total, payment_method: method, contact, items: cart }); setPlaced(true); clearCart(); }; if (!open) return null; if (placed) return (
{L("Order Placed!", "Commande reçue !")}
# {orderId}

{L(`Our team will contact you on ${form.phone} shortly to confirm your order, arrange payment by ${PAYMENT_METHODS.find(m=>m.id===method)?.label}, and organize delivery.`, `Notre équipe vous contactera au ${form.phone} très vite pour confirmer votre commande, organiser le paiement par ${PAYMENT_METHODS.find(m=>m.id===method)?.label}, et planifier la livraison.`)}

{L("A confirmation will be sent to", "Une confirmation sera envoyée à")} {form.email || L("your email", "votre email")}

); return (
e.stopPropagation()} style={{ background: "var(--paper)", borderRadius: 5, maxWidth: 680, width: "100%", boxShadow: "0 30px 80px rgba(0,0,0,.3)" }}> {/* Header */}
{L("Checkout", "Finaliser la commande")}
{[1,2,3].map(s => ( = s ? "var(--spares)" : "var(--paper-2)", color: step >= s ? "#fff" : "var(--muted)", transition: "all .2s" }}>{s} ))}
{/* Step 1 — Contact info */} {step === 1 && (
set("_hp", e.target.value)} autoComplete="off" tabIndex={-1} style={{ position: "absolute", width: 1, height: 1, opacity: 0, pointerEvents: "none" }} aria-hidden="true" />
■ {L("01 / CONTACT INFORMATION", "01 / INFORMATIONS DE CONTACT")}
set("name", e.target.value)} required style={fInp} placeholder="Ibrahim Moussa" />
set("phone", e.target.value)} required style={fInp} placeholder="+227 XX XX XX XX" />
set("email", e.target.value)} style={fInp} placeholder="vous@exemple.com" />
set("address", e.target.value)} required style={fInp} placeholder={L("Niamey — Plateau, Koira Tegui…", "Niamey — Plateau, Koira Tegui…")} />