// Common.jsx — Allen Web Ecommerce shared bits. // Exports: AllenHeader, AllenFooter, AllenIcon, ProductCard, RatingStars, // Breadcrumb, EmptyState, plus a useToast hook. const { useState, useEffect, useRef, createContext, useContext } = React; // --------------------------------------------------------------- // Icon helper — material symbols outlined (CDN). const AllenIcon = ({ name, size = 20, weight = 400, fill = 0, color = "currentColor", style }) => ( {name} ); // --------------------------------------------------------------- // Toast context (lightweight) const ToastCtx = createContext(() => {}); const useToast = () => useContext(ToastCtx); const ToastHost = ({ children }) => { const [msg, setMsg] = useState(null); const timer = useRef(); const show = (m) => { clearTimeout(timer.current); setMsg(m); timer.current = setTimeout(() => setMsg(null), 2200); }; return ( {children}
{msg}
); }; // --------------------------------------------------------------- // Rating stars const RatingStars = ({ value = 4.6, count, size = 14 }) => { const full = Math.floor(value); return ( {[0,1,2,3,4].map(i => ( ))} {value.toFixed(1)} {count != null && ({count.toLocaleString("es-MX")})} ); }; // --------------------------------------------------------------- // Product card (PLP / carousel) const ProductCard = ({ p, onOpen, onAdd, onFav, fav }) => (
onOpen?.(p)} style={{ background: "var(--bg-elev-1)", borderRadius: "var(--radius-card)", padding: 14, display: "flex", flexDirection: "column", gap: 8, boxShadow: "var(--elev-1)", cursor: "pointer", transition: "transform var(--dur-base) var(--ease-out-quart), box-shadow var(--dur-base) var(--ease-out-quart)", }} onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "var(--elev-2)"; }} onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "var(--elev-1)"; }} >
{p.img ? ( {p.name} ) : ( {p.emoji} )} {p.discount && ( −{p.discount}% )}
{p.brand}
{p.name}
${p.price.toLocaleString("es-MX")} {p.was && ${p.was.toLocaleString("es-MX")}}
{p.shipping && (
{p.shipping}
)}
); // --------------------------------------------------------------- // Viewport hook — used across pages to swap grid configs at mobile/tablet/desktop. // Buckets: 'mobile' < 768 | 'tablet' < 1100 | 'desktop' >= 1100 const useViewport = () => { const [w, setW] = useState(() => typeof window === "undefined" ? 1280 : window.innerWidth); useEffect(() => { const onR = () => setW(window.innerWidth); window.addEventListener("resize", onR); return () => window.removeEventListener("resize", onR); }, []); return { w, isMobile: w < 768, isTablet: w >= 768 && w < 1100, isDesktop: w >= 1100, }; }; // --------------------------------------------------------------- // Theme toggle — light <-> dark, persisted in localStorage. // PRODUCT.md mandates dark mode day-one; this is the web counterpart. const useTheme = () => { const [theme, setTheme] = useState(() => { if (typeof window === "undefined") return "light"; return localStorage.getItem("allen-theme") || "light"; }); useEffect(() => { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("allen-theme", theme); }, [theme]); return [theme, () => setTheme(t => t === "light" ? "dark" : "light")]; }; const ThemeToggle = () => { const [theme, toggle] = useTheme(); const isDark = theme === "dark"; return ( ); }; // --------------------------------------------------------------- // Header (utility bar + main nav) const AllenHeader = ({ cartCount, user, onNav, onLogout, currentPath = "/", onReview }) => { const toast = useToast(); const [q, setQ] = useState(""); const [mobileMenu, setMobileMenu] = useState(false); const [mobileSearch, setMobileSearch] = useState(false); const { isMobile, isTablet } = useViewport(); const navItems = [ { id: "home", label: "Inicio" }, { id: "ofertas", label: "Ofertas" }, { id: "novedades", label: "Lo más nuevo" }, { id: "marcas", label: "Marcas" }, { id: "vende", label: "Vende en Allen" }, ]; const handleNav = (id) => { setMobileMenu(false); if (id === "home") return onNav("home"); if (id === "ofertas") return onNav("plp", { onlyDiscount: true }); if (id === "novedades")return onNav("plp", { sort: "newest" }); if (id === "marcas") return onNav("plp"); if (id === "vende") return onNav("vende"); }; const submitSearch = (e) => { e?.preventDefault?.(); const v = q.trim(); if (!v) return; setMobileSearch(false); onNav("plp", { q: v }); }; return (
{/* Utility bar (desktop/tablet only) */} {!isMobile && (
Enviar a Puerto Vallarta · 48330 onNav("seller-onboarding")} style={{ cursor: "pointer" }}>Vende en Allen onNav("ops")} style={{ cursor: "pointer" }}>Allen Ops toast("Allen Food · Travel · Experts")} style={{ cursor: "pointer" }}>Otras verticales toast("Centro de ayuda")} style={{ cursor: "pointer" }}>Ayuda
)} {/* Main header */}
{isMobile && ( )} onNav("home")} style={{ cursor: "pointer", display: "flex", alignItems: "center" }}> Allen {!isMobile && (
setQ(e.target.value)} placeholder={isTablet ? "Busca productos…" : "Busca productos, marcas o categorías…"} style={{ flex: 1, border: 0, outline: 0, background: "transparent", font: "400 14px var(--font-sans)", color: "var(--fg-strong)" }} /> {q && ( )} {!isTablet && ⌘ K} )}
{isMobile && ( )} {!isMobile && !isTablet && ( <> )} {isTablet && ( )} {!isMobile && typeof window.NotificationBell !== "undefined" && ( )}
{/* Sub-nav (desktop only) */} {!isMobile && ( )} {/* Mobile drawer */} {isMobile && mobileMenu && (
setMobileMenu(false)} style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", zIndex: 100 }}>
e.stopPropagation()} style={{ position: "absolute", top: 0, left: 0, bottom: 0, width: 300, background: "var(--bg-elev-1)", padding: "24px 20px", display: "flex", flexDirection: "column", gap: 6, animation: "slideIn .2s var(--ease-out-quart)", }}>
Allen
{navItems.map(n => ( ))}
)} {/* Mobile search drawer */} {isMobile && mobileSearch && (
setQ(e.target.value)} placeholder="Busca productos…" style={{ flex: 1, border: 0, outline: 0, background: "transparent", font: "400 14px var(--font-sans)", color: "var(--fg-strong)" }} />
)}
); }; const headerBtn = { background: "transparent", border: 0, padding: "10px 14px", borderRadius: "9999px", display: "flex", alignItems: "center", gap: 8, font: "600 13px var(--font-sans)", color: "var(--fg-strong)", cursor: "pointer", transition: "background var(--dur-fast)", }; const drawerBtn = { background: "transparent", border: 0, padding: "12px 14px", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 12, font: "600 14px var(--font-sans)", color: "var(--fg-strong)", cursor: "pointer", textAlign: "left", width: "100%", }; // --------------------------------------------------------------- // Footer const AllenFooter = () => ( ); // --------------------------------------------------------------- // Breadcrumb const Breadcrumb = ({ items = [] }) => ( ); Object.assign(window, { AllenIcon, ToastHost, useToast, useTheme, useViewport, ThemeToggle, RatingStars, ProductCard, AllenHeader, AllenFooter, Breadcrumb });