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>
77 lines
3.2 KiB
JavaScript
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>
|
|
);
|
|
}
|