/* ============================================================ Shared UI primitives — Evans Web Store ============================================================ */ const { useState, useEffect, useRef, useContext, createContext } = React; const AppCtx = createContext(null); /* ---------- Icons (stroke, 1.6) ---------- */ function Icon({ name, size = 20, className = '', style }) { const s = { width: size, height: size, fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round', ...style }; const paths = { cart: <>, user: <>, chevron: , chevronDown: , chevronLeft: , check: , checkCircle: <>, plus: , minus: , search: <>, truck: <>, box: <>, pin: <>, coins: <>, bell: <>, grid: <>, users: <>, alert: <>, logout: <>, settings: <>, receipt: <>, trash: <>, edit: <>, shield: <>, spark: , arrowRight: , }; return {paths[name]}; } /* ---------- Logo (real Evans lockup, locked to 733:231 ratio) ---------- */ function Logo({ size = 34, light = false }) { const h = size * 1.34; const w = h * (733 / 231); return ( Evans Equipment & Environmental ); } /* ---------- Garment placeholder image ---------- */ function Garment({ product, color, tag = true, logo = true }) { const c = color || product.colors[0]; const fill = COLORS[c] || '#cfd6d4'; const isWhite = ['white','cream','silver','stone','hivis','tan','khaki'].includes(c); return (
{logo && product.kind !== 'cap' && (
)} {product.fr &&
FR
} {tag &&
Product photo
}
); } /* ---------- Swatch row ---------- */ function Swatches({ colors, selected, onSelect, lg = false, max }) { const shown = max ? colors.slice(0, max) : colors; const extra = max ? colors.length - max : 0; return (
{shown.map(c => (
); } /* ---------- Points display ---------- */ function Price({ points, size = 16 }) { return ( {points} pts (${points}) ); } function PointsPill() { const { user } = useContext(AppCtx); const remaining = user.pointsTotal - user.pointsUsed; const pct = Math.max(0, Math.min(100, (remaining / user.pointsTotal) * 100)); return (
{remaining} / {user.pointsTotal} pts
); } /* ---------- Product card ---------- */ function ProductCard({ product }) { const { navigate } = useContext(AppCtx); const [color, setColor] = useState(product.colors[0]); return (
navigate('product', { id: product.id, color })}>
{product.brand} · {product.sku}
{product.name}
); } /* ---------- Storefront header ---------- */ function StoreHeader() { const { navigate, route, cart, user, logout } = useContext(AppCtx); const [menu, setMenu] = useState(false); const cartCount = cart.reduce((n, i) => n + i.qty, 0); const cats = CATEGORIES.filter(c => user.categories.includes(c.id)); return (
navigate('home')}>
{menu && ( <>
setMenu(false)} />
{user.name}
{user.role}
{ setMenu(false); navigate('account'); }} /> { setMenu(false); navigate('account'); }} /> { setMenu(false); navigate('admin'); }} />
{ setMenu(false); logout(); }} />
)}
); } function NavLink({ label, active, onClick }) { return ( ); } function MenuItem({ icon, label, onClick }) { return ( ); } /* ---------- Footer ---------- */ function StoreFooter() { const { navigate } = useContext(AppCtx); return ( ); } Object.assign(window, { AppCtx, Icon, Logo, Garment, Swatches, Price, PointsPill, ProductCard, StoreHeader, NavLink, MenuItem, StoreFooter });