@cimplify/sdk 0.40.0 → 0.41.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.
Files changed (227) hide show
  1. package/dist/dispatcher.mjs +3 -2
  2. package/dist/mock/cli.mjs +30 -6
  3. package/dist/mock/library.js +30 -6
  4. package/dist/mock/library.mjs +30 -6
  5. package/dist/mock/msw.js +30 -6
  6. package/dist/mock/msw.mjs +30 -6
  7. package/dist/styles.css +1 -1
  8. package/package.json +1 -1
  9. package/templates/storefront-bakery/.env.example +5 -0
  10. package/templates/storefront-bakery/AGENTS.md +57 -0
  11. package/templates/storefront-bakery/app/about/page.tsx +21 -44
  12. package/templates/storefront-bakery/app/categories/[slug]/page.tsx +2 -1
  13. package/templates/storefront-bakery/app/collections/[slug]/page.tsx +2 -1
  14. package/templates/storefront-bakery/app/error.tsx +60 -0
  15. package/templates/storefront-bakery/app/faq/page.tsx +13 -89
  16. package/templates/storefront-bakery/app/layout.tsx +37 -2
  17. package/templates/storefront-bakery/app/llms.txt/route.ts +94 -0
  18. package/templates/storefront-bakery/app/not-found.tsx +39 -0
  19. package/templates/storefront-bakery/app/page.tsx +6 -6
  20. package/templates/storefront-bakery/app/privacy/page.tsx +24 -68
  21. package/templates/storefront-bakery/app/robots.ts +18 -0
  22. package/templates/storefront-bakery/app/shop/page.tsx +3 -2
  23. package/templates/storefront-bakery/app/sitemap.ts +62 -0
  24. package/templates/storefront-bakery/app/terms/page.tsx +24 -82
  25. package/templates/storefront-bakery/components/footer.tsx +88 -130
  26. package/templates/storefront-bakery/components/header.tsx +8 -16
  27. package/templates/storefront-bakery/lib/brand.ts +453 -0
  28. package/templates/storefront-fashion/.env.example +16 -0
  29. package/templates/storefront-fashion/AGENTS.md +52 -0
  30. package/templates/storefront-fashion/README.md +77 -0
  31. package/templates/storefront-fashion/app/about/page.tsx +41 -0
  32. package/templates/storefront-fashion/app/cart/page.tsx +9 -0
  33. package/templates/storefront-fashion/app/categories/[slug]/listing-client.tsx +19 -0
  34. package/templates/storefront-fashion/app/categories/[slug]/page.tsx +130 -0
  35. package/templates/storefront-fashion/app/checkout/page.tsx +17 -0
  36. package/templates/storefront-fashion/app/collections/[slug]/listing-client.tsx +20 -0
  37. package/templates/storefront-fashion/app/collections/[slug]/page.tsx +130 -0
  38. package/templates/storefront-fashion/app/error.tsx +61 -0
  39. package/templates/storefront-fashion/app/faq/page.tsx +46 -0
  40. package/templates/storefront-fashion/app/globals.css +46 -0
  41. package/templates/storefront-fashion/app/layout.tsx +76 -0
  42. package/templates/storefront-fashion/app/llms.txt/route.ts +94 -0
  43. package/templates/storefront-fashion/app/not-found.tsx +39 -0
  44. package/templates/storefront-fashion/app/orders/[id]/page.tsx +24 -0
  45. package/templates/storefront-fashion/app/page.tsx +183 -0
  46. package/templates/storefront-fashion/app/privacy/page.tsx +44 -0
  47. package/templates/storefront-fashion/app/products/[slug]/page.tsx +165 -0
  48. package/templates/storefront-fashion/app/products/[slug]/product-detail.tsx +71 -0
  49. package/templates/storefront-fashion/app/robots.ts +18 -0
  50. package/templates/storefront-fashion/app/shop/page.tsx +63 -0
  51. package/templates/storefront-fashion/app/shop/shop-client.tsx +32 -0
  52. package/templates/storefront-fashion/app/sitemap.ts +59 -0
  53. package/templates/storefront-fashion/app/terms/page.tsx +44 -0
  54. package/templates/storefront-fashion/components/brand-marquee.tsx +27 -0
  55. package/templates/storefront-fashion/components/cart-pill.tsx +32 -0
  56. package/templates/storefront-fashion/components/category-grid.tsx +28 -0
  57. package/templates/storefront-fashion/components/category-tiles.tsx +104 -0
  58. package/templates/storefront-fashion/components/collection-strip.tsx +45 -0
  59. package/templates/storefront-fashion/components/feature-hero.tsx +82 -0
  60. package/templates/storefront-fashion/components/footer.tsx +153 -0
  61. package/templates/storefront-fashion/components/header.tsx +43 -0
  62. package/templates/storefront-fashion/components/hero.tsx +28 -0
  63. package/templates/storefront-fashion/components/nav-link.tsx +20 -0
  64. package/templates/storefront-fashion/components/newsletter.tsx +50 -0
  65. package/templates/storefront-fashion/components/promo-banner.tsx +41 -0
  66. package/templates/storefront-fashion/components/providers.tsx +31 -0
  67. package/templates/storefront-fashion/components/section-heading.tsx +37 -0
  68. package/templates/storefront-fashion/components/store-product-card.tsx +83 -0
  69. package/templates/storefront-fashion/components/trade-in-cta.tsx +54 -0
  70. package/templates/storefront-fashion/components/trust-bar.tsx +66 -0
  71. package/templates/storefront-fashion/lib/brand.ts +421 -0
  72. package/templates/storefront-fashion/lib/cart.ts +27 -0
  73. package/templates/storefront-fashion/next.config.ts +40 -0
  74. package/templates/storefront-fashion/package.json +28 -0
  75. package/templates/storefront-fashion/postcss.config.mjs +7 -0
  76. package/templates/storefront-fashion/tsconfig.json +23 -0
  77. package/templates/storefront-grocery/.env.example +16 -0
  78. package/templates/storefront-grocery/AGENTS.md +39 -0
  79. package/templates/storefront-grocery/README.md +73 -0
  80. package/templates/storefront-grocery/app/about/page.tsx +38 -0
  81. package/templates/storefront-grocery/app/cart/page.tsx +9 -0
  82. package/templates/storefront-grocery/app/categories/[slug]/listing-client.tsx +19 -0
  83. package/templates/storefront-grocery/app/categories/[slug]/page.tsx +118 -0
  84. package/templates/storefront-grocery/app/checkout/page.tsx +17 -0
  85. package/templates/storefront-grocery/app/collections/[slug]/listing-client.tsx +20 -0
  86. package/templates/storefront-grocery/app/collections/[slug]/page.tsx +118 -0
  87. package/templates/storefront-grocery/app/error.tsx +60 -0
  88. package/templates/storefront-grocery/app/faq/page.tsx +46 -0
  89. package/templates/storefront-grocery/app/globals.css +45 -0
  90. package/templates/storefront-grocery/app/layout.tsx +75 -0
  91. package/templates/storefront-grocery/app/llms.txt/route.ts +94 -0
  92. package/templates/storefront-grocery/app/not-found.tsx +39 -0
  93. package/templates/storefront-grocery/app/orders/[id]/page.tsx +21 -0
  94. package/templates/storefront-grocery/app/page.tsx +97 -0
  95. package/templates/storefront-grocery/app/privacy/page.tsx +44 -0
  96. package/templates/storefront-grocery/app/robots.ts +18 -0
  97. package/templates/storefront-grocery/app/shop/page.tsx +31 -0
  98. package/templates/storefront-grocery/app/shop/shop-client.tsx +27 -0
  99. package/templates/storefront-grocery/app/sitemap.ts +62 -0
  100. package/templates/storefront-grocery/app/terms/page.tsx +44 -0
  101. package/templates/storefront-grocery/components/cart-pill.tsx +32 -0
  102. package/templates/storefront-grocery/components/category-grid.tsx +28 -0
  103. package/templates/storefront-grocery/components/collection-strip.tsx +45 -0
  104. package/templates/storefront-grocery/components/footer.tsx +148 -0
  105. package/templates/storefront-grocery/components/header.tsx +43 -0
  106. package/templates/storefront-grocery/components/hero.tsx +25 -0
  107. package/templates/storefront-grocery/components/nav-link.tsx +20 -0
  108. package/templates/storefront-grocery/components/providers.tsx +31 -0
  109. package/templates/storefront-grocery/components/store-product-card.tsx +83 -0
  110. package/templates/storefront-grocery/lib/brand.ts +284 -0
  111. package/templates/storefront-grocery/lib/cart.ts +27 -0
  112. package/templates/storefront-grocery/next.config.ts +40 -0
  113. package/templates/storefront-grocery/package.json +28 -0
  114. package/templates/storefront-grocery/postcss.config.mjs +7 -0
  115. package/templates/storefront-grocery/tsconfig.json +23 -0
  116. package/templates/storefront-restaurant/.env.example +16 -0
  117. package/templates/storefront-restaurant/AGENTS.md +39 -0
  118. package/templates/storefront-restaurant/README.md +73 -0
  119. package/templates/storefront-restaurant/app/about/page.tsx +38 -0
  120. package/templates/storefront-restaurant/app/cart/page.tsx +9 -0
  121. package/templates/storefront-restaurant/app/categories/[slug]/listing-client.tsx +19 -0
  122. package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +118 -0
  123. package/templates/storefront-restaurant/app/checkout/page.tsx +17 -0
  124. package/templates/storefront-restaurant/app/collections/[slug]/listing-client.tsx +20 -0
  125. package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +118 -0
  126. package/templates/storefront-restaurant/app/error.tsx +60 -0
  127. package/templates/storefront-restaurant/app/faq/page.tsx +46 -0
  128. package/templates/storefront-restaurant/app/globals.css +44 -0
  129. package/templates/storefront-restaurant/app/layout.tsx +80 -0
  130. package/templates/storefront-restaurant/app/llms.txt/route.ts +94 -0
  131. package/templates/storefront-restaurant/app/not-found.tsx +39 -0
  132. package/templates/storefront-restaurant/app/orders/[id]/page.tsx +21 -0
  133. package/templates/storefront-restaurant/app/page.tsx +97 -0
  134. package/templates/storefront-restaurant/app/privacy/page.tsx +44 -0
  135. package/templates/storefront-restaurant/app/robots.ts +18 -0
  136. package/templates/storefront-restaurant/app/shop/page.tsx +31 -0
  137. package/templates/storefront-restaurant/app/shop/shop-client.tsx +27 -0
  138. package/templates/storefront-restaurant/app/sitemap.ts +62 -0
  139. package/templates/storefront-restaurant/app/terms/page.tsx +44 -0
  140. package/templates/storefront-restaurant/components/cart-pill.tsx +32 -0
  141. package/templates/storefront-restaurant/components/category-grid.tsx +28 -0
  142. package/templates/storefront-restaurant/components/collection-strip.tsx +45 -0
  143. package/templates/storefront-restaurant/components/footer.tsx +148 -0
  144. package/templates/storefront-restaurant/components/header.tsx +43 -0
  145. package/templates/storefront-restaurant/components/hero.tsx +25 -0
  146. package/templates/storefront-restaurant/components/nav-link.tsx +20 -0
  147. package/templates/storefront-restaurant/components/product-modal.tsx +105 -0
  148. package/templates/storefront-restaurant/components/providers.tsx +31 -0
  149. package/templates/storefront-restaurant/components/store-product-card.tsx +83 -0
  150. package/templates/storefront-restaurant/lib/brand.ts +287 -0
  151. package/templates/storefront-restaurant/lib/cart.ts +27 -0
  152. package/templates/storefront-restaurant/next.config.ts +40 -0
  153. package/templates/storefront-restaurant/package.json +28 -0
  154. package/templates/storefront-restaurant/postcss.config.mjs +7 -0
  155. package/templates/storefront-restaurant/tsconfig.json +23 -0
  156. package/templates/storefront-retail/.env.example +5 -0
  157. package/templates/storefront-retail/AGENTS.md +64 -0
  158. package/templates/storefront-retail/app/about/page.tsx +24 -54
  159. package/templates/storefront-retail/app/categories/[slug]/page.tsx +50 -37
  160. package/templates/storefront-retail/app/collections/[slug]/page.tsx +50 -37
  161. package/templates/storefront-retail/app/error.tsx +61 -0
  162. package/templates/storefront-retail/app/faq/page.tsx +11 -92
  163. package/templates/storefront-retail/app/layout.tsx +37 -8
  164. package/templates/storefront-retail/app/llms.txt/route.ts +94 -0
  165. package/templates/storefront-retail/app/not-found.tsx +39 -0
  166. package/templates/storefront-retail/app/page.tsx +111 -26
  167. package/templates/storefront-retail/app/privacy/page.tsx +24 -81
  168. package/templates/storefront-retail/app/products/[slug]/page.tsx +165 -0
  169. package/templates/storefront-retail/app/products/[slug]/product-detail.tsx +71 -0
  170. package/templates/storefront-retail/app/robots.ts +18 -0
  171. package/templates/storefront-retail/app/shop/page.tsx +36 -3
  172. package/templates/storefront-retail/app/shop/shop-client.tsx +6 -1
  173. package/templates/storefront-retail/app/sitemap.ts +59 -0
  174. package/templates/storefront-retail/app/terms/page.tsx +24 -97
  175. package/templates/storefront-retail/components/brand-marquee.tsx +27 -0
  176. package/templates/storefront-retail/components/category-tiles.tsx +104 -0
  177. package/templates/storefront-retail/components/feature-hero.tsx +84 -0
  178. package/templates/storefront-retail/components/footer.tsx +85 -120
  179. package/templates/storefront-retail/components/header.tsx +10 -12
  180. package/templates/storefront-retail/components/newsletter.tsx +50 -0
  181. package/templates/storefront-retail/components/promo-banner.tsx +41 -0
  182. package/templates/storefront-retail/components/section-heading.tsx +37 -0
  183. package/templates/storefront-retail/components/store-product-card.tsx +7 -7
  184. package/templates/storefront-retail/components/trade-in-cta.tsx +54 -0
  185. package/templates/storefront-retail/components/trust-bar.tsx +66 -0
  186. package/templates/storefront-retail/lib/brand.ts +567 -0
  187. package/templates/storefront-services/.env.example +16 -0
  188. package/templates/storefront-services/AGENTS.md +41 -0
  189. package/templates/storefront-services/README.md +73 -0
  190. package/templates/storefront-services/app/about/page.tsx +38 -0
  191. package/templates/storefront-services/app/cart/page.tsx +9 -0
  192. package/templates/storefront-services/app/categories/[slug]/listing-client.tsx +19 -0
  193. package/templates/storefront-services/app/categories/[slug]/page.tsx +118 -0
  194. package/templates/storefront-services/app/checkout/page.tsx +17 -0
  195. package/templates/storefront-services/app/collections/[slug]/listing-client.tsx +20 -0
  196. package/templates/storefront-services/app/collections/[slug]/page.tsx +118 -0
  197. package/templates/storefront-services/app/error.tsx +60 -0
  198. package/templates/storefront-services/app/faq/page.tsx +46 -0
  199. package/templates/storefront-services/app/globals.css +45 -0
  200. package/templates/storefront-services/app/layout.tsx +80 -0
  201. package/templates/storefront-services/app/llms.txt/route.ts +94 -0
  202. package/templates/storefront-services/app/not-found.tsx +39 -0
  203. package/templates/storefront-services/app/orders/[id]/page.tsx +21 -0
  204. package/templates/storefront-services/app/page.tsx +97 -0
  205. package/templates/storefront-services/app/privacy/page.tsx +44 -0
  206. package/templates/storefront-services/app/robots.ts +18 -0
  207. package/templates/storefront-services/app/shop/page.tsx +31 -0
  208. package/templates/storefront-services/app/shop/shop-client.tsx +27 -0
  209. package/templates/storefront-services/app/sitemap.ts +62 -0
  210. package/templates/storefront-services/app/terms/page.tsx +44 -0
  211. package/templates/storefront-services/components/cart-pill.tsx +32 -0
  212. package/templates/storefront-services/components/category-grid.tsx +28 -0
  213. package/templates/storefront-services/components/collection-strip.tsx +45 -0
  214. package/templates/storefront-services/components/footer.tsx +148 -0
  215. package/templates/storefront-services/components/header.tsx +43 -0
  216. package/templates/storefront-services/components/hero.tsx +25 -0
  217. package/templates/storefront-services/components/nav-link.tsx +20 -0
  218. package/templates/storefront-services/components/product-modal.tsx +105 -0
  219. package/templates/storefront-services/components/providers.tsx +31 -0
  220. package/templates/storefront-services/components/store-product-card.tsx +83 -0
  221. package/templates/storefront-services/lib/brand.ts +303 -0
  222. package/templates/storefront-services/lib/cart.ts +27 -0
  223. package/templates/storefront-services/next.config.ts +40 -0
  224. package/templates/storefront-services/package.json +28 -0
  225. package/templates/storefront-services/postcss.config.mjs +7 -0
  226. package/templates/storefront-services/tsconfig.json +23 -0
  227. /package/templates/{storefront-retail → storefront-grocery}/components/product-modal.tsx +0 -0
@@ -0,0 +1,41 @@
1
+ import Link from "next/link";
2
+ import { brand } from "@/lib/brand";
3
+
4
+ /**
5
+ * Full-width promo banner — reads brand.promo. Renders nothing if the
6
+ * brand doesn't define a promo, so this component is safe to drop in
7
+ * across templates that may or may not run a campaign.
8
+ */
9
+ export function PromoBanner() {
10
+ const promo = brand.promo;
11
+ if (!promo) return null;
12
+ return (
13
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-6">
14
+ <div className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-primary via-primary to-foreground text-primary-foreground p-8 sm:p-12 lg:p-16">
15
+ <div className="absolute -top-20 -right-20 w-72 h-72 rounded-full bg-background/10 blur-3xl pointer-events-none" />
16
+ <div className="absolute -bottom-32 -left-20 w-96 h-96 rounded-full bg-foreground/30 blur-3xl pointer-events-none" />
17
+ <div className="relative grid grid-cols-1 lg:grid-cols-[1fr_auto] gap-8 items-center">
18
+ <div className="max-w-2xl">
19
+ <span className="inline-flex items-center gap-2 mb-4 px-3 py-1 rounded-full bg-background/15 border border-background/25 text-[11px] font-mono uppercase tracking-[0.16em]">
20
+ <span className="w-1.5 h-1.5 rounded-full bg-background animate-pulse" />
21
+ {promo.badge}
22
+ </span>
23
+ <h2 className="text-[clamp(1.75rem,3.5vw,2.75rem)] font-bold m-0 mb-3 -tracking-[0.025em] leading-[1.1]">
24
+ {promo.title}
25
+ </h2>
26
+ <p className="text-base sm:text-lg opacity-90 leading-relaxed">{promo.body}</p>
27
+ </div>
28
+ <Link
29
+ href={promo.ctaHref}
30
+ className="inline-flex items-center gap-2 px-5 py-3 rounded-md bg-background text-foreground font-semibold text-sm hover:bg-background/90 transition-colors whitespace-nowrap"
31
+ >
32
+ {promo.ctaLabel}
33
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
34
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
35
+ </svg>
36
+ </Link>
37
+ </div>
38
+ </div>
39
+ </section>
40
+ );
41
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { useMemo, type ReactNode } from "react";
4
+ import { createCimplifyClient } from "@cimplify/sdk";
5
+ import { CimplifyProvider } from "@cimplify/sdk/react";
6
+
7
+ /**
8
+ * Boots the Cimplify SDK client once on the client-side and exposes it via
9
+ * <CimplifyProvider/>.
10
+ *
11
+ * Base-URL resolution:
12
+ * 1) If NEXT_PUBLIC_CIMPLIFY_API_URL is set, use it verbatim.
13
+ * 2) Otherwise use the current origin so requests flow through the
14
+ * Next.js rewrite in `next.config.ts` to the mock at :8787 (no CORS).
15
+ * 3) Fall back to 127.0.0.1:8787 only during SSR, when there's no window.
16
+ */
17
+ export function Providers({ children }: { children: ReactNode }) {
18
+ const client = useMemo(() => {
19
+ const baseUrl =
20
+ process.env.NEXT_PUBLIC_CIMPLIFY_API_URL?.trim() ||
21
+ (typeof window !== "undefined" ? window.location.origin : "http://127.0.0.1:8787");
22
+ const publicKey = process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY ?? "mock-dev";
23
+ return createCimplifyClient({
24
+ baseUrl,
25
+ publicKey,
26
+ suppressPublicKeyWarning: true,
27
+ });
28
+ }, []);
29
+
30
+ return <CimplifyProvider client={client}>{children}</CimplifyProvider>;
31
+ }
@@ -0,0 +1,37 @@
1
+ import Link from "next/link";
2
+
3
+ interface SectionHeadingProps {
4
+ eyebrow: string;
5
+ title: string;
6
+ description?: string;
7
+ link?: { label: string; href: string };
8
+ }
9
+
10
+ export function SectionHeading({ eyebrow, title, description, link }: SectionHeadingProps) {
11
+ return (
12
+ <div className="flex items-end justify-between gap-6 mb-8">
13
+ <div className="max-w-2xl">
14
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
15
+ {eyebrow}
16
+ </p>
17
+ <h2 className="text-[clamp(1.75rem,3vw,2.25rem)] font-bold m-0 -tracking-[0.025em]">
18
+ {title}
19
+ </h2>
20
+ {description && (
21
+ <p className="mt-2 text-muted-foreground">{description}</p>
22
+ )}
23
+ </div>
24
+ {link && (
25
+ <Link
26
+ href={link.href}
27
+ className="text-sm font-semibold text-primary hover:underline whitespace-nowrap hidden sm:inline-flex items-center gap-1"
28
+ >
29
+ {link.label}
30
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
31
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
32
+ </svg>
33
+ </Link>
34
+ )}
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import {
5
+ CardVariant,
6
+ FoodProductCard,
7
+ RetailProductCard,
8
+ WholesaleProductCard,
9
+ DigitalProductCard,
10
+ BundleProductCard,
11
+ CompositeProductCard,
12
+ StandardServiceCard,
13
+ RentalServiceCard,
14
+ AccommodationCard,
15
+ LeaseServiceCard,
16
+ SubscriptionCard,
17
+ } from "@cimplify/sdk/react";
18
+ import type { CardLayoutProps } from "@cimplify/sdk/react";
19
+ import type { Product } from "@cimplify/sdk";
20
+ import { PRODUCT_TYPE, RENDER_HINT, DURATION_UNIT } from "@cimplify/sdk";
21
+
22
+ const RENTAL_UNITS = new Set<string>([DURATION_UNIT.Days, DURATION_UNIT.Hours]);
23
+ const LEASE_UNITS = new Set<string>([DURATION_UNIT.Weeks, DURATION_UNIT.Months, DURATION_UNIT.Years]);
24
+
25
+ const VARIANT_CARDS: Record<CardVariant, React.ComponentType<CardLayoutProps>> = {
26
+ [CardVariant.Food]: FoodProductCard,
27
+ [CardVariant.Retail]: RetailProductCard,
28
+ [CardVariant.Wholesale]: WholesaleProductCard,
29
+ [CardVariant.Digital]: DigitalProductCard,
30
+ [CardVariant.Bundle]: BundleProductCard,
31
+ [CardVariant.Composite]: CompositeProductCard,
32
+ [CardVariant.Standard]: StandardServiceCard,
33
+ [CardVariant.Rental]: RentalServiceCard,
34
+ [CardVariant.Accommodation]: AccommodationCard,
35
+ [CardVariant.Lease]: LeaseServiceCard,
36
+ [CardVariant.Subscription]: SubscriptionCard,
37
+ };
38
+
39
+ function resolveVariant(p: Product): CardVariant {
40
+ if (p.type === PRODUCT_TYPE.Bundle) return CardVariant.Bundle;
41
+ if (p.type === PRODUCT_TYPE.Composite) return CardVariant.Composite;
42
+ if (p.quantity_pricing && p.quantity_pricing.length > 1) return CardVariant.Wholesale;
43
+ if (p.type === PRODUCT_TYPE.Digital) return CardVariant.Digital;
44
+ if (p.type === PRODUCT_TYPE.Service) {
45
+ if (p.duration_unit && RENTAL_UNITS.has(p.duration_unit)) return CardVariant.Rental;
46
+ if (p.duration_unit === DURATION_UNIT.Nights) return CardVariant.Accommodation;
47
+ if (p.duration_unit && LEASE_UNITS.has(p.duration_unit)) return CardVariant.Lease;
48
+ if (p.billing_plans && p.billing_plans.length > 0 && !p.duration_minutes) return CardVariant.Subscription;
49
+ return CardVariant.Standard;
50
+ }
51
+ if (p.render_hint === RENDER_HINT.Food) return CardVariant.Food;
52
+ return CardVariant.Retail;
53
+ }
54
+
55
+ interface Props {
56
+ product: Product;
57
+ /** Override the auto-detected card variant. */
58
+ variant?: CardVariant;
59
+ }
60
+
61
+ /**
62
+ * Variant-aware product card that links to the dedicated product page at
63
+ * `/products/<slug>`. Statically pre-renderable (no `useSearchParams`),
64
+ * with `prefetch` enabled so hover → instant nav. Full product page
65
+ * (vs a modal) is the right pattern for retail: spec sheets, multi-image
66
+ * galleries, reviews, comparison rails all need vertical real estate.
67
+ */
68
+ export function StoreProductCard({ product, variant }: Props) {
69
+ const slug = product.slug || product.id;
70
+ const Card = VARIANT_CARDS[variant ?? resolveVariant(product)];
71
+ const href = `/products/${encodeURIComponent(slug)}`;
72
+
73
+ return (
74
+ <Card
75
+ product={product}
76
+ renderLink={({ className, children }) => (
77
+ <Link href={href} className={className}>
78
+ {children}
79
+ </Link>
80
+ )}
81
+ />
82
+ );
83
+ }
@@ -0,0 +1,54 @@
1
+ import Link from "next/link";
2
+ import { brand } from "@/lib/brand";
3
+
4
+ export function TradeInCta() {
5
+ const t = brand.tradeIn;
6
+ if (!t) return null;
7
+ return (
8
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
9
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center bg-foreground text-background rounded-3xl p-8 sm:p-12 lg:p-14 overflow-hidden relative">
10
+ <div className="absolute top-0 right-0 w-1/2 h-full opacity-[0.04] [background-image:linear-gradient(135deg,white_1px,transparent_1px)] [background-size:24px_24px] pointer-events-none" />
11
+ <div>
12
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary-foreground/70 mb-3">
13
+ {t.eyebrow}
14
+ </p>
15
+ <h2 className="text-[clamp(1.75rem,3.5vw,2.5rem)] font-bold m-0 mb-4 -tracking-[0.025em] leading-[1.1]">
16
+ {t.title}
17
+ </h2>
18
+ <p className="text-background/75 leading-relaxed mb-6">{t.body}</p>
19
+ <div className="flex flex-wrap items-center gap-3">
20
+ <Link
21
+ href={t.primaryCtaHref}
22
+ 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"
23
+ >
24
+ {t.primaryCtaLabel}
25
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
26
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
27
+ </svg>
28
+ </Link>
29
+ <Link
30
+ href={t.secondaryCtaHref}
31
+ className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-background/25 hover:bg-background/10 transition-colors text-sm font-medium"
32
+ >
33
+ {t.secondaryCtaLabel}
34
+ </Link>
35
+ </div>
36
+ </div>
37
+ <div className="grid grid-cols-3 gap-3">
38
+ {t.steps.map((s) => (
39
+ <div
40
+ key={s.step}
41
+ className="rounded-2xl p-4 sm:p-5 bg-background/5 border border-background/10"
42
+ >
43
+ <p className="text-[10px] font-mono text-primary tabular-nums mb-3">{s.step}</p>
44
+ <p className="text-[13px] sm:text-sm font-semibold mb-1.5 -tracking-[0.015em]">
45
+ {s.title}
46
+ </p>
47
+ <p className="text-[11px] sm:text-xs text-background/65 leading-snug">{s.body}</p>
48
+ </div>
49
+ ))}
50
+ </div>
51
+ </div>
52
+ </section>
53
+ );
54
+ }
@@ -0,0 +1,66 @@
1
+ import { brand } from "@/lib/brand";
2
+
3
+ const ICONS: Record<string, React.ReactNode> = {
4
+ delivery: (
5
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
6
+ <path d="M3 7h12v8H3zM15 10h4l3 3v2h-7z" strokeLinejoin="round" />
7
+ <circle cx="7" cy="17" r="1.5" />
8
+ <circle cx="18" cy="17" r="1.5" />
9
+ </svg>
10
+ ),
11
+ warranty: (
12
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
13
+ <path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" strokeLinejoin="round" />
14
+ <path d="M9 12l2 2 4-4" strokeLinecap="round" strokeLinejoin="round" />
15
+ </svg>
16
+ ),
17
+ payment: (
18
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
19
+ <rect x="3" y="6" width="18" height="12" rx="2" />
20
+ <line x1="3" y1="10" x2="21" y2="10" />
21
+ </svg>
22
+ ),
23
+ verified: (
24
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
25
+ <circle cx="12" cy="12" r="9" />
26
+ <path d="M8 12l3 3 5-6" strokeLinecap="round" strokeLinejoin="round" />
27
+ </svg>
28
+ ),
29
+ support: (
30
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
31
+ <path d="M18 18a8 8 0 1 0-12 0" />
32
+ <path d="M3 18h4v3H3zM17 18h4v3h-4z" strokeLinejoin="round" />
33
+ </svg>
34
+ ),
35
+ };
36
+
37
+ const FALLBACK_ICON = (
38
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
39
+ <circle cx="12" cy="12" r="9" />
40
+ </svg>
41
+ );
42
+
43
+ export function TrustBar() {
44
+ const items = brand.trustItems;
45
+ if (!items || items.length === 0) return null;
46
+ return (
47
+ <section className="border-y border-border bg-card">
48
+ <div className="max-w-7xl mx-auto px-6 sm:px-8 py-10 grid grid-cols-2 md:grid-cols-4 gap-x-6 gap-y-8">
49
+ {items.map((it) => (
50
+ <div key={it.label} className="flex items-start gap-3">
51
+ <div className="grid place-items-center w-10 h-10 rounded-lg bg-primary/10 text-primary shrink-0">
52
+ {ICONS[it.iconKey] ?? FALLBACK_ICON}
53
+ </div>
54
+ <div className="min-w-0">
55
+ <p className="text-[10px] font-mono uppercase tracking-[0.16em] text-muted-foreground mb-0.5">
56
+ {it.label}
57
+ </p>
58
+ <p className="text-base font-semibold -tracking-[0.015em] mb-0.5">{it.value}</p>
59
+ <p className="text-xs text-muted-foreground leading-snug">{it.description}</p>
60
+ </div>
61
+ </div>
62
+ ))}
63
+ </div>
64
+ </section>
65
+ );
66
+ }