@cimplify/sdk 0.40.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/deploy.mjs +561 -0
- package/dist/cli/commands/dev.mjs +434 -0
- package/dist/cli/commands/domains.mjs +479 -0
- package/dist/cli/commands/env.mjs +610 -0
- package/dist/cli/commands/link.mjs +325 -0
- package/dist/cli/commands/login.mjs +515 -0
- package/dist/cli/commands/logout.mjs +54 -0
- package/dist/cli/commands/logs.mjs +358 -0
- package/dist/cli/commands/projects.mjs +327 -0
- package/dist/cli/commands/rollback.mjs +477 -0
- package/dist/cli/commands/status.mjs +380 -0
- package/dist/cli/commands/unlink.mjs +58 -0
- package/dist/cli/commands/whoami.mjs +234 -0
- package/dist/dispatcher.mjs +94 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/storefront-bakery/.env.example +5 -0
- package/templates/storefront-bakery/AGENTS.md +57 -0
- package/templates/storefront-bakery/app/about/page.tsx +21 -44
- package/templates/storefront-bakery/app/accessibility/page.tsx +11 -0
- package/templates/storefront-bakery/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-bakery/app/account/orders/page.tsx +21 -0
- package/templates/storefront-bakery/app/account/page.tsx +22 -0
- package/templates/storefront-bakery/app/account/settings/page.tsx +21 -0
- package/templates/storefront-bakery/app/categories/[slug]/page.tsx +2 -1
- package/templates/storefront-bakery/app/collections/[slug]/page.tsx +2 -1
- package/templates/storefront-bakery/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-bakery/app/contact/page.tsx +54 -0
- package/templates/storefront-bakery/app/error.tsx +60 -0
- package/templates/storefront-bakery/app/faq/page.tsx +13 -89
- package/templates/storefront-bakery/app/layout.tsx +37 -2
- package/templates/storefront-bakery/app/llms.txt/route.ts +94 -0
- package/templates/storefront-bakery/app/login/page.tsx +17 -0
- package/templates/storefront-bakery/app/not-found.tsx +39 -0
- package/templates/storefront-bakery/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-bakery/app/page.tsx +6 -6
- package/templates/storefront-bakery/app/privacy/page.tsx +24 -68
- package/templates/storefront-bakery/app/returns/page.tsx +11 -0
- package/templates/storefront-bakery/app/robots.ts +18 -0
- package/templates/storefront-bakery/app/search/page.tsx +38 -0
- package/templates/storefront-bakery/app/search/search-client.tsx +7 -0
- package/templates/storefront-bakery/app/shipping/page.tsx +16 -0
- package/templates/storefront-bakery/app/shop/page.tsx +3 -2
- package/templates/storefront-bakery/app/signup/page.tsx +17 -0
- package/templates/storefront-bakery/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-bakery/app/sitemap.ts +62 -0
- package/templates/storefront-bakery/app/terms/page.tsx +24 -82
- package/templates/storefront-bakery/app/track-order/page.tsx +24 -0
- package/templates/storefront-bakery/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-bakery/components/account-iframe.tsx +13 -0
- package/templates/storefront-bakery/components/footer.tsx +88 -130
- package/templates/storefront-bakery/components/header.tsx +8 -16
- package/templates/storefront-bakery/components/policy-page.tsx +49 -0
- package/templates/storefront-bakery/lib/brand.ts +568 -0
- package/templates/storefront-fashion/.env.example +16 -0
- package/templates/storefront-fashion/AGENTS.md +52 -0
- package/templates/storefront-fashion/README.md +77 -0
- package/templates/storefront-fashion/app/about/page.tsx +41 -0
- package/templates/storefront-fashion/app/accessibility/page.tsx +11 -0
- package/templates/storefront-fashion/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-fashion/app/account/orders/page.tsx +21 -0
- package/templates/storefront-fashion/app/account/page.tsx +22 -0
- package/templates/storefront-fashion/app/account/settings/page.tsx +21 -0
- package/templates/storefront-fashion/app/cart/page.tsx +9 -0
- package/templates/storefront-fashion/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-fashion/app/categories/[slug]/page.tsx +130 -0
- package/templates/storefront-fashion/app/checkout/page.tsx +17 -0
- package/templates/storefront-fashion/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-fashion/app/collections/[slug]/page.tsx +130 -0
- package/templates/storefront-fashion/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-fashion/app/contact/page.tsx +54 -0
- package/templates/storefront-fashion/app/error.tsx +61 -0
- package/templates/storefront-fashion/app/faq/page.tsx +46 -0
- package/templates/storefront-fashion/app/globals.css +46 -0
- package/templates/storefront-fashion/app/layout.tsx +76 -0
- package/templates/storefront-fashion/app/llms.txt/route.ts +94 -0
- package/templates/storefront-fashion/app/login/page.tsx +17 -0
- package/templates/storefront-fashion/app/lookbook/page.tsx +132 -0
- package/templates/storefront-fashion/app/not-found.tsx +39 -0
- package/templates/storefront-fashion/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-fashion/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-fashion/app/page.tsx +183 -0
- package/templates/storefront-fashion/app/privacy/page.tsx +44 -0
- package/templates/storefront-fashion/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-fashion/app/products/[slug]/product-detail.tsx +71 -0
- package/templates/storefront-fashion/app/returns/page.tsx +11 -0
- package/templates/storefront-fashion/app/robots.ts +18 -0
- package/templates/storefront-fashion/app/search/page.tsx +38 -0
- package/templates/storefront-fashion/app/search/search-client.tsx +7 -0
- package/templates/storefront-fashion/app/shipping/page.tsx +16 -0
- package/templates/storefront-fashion/app/shop/page.tsx +63 -0
- package/templates/storefront-fashion/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-fashion/app/signup/page.tsx +17 -0
- package/templates/storefront-fashion/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-fashion/app/sitemap.ts +59 -0
- package/templates/storefront-fashion/app/size-guide/page.tsx +155 -0
- package/templates/storefront-fashion/app/terms/page.tsx +44 -0
- package/templates/storefront-fashion/app/track-order/page.tsx +24 -0
- package/templates/storefront-fashion/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-fashion/components/account-iframe.tsx +13 -0
- package/templates/storefront-fashion/components/brand-marquee.tsx +27 -0
- package/templates/storefront-fashion/components/cart-pill.tsx +32 -0
- package/templates/storefront-fashion/components/category-grid.tsx +28 -0
- package/templates/storefront-fashion/components/category-tiles.tsx +104 -0
- package/templates/storefront-fashion/components/collection-strip.tsx +45 -0
- package/templates/storefront-fashion/components/feature-hero.tsx +82 -0
- package/templates/storefront-fashion/components/footer.tsx +153 -0
- package/templates/storefront-fashion/components/header.tsx +43 -0
- package/templates/storefront-fashion/components/hero.tsx +28 -0
- package/templates/storefront-fashion/components/nav-link.tsx +20 -0
- package/templates/storefront-fashion/components/newsletter.tsx +50 -0
- package/templates/storefront-fashion/components/policy-page.tsx +49 -0
- package/templates/storefront-fashion/components/promo-banner.tsx +41 -0
- package/templates/storefront-fashion/components/providers.tsx +31 -0
- package/templates/storefront-fashion/components/section-heading.tsx +37 -0
- package/templates/storefront-fashion/components/store-product-card.tsx +83 -0
- package/templates/storefront-fashion/components/trade-in-cta.tsx +54 -0
- package/templates/storefront-fashion/components/trust-bar.tsx +66 -0
- package/templates/storefront-fashion/lib/brand.ts +516 -0
- package/templates/storefront-fashion/lib/cart.ts +27 -0
- package/templates/storefront-fashion/next.config.ts +40 -0
- package/templates/storefront-fashion/package.json +28 -0
- package/templates/storefront-fashion/postcss.config.mjs +7 -0
- package/templates/storefront-fashion/tsconfig.json +23 -0
- package/templates/storefront-grocery/.env.example +16 -0
- package/templates/storefront-grocery/AGENTS.md +39 -0
- package/templates/storefront-grocery/README.md +73 -0
- package/templates/storefront-grocery/app/about/page.tsx +38 -0
- package/templates/storefront-grocery/app/accessibility/page.tsx +11 -0
- package/templates/storefront-grocery/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-grocery/app/account/orders/page.tsx +21 -0
- package/templates/storefront-grocery/app/account/page.tsx +22 -0
- package/templates/storefront-grocery/app/account/settings/page.tsx +21 -0
- package/templates/storefront-grocery/app/cart/page.tsx +9 -0
- package/templates/storefront-grocery/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-grocery/app/categories/[slug]/page.tsx +118 -0
- package/templates/storefront-grocery/app/checkout/page.tsx +17 -0
- package/templates/storefront-grocery/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-grocery/app/collections/[slug]/page.tsx +118 -0
- package/templates/storefront-grocery/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-grocery/app/contact/page.tsx +54 -0
- package/templates/storefront-grocery/app/error.tsx +60 -0
- package/templates/storefront-grocery/app/faq/page.tsx +46 -0
- package/templates/storefront-grocery/app/globals.css +45 -0
- package/templates/storefront-grocery/app/layout.tsx +75 -0
- package/templates/storefront-grocery/app/llms.txt/route.ts +94 -0
- package/templates/storefront-grocery/app/login/page.tsx +17 -0
- package/templates/storefront-grocery/app/not-found.tsx +39 -0
- package/templates/storefront-grocery/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-grocery/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront-grocery/app/page.tsx +97 -0
- package/templates/storefront-grocery/app/privacy/page.tsx +44 -0
- package/templates/storefront-grocery/app/returns/page.tsx +11 -0
- package/templates/storefront-grocery/app/robots.ts +18 -0
- package/templates/storefront-grocery/app/search/page.tsx +38 -0
- package/templates/storefront-grocery/app/search/search-client.tsx +7 -0
- package/templates/storefront-grocery/app/shipping/page.tsx +16 -0
- package/templates/storefront-grocery/app/shop/page.tsx +31 -0
- package/templates/storefront-grocery/app/shop/shop-client.tsx +27 -0
- package/templates/storefront-grocery/app/signup/page.tsx +17 -0
- package/templates/storefront-grocery/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-grocery/app/sitemap.ts +62 -0
- package/templates/storefront-grocery/app/terms/page.tsx +44 -0
- package/templates/storefront-grocery/app/track-order/page.tsx +24 -0
- package/templates/storefront-grocery/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-grocery/components/account-iframe.tsx +13 -0
- package/templates/storefront-grocery/components/cart-pill.tsx +32 -0
- package/templates/storefront-grocery/components/category-grid.tsx +28 -0
- package/templates/storefront-grocery/components/collection-strip.tsx +45 -0
- package/templates/storefront-grocery/components/footer.tsx +148 -0
- package/templates/storefront-grocery/components/header.tsx +43 -0
- package/templates/storefront-grocery/components/hero.tsx +25 -0
- package/templates/storefront-grocery/components/nav-link.tsx +20 -0
- package/templates/storefront-grocery/components/policy-page.tsx +49 -0
- package/templates/storefront-grocery/components/providers.tsx +31 -0
- package/templates/storefront-grocery/components/store-product-card.tsx +83 -0
- package/templates/storefront-grocery/lib/brand.ts +373 -0
- package/templates/storefront-grocery/lib/cart.ts +27 -0
- package/templates/storefront-grocery/next.config.ts +40 -0
- package/templates/storefront-grocery/package.json +28 -0
- package/templates/storefront-grocery/postcss.config.mjs +7 -0
- package/templates/storefront-grocery/tsconfig.json +23 -0
- package/templates/storefront-restaurant/.env.example +16 -0
- package/templates/storefront-restaurant/AGENTS.md +39 -0
- package/templates/storefront-restaurant/README.md +73 -0
- package/templates/storefront-restaurant/app/about/page.tsx +38 -0
- package/templates/storefront-restaurant/app/accessibility/page.tsx +11 -0
- package/templates/storefront-restaurant/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-restaurant/app/account/orders/page.tsx +21 -0
- package/templates/storefront-restaurant/app/account/page.tsx +22 -0
- package/templates/storefront-restaurant/app/account/settings/page.tsx +21 -0
- package/templates/storefront-restaurant/app/cart/page.tsx +9 -0
- package/templates/storefront-restaurant/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +118 -0
- package/templates/storefront-restaurant/app/checkout/page.tsx +17 -0
- package/templates/storefront-restaurant/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +118 -0
- package/templates/storefront-restaurant/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-restaurant/app/contact/page.tsx +54 -0
- package/templates/storefront-restaurant/app/error.tsx +60 -0
- package/templates/storefront-restaurant/app/faq/page.tsx +46 -0
- package/templates/storefront-restaurant/app/globals.css +44 -0
- package/templates/storefront-restaurant/app/layout.tsx +80 -0
- package/templates/storefront-restaurant/app/llms.txt/route.ts +94 -0
- package/templates/storefront-restaurant/app/login/page.tsx +17 -0
- package/templates/storefront-restaurant/app/not-found.tsx +39 -0
- package/templates/storefront-restaurant/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-restaurant/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront-restaurant/app/page.tsx +97 -0
- package/templates/storefront-restaurant/app/privacy/page.tsx +44 -0
- package/templates/storefront-restaurant/app/reservations/page.tsx +66 -0
- package/templates/storefront-restaurant/app/reservations/reservations-client.tsx +234 -0
- package/templates/storefront-restaurant/app/returns/page.tsx +11 -0
- package/templates/storefront-restaurant/app/robots.ts +18 -0
- package/templates/storefront-restaurant/app/search/page.tsx +38 -0
- package/templates/storefront-restaurant/app/search/search-client.tsx +7 -0
- package/templates/storefront-restaurant/app/shipping/page.tsx +16 -0
- package/templates/storefront-restaurant/app/shop/page.tsx +31 -0
- package/templates/storefront-restaurant/app/shop/shop-client.tsx +27 -0
- package/templates/storefront-restaurant/app/signup/page.tsx +17 -0
- package/templates/storefront-restaurant/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-restaurant/app/sitemap.ts +62 -0
- package/templates/storefront-restaurant/app/terms/page.tsx +44 -0
- package/templates/storefront-restaurant/app/track-order/page.tsx +24 -0
- package/templates/storefront-restaurant/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-restaurant/components/account-iframe.tsx +13 -0
- package/templates/storefront-restaurant/components/cart-pill.tsx +32 -0
- package/templates/storefront-restaurant/components/category-grid.tsx +28 -0
- package/templates/storefront-restaurant/components/collection-strip.tsx +45 -0
- package/templates/storefront-restaurant/components/footer.tsx +148 -0
- package/templates/storefront-restaurant/components/header.tsx +43 -0
- package/templates/storefront-restaurant/components/hero.tsx +25 -0
- package/templates/storefront-restaurant/components/nav-link.tsx +20 -0
- package/templates/storefront-restaurant/components/policy-page.tsx +49 -0
- package/templates/storefront-restaurant/components/product-modal.tsx +105 -0
- package/templates/storefront-restaurant/components/providers.tsx +31 -0
- package/templates/storefront-restaurant/components/store-product-card.tsx +83 -0
- package/templates/storefront-restaurant/lib/brand.ts +375 -0
- package/templates/storefront-restaurant/lib/cart.ts +27 -0
- package/templates/storefront-restaurant/next.config.ts +40 -0
- package/templates/storefront-restaurant/package.json +28 -0
- package/templates/storefront-restaurant/postcss.config.mjs +7 -0
- package/templates/storefront-restaurant/tsconfig.json +23 -0
- package/templates/storefront-retail/.env.example +5 -0
- package/templates/storefront-retail/AGENTS.md +64 -0
- package/templates/storefront-retail/app/about/page.tsx +24 -54
- package/templates/storefront-retail/app/accessibility/page.tsx +11 -0
- package/templates/storefront-retail/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-retail/app/account/orders/page.tsx +21 -0
- package/templates/storefront-retail/app/account/page.tsx +22 -0
- package/templates/storefront-retail/app/account/settings/page.tsx +21 -0
- package/templates/storefront-retail/app/categories/[slug]/page.tsx +2 -1
- package/templates/storefront-retail/app/collections/[slug]/page.tsx +2 -1
- package/templates/storefront-retail/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-retail/app/contact/page.tsx +54 -0
- package/templates/storefront-retail/app/error.tsx +61 -0
- package/templates/storefront-retail/app/faq/page.tsx +11 -92
- package/templates/storefront-retail/app/layout.tsx +37 -8
- package/templates/storefront-retail/app/llms.txt/route.ts +94 -0
- package/templates/storefront-retail/app/login/page.tsx +17 -0
- package/templates/storefront-retail/app/not-found.tsx +39 -0
- package/templates/storefront-retail/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-retail/app/page.tsx +16 -73
- package/templates/storefront-retail/app/privacy/page.tsx +24 -81
- package/templates/storefront-retail/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-retail/app/products/[slug]/product-detail.tsx +71 -0
- package/templates/storefront-retail/app/returns/page.tsx +11 -0
- package/templates/storefront-retail/app/robots.ts +18 -0
- package/templates/storefront-retail/app/search/page.tsx +38 -0
- package/templates/storefront-retail/app/search/search-client.tsx +7 -0
- package/templates/storefront-retail/app/shipping/page.tsx +16 -0
- package/templates/storefront-retail/app/shop/page.tsx +3 -3
- package/templates/storefront-retail/app/signup/page.tsx +17 -0
- package/templates/storefront-retail/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-retail/app/sitemap.ts +59 -0
- package/templates/storefront-retail/app/terms/page.tsx +24 -97
- package/templates/storefront-retail/app/track-order/page.tsx +24 -0
- package/templates/storefront-retail/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-retail/components/account-iframe.tsx +13 -0
- package/templates/storefront-retail/components/brand-marquee.tsx +6 -15
- package/templates/storefront-retail/components/feature-hero.tsx +2 -1
- package/templates/storefront-retail/components/footer.tsx +85 -120
- package/templates/storefront-retail/components/header.tsx +10 -12
- package/templates/storefront-retail/components/newsletter.tsx +7 -9
- package/templates/storefront-retail/components/policy-page.tsx +49 -0
- package/templates/storefront-retail/components/promo-banner.tsx +12 -15
- package/templates/storefront-retail/components/store-product-card.tsx +7 -7
- package/templates/storefront-retail/components/trade-in-cta.tsx +11 -17
- package/templates/storefront-retail/components/trust-bar.tsx +45 -5
- package/templates/storefront-retail/lib/brand.ts +663 -0
- package/templates/storefront-services/.env.example +16 -0
- package/templates/storefront-services/AGENTS.md +41 -0
- package/templates/storefront-services/README.md +73 -0
- package/templates/storefront-services/app/about/page.tsx +38 -0
- package/templates/storefront-services/app/accessibility/page.tsx +11 -0
- package/templates/storefront-services/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-services/app/account/orders/page.tsx +21 -0
- package/templates/storefront-services/app/account/page.tsx +22 -0
- package/templates/storefront-services/app/account/settings/page.tsx +21 -0
- package/templates/storefront-services/app/book/book-client.tsx +195 -0
- package/templates/storefront-services/app/book/page.tsx +65 -0
- package/templates/storefront-services/app/cart/page.tsx +9 -0
- package/templates/storefront-services/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-services/app/categories/[slug]/page.tsx +118 -0
- package/templates/storefront-services/app/checkout/page.tsx +17 -0
- package/templates/storefront-services/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-services/app/collections/[slug]/page.tsx +118 -0
- package/templates/storefront-services/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-services/app/contact/page.tsx +54 -0
- package/templates/storefront-services/app/error.tsx +60 -0
- package/templates/storefront-services/app/faq/page.tsx +46 -0
- package/templates/storefront-services/app/globals.css +45 -0
- package/templates/storefront-services/app/layout.tsx +80 -0
- package/templates/storefront-services/app/llms.txt/route.ts +94 -0
- package/templates/storefront-services/app/login/page.tsx +17 -0
- package/templates/storefront-services/app/not-found.tsx +39 -0
- package/templates/storefront-services/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-services/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront-services/app/page.tsx +97 -0
- package/templates/storefront-services/app/privacy/page.tsx +44 -0
- package/templates/storefront-services/app/returns/page.tsx +11 -0
- package/templates/storefront-services/app/robots.ts +18 -0
- package/templates/storefront-services/app/search/page.tsx +38 -0
- package/templates/storefront-services/app/search/search-client.tsx +7 -0
- package/templates/storefront-services/app/shipping/page.tsx +16 -0
- package/templates/storefront-services/app/shop/page.tsx +31 -0
- package/templates/storefront-services/app/shop/shop-client.tsx +27 -0
- package/templates/storefront-services/app/signup/page.tsx +17 -0
- package/templates/storefront-services/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-services/app/sitemap.ts +62 -0
- package/templates/storefront-services/app/terms/page.tsx +44 -0
- package/templates/storefront-services/app/track-order/page.tsx +24 -0
- package/templates/storefront-services/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-services/components/account-iframe.tsx +13 -0
- package/templates/storefront-services/components/cart-pill.tsx +32 -0
- package/templates/storefront-services/components/category-grid.tsx +28 -0
- package/templates/storefront-services/components/collection-strip.tsx +45 -0
- package/templates/storefront-services/components/footer.tsx +148 -0
- package/templates/storefront-services/components/header.tsx +43 -0
- package/templates/storefront-services/components/hero.tsx +25 -0
- package/templates/storefront-services/components/nav-link.tsx +20 -0
- package/templates/storefront-services/components/policy-page.tsx +49 -0
- package/templates/storefront-services/components/product-modal.tsx +105 -0
- package/templates/storefront-services/components/providers.tsx +31 -0
- package/templates/storefront-services/components/store-product-card.tsx +83 -0
- package/templates/storefront-services/lib/brand.ts +394 -0
- package/templates/storefront-services/lib/cart.ts +27 -0
- package/templates/storefront-services/next.config.ts +40 -0
- package/templates/storefront-services/package.json +28 -0
- package/templates/storefront-services/postcss.config.mjs +7 -0
- package/templates/storefront-services/tsconfig.json +23 -0
- /package/templates/{storefront-retail → storefront-grocery}/components/product-modal.tsx +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
2
|
+
import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
const SITE_URL =
|
|
6
|
+
process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
|
|
7
|
+
|
|
8
|
+
async function buildLlmsTxt(): Promise<string> {
|
|
9
|
+
"use cache";
|
|
10
|
+
cacheTag(tags.products(), tags.categories(), tags.collections());
|
|
11
|
+
cacheLife("hours");
|
|
12
|
+
|
|
13
|
+
const client = getServerClient();
|
|
14
|
+
const [productsRes, categoriesRes, collectionsRes] = await Promise.all([
|
|
15
|
+
client.catalogue.getProducts({ limit: 500 }),
|
|
16
|
+
client.catalogue.getCategories(),
|
|
17
|
+
client.catalogue.getCollections(),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const products: Product[] = productsRes.ok ? productsRes.value.items : [];
|
|
21
|
+
const categories = categoriesRes.ok ? categoriesRes.value : [];
|
|
22
|
+
const collections = collectionsRes.ok ? collectionsRes.value : [];
|
|
23
|
+
|
|
24
|
+
const lines: string[] = [];
|
|
25
|
+
lines.push(`# ${brand.name}`);
|
|
26
|
+
lines.push("");
|
|
27
|
+
lines.push(`> ${brand.llms.summary}`);
|
|
28
|
+
lines.push("");
|
|
29
|
+
lines.push("## Browse");
|
|
30
|
+
lines.push(`- [Home](${SITE_URL}/)`);
|
|
31
|
+
lines.push(`- [Shop](${SITE_URL}/shop): Full catalogue with filter and sort.`);
|
|
32
|
+
|
|
33
|
+
if (categories.length > 0) {
|
|
34
|
+
lines.push("");
|
|
35
|
+
lines.push("## Categories");
|
|
36
|
+
for (const c of categories) {
|
|
37
|
+
lines.push(
|
|
38
|
+
`- [${c.name}](${SITE_URL}/categories/${c.slug})${c.description ? `: ${c.description}` : ""}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (collections.length > 0) {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push("## Collections");
|
|
46
|
+
for (const c of collections) {
|
|
47
|
+
lines.push(
|
|
48
|
+
`- [${c.name}](${SITE_URL}/collections/${c.slug})${c.description ? `: ${c.description}` : ""}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (products.length > 0) {
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push("## Products");
|
|
56
|
+
for (const p of products) {
|
|
57
|
+
const slug = p.slug ?? p.id;
|
|
58
|
+
const price = `${p.currency ?? brand.currency} ${p.default_price}`;
|
|
59
|
+
const desc = p.description ? ` — ${p.description.replace(/\s+/g, " ").slice(0, 200)}` : "";
|
|
60
|
+
lines.push(`- [${p.name}](${SITE_URL}/products/${slug}) (${price})${desc}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push("## Information");
|
|
66
|
+
lines.push(`- [About](${SITE_URL}/about)`);
|
|
67
|
+
lines.push(`- [Support / FAQ](${SITE_URL}/faq)`);
|
|
68
|
+
lines.push(`- [Terms of Service](${SITE_URL}/terms)`);
|
|
69
|
+
lines.push(`- [Privacy Policy](${SITE_URL}/privacy)`);
|
|
70
|
+
lines.push(`- [Sitemap (XML)](${SITE_URL}/sitemap.xml)`);
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push("## Contact");
|
|
73
|
+
lines.push(`- Email: ${brand.contact.email}`);
|
|
74
|
+
lines.push(`- Phone: ${brand.contact.phone}`);
|
|
75
|
+
lines.push(`- Address: ${brand.contact.address}`);
|
|
76
|
+
|
|
77
|
+
return lines.join("\n") + "\n";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `/llms.txt` — machine-readable site index for LLMs (per llmstxt.org).
|
|
82
|
+
* Lets coding agents and chat assistants find products, categories, and
|
|
83
|
+
* support pages without scraping HTML. Plain Markdown so it streams cheaply
|
|
84
|
+
* into context windows.
|
|
85
|
+
*/
|
|
86
|
+
export async function GET(): Promise<Response> {
|
|
87
|
+
const body = await buildLlmsTxt();
|
|
88
|
+
return new Response(body, {
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
91
|
+
"Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Sign in — ${brand.name}`,
|
|
7
|
+
description: brand.account.loginSubtitle,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cimplify Link (the iframe in `<CimplifyAccount>`) handles sign-in
|
|
12
|
+
* automatically when no session exists. We just bounce /login to /account
|
|
13
|
+
* so consumers landing here get the right UI without a duplicate form.
|
|
14
|
+
*/
|
|
15
|
+
export default function LoginPage(): never {
|
|
16
|
+
redirect("/account");
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Page not found — ${brand.name}`,
|
|
7
|
+
description: "We couldn't find that page.",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function NotFound() {
|
|
11
|
+
return (
|
|
12
|
+
<section className="max-w-2xl mx-auto px-6 sm:px-8 py-20 text-center">
|
|
13
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-3">
|
|
14
|
+
404
|
|
15
|
+
</p>
|
|
16
|
+
<h1 className="text-[clamp(2.5rem,5vw,4rem)] font-bold mb-4 -tracking-[0.03em]">
|
|
17
|
+
Page not found.
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="text-muted-foreground leading-relaxed mb-8">
|
|
20
|
+
The URL you followed might be old, a typo, or pointing at a product
|
|
21
|
+
that's no longer in stock. Try the menu or head back home.
|
|
22
|
+
</p>
|
|
23
|
+
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
24
|
+
<Link
|
|
25
|
+
href="/shop"
|
|
26
|
+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
|
|
27
|
+
>
|
|
28
|
+
Browse the shop
|
|
29
|
+
</Link>
|
|
30
|
+
<Link
|
|
31
|
+
href="/"
|
|
32
|
+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-border hover:bg-muted transition-colors text-sm font-medium"
|
|
33
|
+
>
|
|
34
|
+
Back home
|
|
35
|
+
</Link>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { brand } from "@/lib/brand";
|
|
2
|
+
|
|
3
|
+
const SITE_URL =
|
|
4
|
+
process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OpenSearch description document — lets browsers add this site to the
|
|
8
|
+
* address bar's search engine list. When users press Tab after typing the
|
|
9
|
+
* domain, they get an inline search box that hits /search?q=...
|
|
10
|
+
*/
|
|
11
|
+
export async function GET(): Promise<Response> {
|
|
12
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
13
|
+
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
|
14
|
+
<ShortName>${escapeXml(brand.shortName)}</ShortName>
|
|
15
|
+
<Description>Search ${escapeXml(brand.name)}</Description>
|
|
16
|
+
<InputEncoding>UTF-8</InputEncoding>
|
|
17
|
+
<Url type="text/html" method="get" template="${SITE_URL}/search?q={searchTerms}" />
|
|
18
|
+
<Url type="application/opensearchdescription+xml" rel="self" template="${SITE_URL}/opensearch.xml" />
|
|
19
|
+
<moz:SearchForm>${SITE_URL}/search</moz:SearchForm>
|
|
20
|
+
</OpenSearchDescription>
|
|
21
|
+
`;
|
|
22
|
+
return new Response(xml, {
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/opensearchdescription+xml; charset=utf-8",
|
|
25
|
+
"Cache-Control": "public, max-age=86400",
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function escapeXml(s: string): string {
|
|
31
|
+
return s
|
|
32
|
+
.replace(/&/g, "&")
|
|
33
|
+
.replace(/</g, "<")
|
|
34
|
+
.replace(/>/g, ">")
|
|
35
|
+
.replace(/"/g, """)
|
|
36
|
+
.replace(/'/g, "'");
|
|
37
|
+
}
|
|
@@ -12,11 +12,11 @@ import { TradeInCta } from "@/components/trade-in-cta";
|
|
|
12
12
|
import { Newsletter } from "@/components/newsletter";
|
|
13
13
|
import { SectionHeading } from "@/components/section-heading";
|
|
14
14
|
import { StoreProductCard } from "@/components/store-product-card";
|
|
15
|
+
import { brand } from "@/lib/brand";
|
|
15
16
|
|
|
16
17
|
export const metadata: Metadata = {
|
|
17
|
-
title:
|
|
18
|
-
description:
|
|
19
|
-
"Authorised dealer for Apple, Samsung, Sony, Bose. Same-day delivery, two-year warranty, instalment payments — across Accra and West Africa.",
|
|
18
|
+
title: brand.hero.title,
|
|
19
|
+
description: brand.description,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
interface CollectionWithProducts {
|
|
@@ -57,55 +57,7 @@ async function getHomeData() {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const
|
|
61
|
-
{
|
|
62
|
-
label: "Delivery",
|
|
63
|
-
value: "Same-day in Accra",
|
|
64
|
-
description: "Orders before 4pm, free over GH₵500.",
|
|
65
|
-
icon: (
|
|
66
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
67
|
-
<path d="M3 7h12v8H3zM15 10h4l3 3v2h-7z" strokeLinejoin="round" />
|
|
68
|
-
<circle cx="7" cy="17" r="1.5" />
|
|
69
|
-
<circle cx="18" cy="17" r="1.5" />
|
|
70
|
-
</svg>
|
|
71
|
-
),
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
label: "Warranty",
|
|
75
|
-
value: "2 years on everything",
|
|
76
|
-
description: "1yr manufacturer + 1yr Currents.",
|
|
77
|
-
icon: (
|
|
78
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
79
|
-
<path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" strokeLinejoin="round" />
|
|
80
|
-
<path d="M9 12l2 2 4-4" strokeLinecap="round" strokeLinejoin="round" />
|
|
81
|
-
</svg>
|
|
82
|
-
),
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
label: "Payment",
|
|
86
|
-
value: "Pay in 3, interest-free",
|
|
87
|
-
description: "Or 6/12 months via Fidelity Bank.",
|
|
88
|
-
icon: (
|
|
89
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
90
|
-
<rect x="3" y="6" width="18" height="12" rx="2" />
|
|
91
|
-
<line x1="3" y1="10" x2="21" y2="10" />
|
|
92
|
-
</svg>
|
|
93
|
-
),
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
label: "Genuine",
|
|
97
|
-
value: "Authorised dealer",
|
|
98
|
-
description: "Direct from Apple, Samsung, Sony, Bose.",
|
|
99
|
-
icon: (
|
|
100
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
101
|
-
<circle cx="12" cy="12" r="9" />
|
|
102
|
-
<path d="M8 12l3 3 5-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
103
|
-
</svg>
|
|
104
|
-
),
|
|
105
|
-
},
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
const HERO_IMAGE =
|
|
60
|
+
const HERO_FALLBACK_IMAGE =
|
|
109
61
|
"https://images.unsplash.com/photo-1592899677977-9c10ca588bbd?w=1200&h=1200&fit=crop&auto=format&q=85";
|
|
110
62
|
|
|
111
63
|
export default async function HomePage() {
|
|
@@ -117,37 +69,28 @@ export default async function HomePage() {
|
|
|
117
69
|
<FeatureHero
|
|
118
70
|
eyebrow="Featured · in stock"
|
|
119
71
|
badge="Just landed"
|
|
120
|
-
title={
|
|
121
|
-
|
|
122
|
-
Built for the way<br />
|
|
123
|
-
you actually work.
|
|
124
|
-
</>
|
|
125
|
-
}
|
|
126
|
-
description={
|
|
127
|
-
heroProduct?.description ??
|
|
128
|
-
"Apple, Samsung, Sony, Bose — direct from the source, with a two-year warranty and same-day Accra delivery on every order."
|
|
129
|
-
}
|
|
72
|
+
title={brand.hero.title}
|
|
73
|
+
description={heroProduct?.description ?? brand.hero.subtitle}
|
|
130
74
|
primaryCta={{
|
|
131
|
-
label: heroProduct ? `Shop ${heroProduct.name}` :
|
|
132
|
-
href: heroProduct ?
|
|
75
|
+
label: heroProduct ? `Shop ${heroProduct.name}` : brand.hero.primaryCtaLabel,
|
|
76
|
+
href: heroProduct ? `/products/${heroProduct.slug ?? heroProduct.id}` : "/shop",
|
|
133
77
|
}}
|
|
134
|
-
secondaryCta={
|
|
135
|
-
|
|
78
|
+
secondaryCta={
|
|
79
|
+
brand.hero.secondaryCtaLabel && brand.hero.secondaryCtaHref
|
|
80
|
+
? { label: brand.hero.secondaryCtaLabel, href: brand.hero.secondaryCtaHref }
|
|
81
|
+
: undefined
|
|
82
|
+
}
|
|
83
|
+
imageUrl={heroProduct?.image_url ?? heroProduct?.images?.[0] ?? HERO_FALLBACK_IMAGE}
|
|
136
84
|
imageAlt={heroProduct?.name ?? "Featured product"}
|
|
137
85
|
/>
|
|
138
86
|
|
|
139
|
-
<TrustBar
|
|
87
|
+
<TrustBar />
|
|
140
88
|
|
|
141
89
|
<Suspense fallback={<CategoryTilesSkeleton />}>
|
|
142
90
|
<CategoryTiles categories={categories} />
|
|
143
91
|
</Suspense>
|
|
144
92
|
|
|
145
|
-
<PromoBanner
|
|
146
|
-
badge="Limited · ends Sunday"
|
|
147
|
-
title="Trade in your old phone, get up to GH₵3,500 off."
|
|
148
|
-
body="Send us your last-gen iPhone or Galaxy. We'll quote within 48 hours and credit the value toward your next upgrade."
|
|
149
|
-
cta={{ label: "Start trade-in", href: "/about" }}
|
|
150
|
-
/>
|
|
93
|
+
<PromoBanner />
|
|
151
94
|
|
|
152
95
|
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
153
96
|
<SectionHeading
|
|
@@ -1,100 +1,43 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
2
3
|
|
|
3
4
|
export const metadata: Metadata = {
|
|
4
|
-
title:
|
|
5
|
-
description:
|
|
6
|
-
"How Currents Electronics collects, uses, and protects your personal data.",
|
|
5
|
+
title: `Privacy Policy — ${brand.name}`,
|
|
6
|
+
description: `How ${brand.name} collects, uses, and protects your personal data.`,
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export default function PrivacyPage() {
|
|
10
|
+
const p = brand.privacy;
|
|
10
11
|
return (
|
|
11
12
|
<article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
|
|
12
13
|
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
|
|
13
|
-
|
|
14
|
+
{p.eyebrow}
|
|
14
15
|
</p>
|
|
15
16
|
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
|
|
16
|
-
|
|
17
|
+
{p.title}
|
|
17
18
|
</h1>
|
|
18
19
|
<p className="text-sm text-muted-foreground not-prose mb-10">
|
|
19
|
-
Last updated:
|
|
20
|
+
Last updated: {p.lastUpdated}
|
|
20
21
|
</p>
|
|
21
22
|
|
|
22
23
|
<section className="space-y-5 leading-relaxed text-foreground/90">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<li>To send order updates by SMS and email.</li>
|
|
41
|
-
<li>
|
|
42
|
-
To send occasional product news and deals — only if you opted in.
|
|
43
|
-
Unsubscribe anytime in one click.
|
|
44
|
-
</li>
|
|
45
|
-
<li>
|
|
46
|
-
To analyse aggregated, anonymised browsing patterns so we can
|
|
47
|
-
improve the site and recommend products.
|
|
48
|
-
</li>
|
|
49
|
-
</ul>
|
|
50
|
-
|
|
51
|
-
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
52
|
-
Who we share it with
|
|
53
|
-
</h2>
|
|
54
|
-
<p>
|
|
55
|
-
We share data only with the partners required to fulfil your order
|
|
56
|
-
and warranty: payment processors (Paystack, Stripe, Fidelity Bank),
|
|
57
|
-
delivery couriers (DHL, local riders), manufacturer warranty portals
|
|
58
|
-
(for serial registration), and our email/SMS providers. We do not
|
|
59
|
-
sell or rent personal data to third parties — ever.
|
|
60
|
-
</p>
|
|
61
|
-
|
|
62
|
-
<h2 className="text-2xl font-semibold -tracking-[0.02em]">Cookies</h2>
|
|
63
|
-
<p>
|
|
64
|
-
We use a small number of strictly-necessary cookies to keep your cart
|
|
65
|
-
working and your session signed in, plus optional analytics cookies
|
|
66
|
-
(you can disable these in our cookie banner).
|
|
67
|
-
</p>
|
|
68
|
-
|
|
69
|
-
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
70
|
-
Your rights
|
|
71
|
-
</h2>
|
|
72
|
-
<p>
|
|
73
|
-
Under the Ghana Data Protection Act, 2012 (Act 843), you have the
|
|
74
|
-
right to access the data we hold about you, correct it, or have it
|
|
75
|
-
deleted (subject to our tax-record retention obligations). Email{" "}
|
|
76
|
-
<a
|
|
77
|
-
href="mailto:privacy@currentselectronics.test"
|
|
78
|
-
className="text-primary font-semibold hover:underline"
|
|
79
|
-
>
|
|
80
|
-
privacy@currentselectronics.test
|
|
81
|
-
</a>{" "}
|
|
82
|
-
and we'll respond within 14 days.
|
|
83
|
-
</p>
|
|
84
|
-
|
|
85
|
-
<h2 className="text-2xl font-semibold -tracking-[0.02em]">Retention</h2>
|
|
86
|
-
<p>
|
|
87
|
-
We keep order records for 7 years to comply with Ghanaian tax law.
|
|
88
|
-
Warranty records are retained for the life of the warranty plus 24
|
|
89
|
-
months. Marketing-list opt-ins are kept until you unsubscribe.
|
|
90
|
-
</p>
|
|
91
|
-
|
|
92
|
-
<h2 className="text-2xl font-semibold -tracking-[0.02em]">Changes</h2>
|
|
93
|
-
<p>
|
|
94
|
-
If we make material changes to this policy, we'll email
|
|
95
|
-
customers on our marketing list and update the "last updated"
|
|
96
|
-
date above.
|
|
97
|
-
</p>
|
|
24
|
+
{p.sections.map((s) => (
|
|
25
|
+
<div key={s.heading}>
|
|
26
|
+
<h2 className="text-2xl font-semibold mt-0 -tracking-[0.02em]">{s.heading}</h2>
|
|
27
|
+
{typeof s.body === "string" ? (
|
|
28
|
+
<p>{s.body}</p>
|
|
29
|
+
) : (
|
|
30
|
+
<>
|
|
31
|
+
<p>{s.body.intro}</p>
|
|
32
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
33
|
+
{s.body.bullets.map((b) => (
|
|
34
|
+
<li key={b}>{b}</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
</>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
98
41
|
</section>
|
|
99
42
|
</article>
|
|
100
43
|
);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { notFound } from "next/navigation";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
6
|
+
import {
|
|
7
|
+
getServerClient,
|
|
8
|
+
tags,
|
|
9
|
+
type Product,
|
|
10
|
+
type ProductWithDetails,
|
|
11
|
+
} from "@cimplify/sdk/server";
|
|
12
|
+
import { ProductDetail } from "./product-detail";
|
|
13
|
+
import { brand } from "@/lib/brand";
|
|
14
|
+
|
|
15
|
+
const SITE_URL =
|
|
16
|
+
process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
|
|
17
|
+
|
|
18
|
+
function productLd(product: ProductWithDetails) {
|
|
19
|
+
const image = product.image_url ?? product.images?.[0];
|
|
20
|
+
const inStock = product.inventory_status?.in_stock !== false;
|
|
21
|
+
return {
|
|
22
|
+
"@context": "https://schema.org",
|
|
23
|
+
"@type": "Product",
|
|
24
|
+
name: product.name,
|
|
25
|
+
description: product.description ?? undefined,
|
|
26
|
+
image: image ? [image] : undefined,
|
|
27
|
+
sku: product.id,
|
|
28
|
+
brand: { "@type": "Brand", name: brand.name },
|
|
29
|
+
offers: {
|
|
30
|
+
"@type": "Offer",
|
|
31
|
+
price: product.default_price,
|
|
32
|
+
priceCurrency: product.currency ?? brand.currency,
|
|
33
|
+
availability: inStock
|
|
34
|
+
? "https://schema.org/InStock"
|
|
35
|
+
: "https://schema.org/OutOfStock",
|
|
36
|
+
url: `${SITE_URL}/products/${product.slug ?? product.id}`,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ProductData {
|
|
42
|
+
product: ProductWithDetails;
|
|
43
|
+
related: Product[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getProduct(slug: string): Promise<ProductData | null> {
|
|
47
|
+
"use cache";
|
|
48
|
+
cacheTag(tags.product(slug), tags.products());
|
|
49
|
+
cacheLife("hours");
|
|
50
|
+
|
|
51
|
+
const client = getServerClient();
|
|
52
|
+
const r = await client.catalogue.getProductBySlug(slug);
|
|
53
|
+
if (!r.ok) return null;
|
|
54
|
+
|
|
55
|
+
const related = r.value.category_id
|
|
56
|
+
? await client.catalogue
|
|
57
|
+
.getCategoryProducts(r.value.category_id)
|
|
58
|
+
.then((res) =>
|
|
59
|
+
res.ok
|
|
60
|
+
? (
|
|
61
|
+
((res.value as { items?: Product[] }).items ??
|
|
62
|
+
(res.value as Product[])) as Product[]
|
|
63
|
+
).filter((p) => p.id !== r.value.id).slice(0, 4)
|
|
64
|
+
: [],
|
|
65
|
+
)
|
|
66
|
+
: [];
|
|
67
|
+
|
|
68
|
+
return { product: r.value, related };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function generateMetadata({
|
|
72
|
+
params,
|
|
73
|
+
}: {
|
|
74
|
+
params: Promise<{ slug: string }>;
|
|
75
|
+
}): Promise<Metadata> {
|
|
76
|
+
const { slug } = await params;
|
|
77
|
+
const data = await getProduct(slug);
|
|
78
|
+
if (!data) return {};
|
|
79
|
+
const { product } = data;
|
|
80
|
+
const image = product.image_url ?? product.images?.[0];
|
|
81
|
+
return {
|
|
82
|
+
title: `${product.name} — ${brand.name}`,
|
|
83
|
+
description: product.description ?? undefined,
|
|
84
|
+
openGraph: {
|
|
85
|
+
title: product.name,
|
|
86
|
+
description: product.description ?? undefined,
|
|
87
|
+
images: image ? [{ url: image }] : undefined,
|
|
88
|
+
type: "website",
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default async function ProductPage({
|
|
94
|
+
params,
|
|
95
|
+
}: {
|
|
96
|
+
params: Promise<{ slug: string }>;
|
|
97
|
+
}) {
|
|
98
|
+
return (
|
|
99
|
+
<Suspense fallback={<ProductSkeleton />}>
|
|
100
|
+
<ProductContent params={params} />
|
|
101
|
+
</Suspense>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function ProductContent({
|
|
106
|
+
params,
|
|
107
|
+
}: {
|
|
108
|
+
params: Promise<{ slug: string }>;
|
|
109
|
+
}) {
|
|
110
|
+
const { slug } = await params;
|
|
111
|
+
const data = await getProduct(slug);
|
|
112
|
+
if (!data) notFound();
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
<script
|
|
117
|
+
type="application/ld+json"
|
|
118
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(productLd(data.product)) }}
|
|
119
|
+
/>
|
|
120
|
+
<nav
|
|
121
|
+
aria-label="Breadcrumb"
|
|
122
|
+
className="max-w-7xl mx-auto px-6 sm:px-8 pt-6 text-[12px] font-mono text-muted-foreground flex items-center gap-2"
|
|
123
|
+
>
|
|
124
|
+
<Link href="/" className="hover:text-foreground transition-colors">
|
|
125
|
+
Home
|
|
126
|
+
</Link>
|
|
127
|
+
<span>/</span>
|
|
128
|
+
<Link href="/shop" className="hover:text-foreground transition-colors">
|
|
129
|
+
Shop
|
|
130
|
+
</Link>
|
|
131
|
+
{data.product.category?.slug && (
|
|
132
|
+
<>
|
|
133
|
+
<span>/</span>
|
|
134
|
+
<Link
|
|
135
|
+
href={`/categories/${data.product.category.slug}`}
|
|
136
|
+
className="hover:text-foreground transition-colors"
|
|
137
|
+
>
|
|
138
|
+
{data.product.category.name}
|
|
139
|
+
</Link>
|
|
140
|
+
</>
|
|
141
|
+
)}
|
|
142
|
+
<span>/</span>
|
|
143
|
+
<span className="text-foreground/90 truncate">{data.product.name}</span>
|
|
144
|
+
</nav>
|
|
145
|
+
<ProductDetail product={data.product} related={data.related} />
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function ProductSkeleton() {
|
|
151
|
+
return (
|
|
152
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-10">
|
|
153
|
+
<div className="grid grid-cols-1 lg:grid-cols-[1.1fr_1fr] gap-10 items-start">
|
|
154
|
+
<div className="aspect-square bg-muted rounded-3xl animate-pulse" />
|
|
155
|
+
<div className="space-y-4">
|
|
156
|
+
<div className="h-3 w-24 bg-muted rounded animate-pulse" />
|
|
157
|
+
<div className="h-10 w-3/4 bg-muted rounded animate-pulse" />
|
|
158
|
+
<div className="h-7 w-32 bg-muted rounded animate-pulse" />
|
|
159
|
+
<div className="h-20 w-full bg-muted rounded animate-pulse mt-6" />
|
|
160
|
+
<div className="h-12 w-full bg-muted rounded animate-pulse mt-4" />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</section>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import { ProductPage as SdkProductPage } from "@cimplify/sdk/react";
|
|
6
|
+
import type { Product, ProductWithDetails } from "@cimplify/sdk";
|
|
7
|
+
import { StoreProductCard } from "@/components/store-product-card";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Client island for the product detail page.
|
|
11
|
+
*
|
|
12
|
+
* - Receives a server-fetched `ProductWithDetails` (no client refetch).
|
|
13
|
+
* - Renders the SDK `<ProductPage>` which picks the right layout
|
|
14
|
+
* (Default / Wholesale / Service / Bundle / Composite) automatically.
|
|
15
|
+
* - On add-to-cart success, routes to `/cart`.
|
|
16
|
+
* - Custom Next.js Image renderer for optimised, lazy-loaded gallery shots.
|
|
17
|
+
* - Renders a "You may also like" rail of in-category products below.
|
|
18
|
+
*/
|
|
19
|
+
export function ProductDetail({
|
|
20
|
+
product,
|
|
21
|
+
related,
|
|
22
|
+
}: {
|
|
23
|
+
product: ProductWithDetails;
|
|
24
|
+
related: Product[];
|
|
25
|
+
}) {
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
<SdkProductPage
|
|
31
|
+
product={product}
|
|
32
|
+
showRelated={false}
|
|
33
|
+
onAddToCart={() => {
|
|
34
|
+
router.push("/cart");
|
|
35
|
+
}}
|
|
36
|
+
renderImage={({ src, alt, className }) => (
|
|
37
|
+
<Image
|
|
38
|
+
src={src}
|
|
39
|
+
alt={alt}
|
|
40
|
+
width={1200}
|
|
41
|
+
height={1200}
|
|
42
|
+
className={className}
|
|
43
|
+
style={{ width: "100%", height: "auto", objectFit: "cover" }}
|
|
44
|
+
priority
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
className="max-w-7xl mx-auto px-6 sm:px-8 py-8 sm:py-10"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{related.length > 0 && (
|
|
51
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-16 border-t border-border mt-8">
|
|
52
|
+
<div className="flex items-end justify-between gap-6 mb-8">
|
|
53
|
+
<div>
|
|
54
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
|
|
55
|
+
You may also like
|
|
56
|
+
</p>
|
|
57
|
+
<h2 className="text-[clamp(1.5rem,2.5vw,2rem)] font-bold m-0 -tracking-[0.025em]">
|
|
58
|
+
More from this category.
|
|
59
|
+
</h2>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
63
|
+
{related.map((p) => (
|
|
64
|
+
<StoreProductCard key={p.id} product={p} />
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
)}
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|