Files
apparel-designer/src/components/shop/ShopHeader.jsx
khalid@traclabs.com 6599a5e68f v0.7: shop storefront + routed app
AppRouter replaces the single-page App as the mounted root: shop routes
(/ and /shop → ShopHome, /shop/:productSlug → ProductPage, /cart →
CartStub, /customize → CustomizeRoute) plus the template-manager admin
surfaces (/admin/templates|products|featured|trash, v0.8 adds
/admin/inventory + RequireAdmin wrapping). App.jsx stays on disk one
release as the documented legacy fallback — nothing imports it.

New shop UI: components/shop/ (ShopHome/Hero/ProductGrid/ProductCard/
ProductPage/ProductGallery/ProductInfo/ProductBuyPanel/CartStub/
ShopHeader/TemplateGalleryForProduct), hooks (useShopProducts/
useShopProduct/useCart/useFacetCatalog), and shop.css + admin.css.

Header gains the module-gated Clear button (onCanClearChange /
onElementCountChange mirror the editor module's history-commit
signals); i18n keys follow. vite proxies /products to the Node server
(template-manager serves product imagery — without it the SPA fallback
returns index.html as an image) and adds a template-manager chunk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 23:28:58 -05:00

77 lines
3.2 KiB
JavaScript

// <ShopHeader> — customer-facing top bar for shop routes.
//
// Mounted by ShopHome, ProductPage, and CartStub. Replaces the
// editor-focused Header from App.jsx for non-/customize routes.
//
// Layout
// ──────
// [brand mark + wordmark] [nav: Shop] [account chip] [cart icon + badge]
//
// The account chip (v0.8, from auth-manager) renders a "Sign in"
// link when logged out and an avatar dropdown when logged in.
//
// Reuses the .ph-* CSS classes from Header.css so the brand identity
// (paw mark, wordmark, tagline, cart-badge styling) stays consistent
// between the editor header and the shop header. The middle slot
// holds shop-specific nav links instead of the editor's Preview /
// Download / Share controls.
//
// Why a separate component instead of a prop-driven Header
// ────────────────────────────────────────────────────────
// The editor Header takes editorRef-based action callbacks (preview,
// download, share) that only make sense in the editor mount. The
// shop chrome needs none of those and shouldn't carry the dead
// callbacks around. A separate component keeps each surface focused.
//
// Cart count comes from useCart(), which is localStorage-backed and
// cross-tab synced. The badge re-renders the moment another tab adds
// to cart.
import { Link } from 'react-router-dom';
import { AccountMenu } from '@khalidadil/auth-manager';
import { useCart } from '../../hooks/useCart.js';
import { t } from '../../i18n/t.js';
export function ShopHeader() {
const { count } = useCart();
const appName = t('header.app-name');
const cartAria = count === 1
? t('header.cart-aria.singular', { count })
: t('header.cart-aria.plural', { count });
return (
<header className="ph-header shop-header" role="banner">
<Link
to="/"
className="ph-logo"
aria-label={t('header.logo-home-aria', { app: appName })}
>
<span className="ph-logo__mark" aria-hidden="true">🐾</span>
<span className="ph-logo__wordmark">
<span className="ph-logo__name">{appName}</span>
<span className="ph-logo__tagline">{t('header.app-tagline')}</span>
</span>
</Link>
<nav className="shop-header__nav" aria-label="Shop navigation">
<Link to="/shop" className="shop-header__nav-link">Shop</Link>
</nav>
<div className="ph-actions shop-header__actions">
{/* v0.8 — account chip lives between nav and cart. Renders a
"Sign in" link when logged out, an avatar+dropdown when
logged in. */}
<AccountMenu />
<Link to="/cart" className="ph-cart" aria-label={cartAria}>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="9" cy="20" r="1.6" />
<circle cx="17" cy="20" r="1.6" />
<path d="M3 4h2l2.4 11.5a2 2 0 0 0 2 1.5h7.6a2 2 0 0 0 2-1.5L21 8H6" />
</svg>
{count > 0 && <span className="ph-cart__badge">{count}</span>}
</Link>
</div>
</header>
);
}