// ===== Shared page wrapper =====
// Hosts announcement, header, mega-nav, footer, cart drawer, product modal, toast.
// Pages render their content inside ... .
const { useState: useWS, useEffect: useWE } = React;
const useCart = () => {
const [cart, setCart] = useWS(() => {
try { return JSON.parse(localStorage.getItem("store_cart")) || []; } catch { return []; }
});
useWE(() => { localStorage.setItem("store_cart", JSON.stringify(cart)); }, [cart]);
return [cart, setCart];
};
const useFaves = () => {
const [faves, setFaves] = useWS(() => {
try { return new Set(JSON.parse(localStorage.getItem("store_faves")) || []); } catch { return new Set(); }
});
useWE(() => { localStorage.setItem("store_faves", JSON.stringify([...faves])); }, [faves]);
return [faves, setFaves];
};
// ---------- Cart Drawer ----------
const CartDrawer = ({ open, onClose, items, onQty, onRemove, onCheckout }) => {
const subtotal = items.reduce((s, it) => s + it.p.price * it.qty, 0);
const shipping = subtotal > 50000 || items.length === 0 ? 0 : 1200;
const total = subtotal + shipping;
return (
<>
Your cart
{items.length} item{items.length === 1 ? "" : "s"}
{items.length === 0 ? (
Your cart is empty
Browse the deals — flash drops end tonight.
) : (
items.map(it => (
{it.p.brand}
{it.p.name}
{money(it.p.price * it.qty)} DZD
onQty(it.p.id, it.qty - 1)}>
{it.qty}
onQty(it.p.id, it.qty + 1)}>
onRemove(it.p.id)}>
))
)}
{items.length > 0 && (
Subtotal {money(subtotal)} DZD
Shipping {shipping === 0 ? "Free" : `${money(shipping)} DZD`}
VAT (incl.) {money(Math.round(total * 0.19))} DZD
Total {money(total)} DZD
Checkout securely
Pay in 3 interest-free installments available at checkout
)}
>
);
};
// ---------- Product Modal ----------
const ProductModal = ({ p, onClose, onAdd }) => {
const [qty, setQty] = useWS(1);
const [thumb, setThumb] = useWS(0);
useWE(() => { setQty(1); setThumb(0); }, [p?.id]);
if (!p) return null;
const discount = p.was ? Math.round((1 - p.price / p.was) * 100) : 0;
return (
e.stopPropagation()}>
{p.badges.map((b, i) => {
const cls = b.includes("%") ? "sale" : b === "NEW" ? "new" : "hot";
return {b} ;
})}
{p.img ? (
) : (
{p.cat.toLowerCase()}.hero.render
)}
{[0,1,2,3].map(i => (
setThumb(i)}>
))}
{p.brand} · SKU APT-{1000 + p.id}
{p.name}
{p.rating.toFixed(1)} ({p.reviews} reviews)
In stock — {p.stock} left
{money(p.price)} DZD
{p.was && {money(p.was)} DZD }
{p.was && -{discount}% }
VAT included · Ships in 24h from Algiers warehouse
{p.specs.map((s, i) => {
const labels = ["Chipset", "Memory", "Speed", "Form factor"];
return (
{labels[i] || "Spec"}
{s}
);
})}
Quantity
setQty(Math.max(1, qty - 1))}>−
{qty}
setQty(qty + 1)}>+
{ onAdd(p, qty); onClose(); }}>
Add to cart · {money(p.price * qty)} DZD
Full details
Free delivery on 50K+
2-year warranty
14-day returns
Compatibility-verified
);
};
const Toast = ({ msg, show }) => (
{msg}
);
// ---------- Algerian Checkout Modal ----------
const WILAYAS = [
["16","Alger",400], ["31","Oran",600], ["25","Constantine",600], ["19","Sétif",500],
["09","Blida",450], ["35","Boumerdès",450], ["06","Béjaïa",550], ["23","Annaba",650],
["15","Tizi Ouzou",500], ["05","Batna",650], ["02","Chlef",550], ["27","Mostaganem",650],
["13","Tlemcen",700], ["22","Sidi Bel Abbès",700], ["44","Aïn Defla",500], ["42","Tipaza",450],
["10","Bouira",500], ["26","Médéa",500], ["38","Tissemsilt",600], ["48","Relizane",600],
["29","Mascara",650], ["46","Aïn Témouchent",700], ["20","Saïda",700], ["14","Tiaret",650],
["17","Djelfa",750], ["28","M'Sila",700], ["34","Bordj Bou Arréridj",550], ["18","Jijel",600],
["21","Skikda",650], ["43","Mila",600], ["24","Guelma",700], ["36","El Tarf",750],
["41","Souk Ahras",700], ["04","Oum El Bouaghi",700], ["12","Tébessa",800], ["40","Khenchela",750],
["07","Biskra",800], ["39","El Oued",900], ["30","Ouargla",1000], ["32","El Bayadh",900],
["45","Naâma",900], ["08","Béchar",1100], ["37","Tindouf",1300], ["11","Tamanrasset",1500],
["33","Illizi",1500], ["47","Ghardaïa",1000], ["01","Adrar",1200], ["03","Laghouat",800],
];
const CheckoutModal = ({ open, items, onClose, onConfirm }) => {
const [step, setStep] = useWS(1);
const [form, setForm] = useWS({
name: "", phone: "", wilaya: "16", commune: "", address: "",
delivery: "home", notes: "", payment: "cod",
});
const [submitting, setSubmitting] = useWS(false);
const [orderId, setOrderId] = useWS(null);
useWE(() => {
if (open) { setStep(1); setOrderId(null); }
}, [open]);
const wilayaInfo = WILAYAS.find(w => w[0] === form.wilaya) || WILAYAS[0];
const homeFee = wilayaInfo[2];
const stopdeskFee = Math.round(homeFee * 0.6);
const deliveryFee = form.delivery === "home" ? homeFee : stopdeskFee;
const subtotal = items.reduce((s, it) => s + it.p.price * it.qty, 0);
const total = subtotal + deliveryFee;
const set = (k, v) => setForm(p => ({ ...p, [k]: v }));
const phoneOk = /^0(5|6|7)\d{8}$/.test(form.phone.replace(/\s/g, ""));
const canContinue =
(step === 1) ||
(step === 2 && form.name.trim().length >= 3 && phoneOk && form.address.trim().length >= 5);
const submit = () => {
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
const id = "APT-" + Math.floor(100000 + Math.random() * 900000);
setOrderId(id);
setStep(3);
onConfirm && onConfirm();
}, 1100);
};
if (!open) return null;
return (
e.stopPropagation()}>
{step !== 3 && (
= 1 ? "active" : ""}`}>
1 Review
= 2 ? "active" : ""}`}>
2 Delivery & contact
= 3 ? "active" : ""}`}>
3 Confirmation
)}
{step === 1 && (
Order items ({items.reduce((s, i) => s + i.qty, 0)})
{items.map(it => (
{it.p.brand}
{it.p.name}
Qty: {it.qty} × {money(it.p.price)} DZD
{money(it.p.price * it.qty)} DZD
))}
Apply
)}
{step === 2 && (
)}
{step === 3 && (
Thank you, {form.name.split(" ")[0]}!
Your order {orderId} has been placed.
We'll call +213 {form.phone} within 2 hours to confirm,
then dispatch to {wilayaInfo[1]} for delivery in 2–4 working days .
Order total
{money(total)} DZD
Payment
{form.payment === "cod" ? "Cash on delivery" : "Online (CIB / Edahabia)"}
Delivery
{form.delivery === "home" ? "Home delivery" : "Stop-desk pickup"}
)}
{step !== 3 && (
Subtotal {money(subtotal)} DZD
Delivery ({wilayaInfo[1]}) {money(deliveryFee)} DZD
Total to pay {money(total)} DZD
{step > 1 && (
setStep(step - 1)}>← Back
)}
{step === 1 && (
setStep(2)}>
Continue to delivery
)}
{step === 2 && (
{submitting ? "Placing order…" : <>Confirm order · {money(total)} DZD>}
)}
)}
);
};
window.CheckoutModal = CheckoutModal;
const PageWrapper = ({ children, query, setQuery }) => {
const [cart, setCart] = useCart();
const [faves, setFaves] = useFaves();
const [drawer, setDrawer] = useWS(false);
const [modal, setModal] = useWS(null);
const [toast, setToast] = useWS({ msg: "", show: false });
const [internalQ, setInternalQ] = useWS("");
const [checkout, setCheckout] = useWS(null); // null or { items, source }
const q = query ?? internalQ;
const setQ = setQuery ?? setInternalQ;
const showToast = (msg) => {
setToast({ msg, show: true });
setTimeout(() => setToast({ msg: "", show: false }), 2200);
};
const addToCart = (p, qty = 1) => {
setCart(prev => {
const found = prev.find(it => it.p.id === p.id);
if (found) return prev.map(it => it.p.id === p.id ? { ...it, qty: it.qty + qty } : it);
return [...prev, { p, qty }];
});
showToast(`${p.name.split(" ").slice(0, 4).join(" ")} added to cart`);
};
const setQty = (id, qty) => {
if (qty < 1) return removeFromCart(id);
setCart(prev => prev.map(it => it.p.id === id ? { ...it, qty } : it));
};
const removeFromCart = (id) => setCart(prev => prev.filter(it => it.p.id !== id));
const toggleFav = (id) => setFaves(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
const openCheckout = (items, source = "buynow") => {
if (!items || items.length === 0) return;
setCheckout({ items, source });
setDrawer(false);
setModal(null);
};
const onCheckoutConfirm = () => {
if (checkout?.source === "cart") setCart([]);
};
useWE(() => {
const onKey = (e) => { if (e.key === "Escape") { setDrawer(false); setModal(null); setCheckout(null); } };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
const cartCount = cart.reduce((s, it) => s + it.qty, 0);
const ctx = { addToCart, openModal: setModal, faves, toggleFav, openCheckout, cart };
return (
setDrawer(true)}
query={q}
setQuery={setQ}
/>
{typeof children === "function" ? children(ctx) : children}
setDrawer(false)}
items={cart}
onQty={setQty}
onRemove={removeFromCart}
onCheckout={() => openCheckout(cart, "cart")}
/>
{modal && (
setModal(null)}
onAdd={addToCart}
/>
)}
setCheckout(null)}
onConfirm={onCheckoutConfirm}
/>
);
};
window.PageWrapper = PageWrapper;
window.CartDrawer = CartDrawer;
window.ProductModal = ProductModal;
window.Toast = Toast;