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>
This commit is contained in:
82
src/components/shop/ProductPage.jsx
Normal file
82
src/components/shop/ProductPage.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
// <ProductPage> — shop product detail at /shop/:productSlug.
|
||||
//
|
||||
// Composition:
|
||||
// ShopHeader
|
||||
// ├ ProductGallery (left column)
|
||||
// └ ProductInfo + ProductBuyPanel (right column)
|
||||
// TemplateGalleryForProduct (full width, customizable kind only)
|
||||
//
|
||||
// Loading / error / 404 states are handled inline (kept simple — no
|
||||
// dedicated error boundary needed for a leaf route).
|
||||
//
|
||||
// The buy panel's "Customize this product" CTA scrolls the template
|
||||
// gallery into view via a ref + scrollIntoView. The gallery section
|
||||
// has id="templates" so the URL hash also works as a fallback.
|
||||
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useRef } from 'react';
|
||||
import { ShopHeader } from './ShopHeader.jsx';
|
||||
import { ProductGallery } from './ProductGallery.jsx';
|
||||
import { ProductInfo } from './ProductInfo.jsx';
|
||||
import { ProductBuyPanel } from './ProductBuyPanel.jsx';
|
||||
import { TemplateGalleryForProduct } from './TemplateGalleryForProduct.jsx';
|
||||
import { useShopProduct } from '../../hooks/useShopProduct.js';
|
||||
|
||||
export function ProductPage() {
|
||||
const { productSlug } = useParams();
|
||||
const { product, loading, error } = useShopProduct(productSlug);
|
||||
const templatesRef = useRef(null);
|
||||
|
||||
const handleScrollToTemplates = () => {
|
||||
if (templatesRef.current) {
|
||||
templatesRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<ShopHeader />
|
||||
<main className="shop-main">
|
||||
{loading && (
|
||||
<div className="shop-product-page shop-product-page--loading">
|
||||
<p>Loading…</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="shop-product-page shop-product-page--error">
|
||||
<h1>Product not found</h1>
|
||||
<p>
|
||||
We couldn't find that product. <Link to="/shop">Back to the shop →</Link>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{product && (
|
||||
<>
|
||||
<nav className="shop-breadcrumb" aria-label="Breadcrumb">
|
||||
<Link to="/shop">← Shop</Link>
|
||||
</nav>
|
||||
<article className="shop-product-page">
|
||||
<div className="shop-product-page__gallery">
|
||||
<ProductGallery product={product} />
|
||||
</div>
|
||||
<div className="shop-product-page__details">
|
||||
<ProductInfo product={product} />
|
||||
<ProductBuyPanel
|
||||
product={product}
|
||||
onScrollToTemplates={handleScrollToTemplates}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
{product.kind === 'customizable' && (
|
||||
<div ref={templatesRef}>
|
||||
<TemplateGalleryForProduct product={product} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user