@cimplify/sdk 0.39.0 → 0.40.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 (72) hide show
  1. package/dist/dispatcher.mjs +39 -5
  2. package/dist/styles.css +1 -1
  3. package/package.json +1 -1
  4. package/templates/{storefront → storefront-bakery}/README.md +8 -1
  5. package/templates/storefront-bakery/app/about/page.tsx +61 -0
  6. package/templates/storefront-bakery/app/categories/[slug]/listing-client.tsx +19 -0
  7. package/templates/{storefront → storefront-bakery}/app/categories/[slug]/page.tsx +42 -10
  8. package/templates/storefront-bakery/app/collections/[slug]/listing-client.tsx +20 -0
  9. package/templates/{storefront → storefront-bakery}/app/collections/[slug]/page.tsx +42 -10
  10. package/templates/storefront-bakery/app/faq/page.tsx +122 -0
  11. package/templates/{storefront → storefront-bakery}/app/page.tsx +39 -6
  12. package/templates/storefront-bakery/app/privacy/page.tsx +88 -0
  13. package/templates/storefront-bakery/app/terms/page.tsx +102 -0
  14. package/templates/storefront-bakery/components/cart-pill.tsx +32 -0
  15. package/templates/storefront-bakery/components/footer.tsx +190 -0
  16. package/templates/storefront-bakery/components/header.tsx +51 -0
  17. package/templates/storefront-bakery/components/nav-link.tsx +20 -0
  18. package/templates/{storefront → storefront-bakery}/components/store-product-card.tsx +9 -25
  19. package/templates/{storefront → storefront-bakery}/next.config.ts +3 -4
  20. package/templates/{storefront → storefront-bakery}/package.json +1 -1
  21. package/templates/storefront-retail/.env.example +11 -0
  22. package/templates/storefront-retail/README.md +77 -0
  23. package/templates/storefront-retail/app/about/page.tsx +71 -0
  24. package/templates/storefront-retail/app/cart/page.tsx +9 -0
  25. package/templates/storefront-retail/app/categories/[slug]/listing-client.tsx +19 -0
  26. package/templates/storefront-retail/app/categories/[slug]/page.tsx +117 -0
  27. package/templates/storefront-retail/app/checkout/page.tsx +17 -0
  28. package/templates/storefront-retail/app/collections/[slug]/listing-client.tsx +20 -0
  29. package/templates/storefront-retail/app/collections/[slug]/page.tsx +117 -0
  30. package/templates/storefront-retail/app/faq/page.tsx +127 -0
  31. package/templates/storefront-retail/app/globals.css +47 -0
  32. package/templates/storefront-retail/app/layout.tsx +46 -0
  33. package/templates/storefront-retail/app/orders/[id]/page.tsx +24 -0
  34. package/templates/storefront-retail/app/page.tsx +97 -0
  35. package/templates/storefront-retail/app/privacy/page.tsx +101 -0
  36. package/templates/storefront-retail/app/shop/page.tsx +30 -0
  37. package/templates/storefront-retail/app/shop/shop-client.tsx +27 -0
  38. package/templates/storefront-retail/app/terms/page.tsx +117 -0
  39. package/templates/storefront-retail/components/cart-pill.tsx +32 -0
  40. package/templates/storefront-retail/components/category-grid.tsx +28 -0
  41. package/templates/storefront-retail/components/collection-strip.tsx +45 -0
  42. package/templates/storefront-retail/components/footer.tsx +188 -0
  43. package/templates/storefront-retail/components/header.tsx +47 -0
  44. package/templates/storefront-retail/components/hero.tsx +28 -0
  45. package/templates/storefront-retail/components/nav-link.tsx +20 -0
  46. package/templates/storefront-retail/components/product-modal.tsx +105 -0
  47. package/templates/storefront-retail/components/providers.tsx +31 -0
  48. package/templates/storefront-retail/components/store-product-card.tsx +83 -0
  49. package/templates/storefront-retail/lib/cart.ts +27 -0
  50. package/templates/storefront-retail/next.config.ts +40 -0
  51. package/templates/storefront-retail/package.json +28 -0
  52. package/templates/storefront-retail/postcss.config.mjs +7 -0
  53. package/templates/storefront-retail/tsconfig.json +23 -0
  54. package/templates/storefront/components/footer.tsx +0 -19
  55. package/templates/storefront/components/header.tsx +0 -41
  56. package/templates/storefront/lib/server-client.ts +0 -27
  57. /package/templates/{storefront → storefront-bakery}/.env.example +0 -0
  58. /package/templates/{storefront → storefront-bakery}/app/cart/page.tsx +0 -0
  59. /package/templates/{storefront → storefront-bakery}/app/checkout/page.tsx +0 -0
  60. /package/templates/{storefront → storefront-bakery}/app/globals.css +0 -0
  61. /package/templates/{storefront → storefront-bakery}/app/layout.tsx +0 -0
  62. /package/templates/{storefront → storefront-bakery}/app/orders/[id]/page.tsx +0 -0
  63. /package/templates/{storefront → storefront-bakery}/app/shop/page.tsx +0 -0
  64. /package/templates/{storefront → storefront-bakery}/app/shop/shop-client.tsx +0 -0
  65. /package/templates/{storefront → storefront-bakery}/components/category-grid.tsx +0 -0
  66. /package/templates/{storefront → storefront-bakery}/components/collection-strip.tsx +0 -0
  67. /package/templates/{storefront → storefront-bakery}/components/hero.tsx +0 -0
  68. /package/templates/{storefront → storefront-bakery}/components/product-modal.tsx +0 -0
  69. /package/templates/{storefront → storefront-bakery}/components/providers.tsx +0 -0
  70. /package/templates/{storefront → storefront-bakery}/lib/cart.ts +0 -0
  71. /package/templates/{storefront → storefront-bakery}/postcss.config.mjs +0 -0
  72. /package/templates/{storefront → storefront-bakery}/tsconfig.json +0 -0
@@ -0,0 +1,24 @@
1
+ import Link from "next/link";
2
+
3
+ export default async function OrderPage({ params }: { params: Promise<{ id: string }> }) {
4
+ const { id } = await params;
5
+ return (
6
+ <section className="max-w-2xl mx-auto px-8 py-20 text-center">
7
+ <h1 className="text-3xl mt-0 mb-3 font-bold -tracking-[0.025em]">
8
+ Order confirmed.
9
+ </h1>
10
+ <p className="text-muted-foreground">
11
+ Order <code className="font-mono text-foreground">{id}</code> — you&apos;ll
12
+ get an SMS with tracking within 30 minutes.
13
+ </p>
14
+ <p className="mt-6">
15
+ <Link
16
+ href="/"
17
+ className="inline-block px-6 py-3 rounded-md bg-primary text-primary-foreground font-semibold text-sm transition-colors hover:bg-primary/90"
18
+ >
19
+ Continue shopping
20
+ </Link>
21
+ </p>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,97 @@
1
+ import type { Metadata } from "next";
2
+ import { Suspense } from "react";
3
+ import { cacheTag, cacheLife } from "next/cache";
4
+ import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
+ import { Hero } from "@/components/hero";
6
+ import { CollectionStrip } from "@/components/collection-strip";
7
+ import { CategoryGrid } from "@/components/category-grid";
8
+
9
+ export const metadata: Metadata = {
10
+ title: "Currents Electronics — Tech, in stock, in Accra",
11
+ description:
12
+ "The latest laptops, phones, audio, and accessories — curated, in-stock, shipped same-day from Accra.",
13
+ };
14
+
15
+ async function getHomeData() {
16
+ "use cache";
17
+ cacheTag(tags.collections(), tags.categories(), tags.products());
18
+ cacheLife("hours");
19
+
20
+ const client = getServerClient();
21
+ const [colRes, catRes] = await Promise.all([
22
+ client.catalogue.getCollections(),
23
+ client.catalogue.getCategories(),
24
+ ]);
25
+ const collections = colRes.ok ? colRes.value : [];
26
+ const categories = catRes.ok ? catRes.value : [];
27
+
28
+ const collectionsWithProducts = await Promise.all(
29
+ collections.map(async (col) => {
30
+ const r = await client.catalogue.getCollectionProducts(col.id);
31
+ const items = r.ok
32
+ ? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
33
+ : [];
34
+ return { collection: col, products: items };
35
+ }),
36
+ );
37
+
38
+ return {
39
+ collections: collectionsWithProducts.filter((x) => x.products.length > 0),
40
+ categories,
41
+ };
42
+ }
43
+
44
+ export default async function HomePage() {
45
+ const { collections, categories } = await getHomeData();
46
+
47
+ return (
48
+ <>
49
+ <Hero
50
+ badge="LAPTOPS · PHONES · AUDIO · ACCESSORIES"
51
+ title="The tech you want, in stock today."
52
+ subtitle="Authorised dealer for Apple, Samsung, Sony, Bose. Same-day delivery in Accra. Two-year warranty on every product."
53
+ />
54
+ {collections.map(({ collection, products }) => (
55
+ <Suspense
56
+ key={collection.id}
57
+ fallback={<StripSkeleton title={collection.name} />}
58
+ >
59
+ <CollectionStrip
60
+ collection={collection}
61
+ products={products}
62
+ collectionHref={`/collections/${collection.slug}`}
63
+ />
64
+ </Suspense>
65
+ ))}
66
+ <Suspense fallback={<CategoryGridSkeleton />}>
67
+ <CategoryGrid categories={categories} />
68
+ </Suspense>
69
+ </>
70
+ );
71
+ }
72
+
73
+ function StripSkeleton({ title }: { title: string }) {
74
+ return (
75
+ <section className="max-w-7xl mx-auto px-8 pt-12">
76
+ <h2 className="text-[26px] font-semibold m-0 mb-5 -tracking-[0.02em]">{title}</h2>
77
+ <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto pb-2">
78
+ {Array.from({ length: 5 }).map((_, i) => (
79
+ <div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
80
+ ))}
81
+ </div>
82
+ </section>
83
+ );
84
+ }
85
+
86
+ function CategoryGridSkeleton() {
87
+ return (
88
+ <section className="max-w-7xl mx-auto px-8 pt-14">
89
+ <div className="h-8 w-48 bg-muted rounded mb-5 animate-pulse" />
90
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
91
+ {Array.from({ length: 6 }).map((_, i) => (
92
+ <div key={i} className="h-32 bg-muted rounded-2xl animate-pulse" />
93
+ ))}
94
+ </div>
95
+ </section>
96
+ );
97
+ }
@@ -0,0 +1,101 @@
1
+ import type { Metadata } from "next";
2
+
3
+ export const metadata: Metadata = {
4
+ title: "Privacy Policy — Currents Electronics",
5
+ description:
6
+ "How Currents Electronics collects, uses, and protects your personal data.",
7
+ };
8
+
9
+ export default function PrivacyPage() {
10
+ return (
11
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
12
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
13
+ Privacy
14
+ </p>
15
+ <h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
16
+ Privacy Policy
17
+ </h1>
18
+ <p className="text-sm text-muted-foreground not-prose mb-10">
19
+ Last updated: 1 May 2026
20
+ </p>
21
+
22
+ <section className="space-y-5 leading-relaxed text-foreground/90">
23
+ <h2 className="text-2xl font-semibold mt-0 -tracking-[0.02em]">
24
+ What we collect
25
+ </h2>
26
+ <p>
27
+ We collect what we need to fulfil your order: name, phone, delivery
28
+ address, email, and the items in your order. For warranty service
29
+ and trade-ins, we also collect device serial numbers. Payment details
30
+ are processed by our payment partners (Paystack, Stripe, Fidelity
31
+ Bank) and never stored on our servers.
32
+ </p>
33
+
34
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
35
+ How we use it
36
+ </h2>
37
+ <ul className="list-disc pl-6 space-y-2">
38
+ <li>To prepare, deliver, and confirm your order.</li>
39
+ <li>To register product warranties on your behalf.</li>
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&apos;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&apos;ll email
95
+ customers on our marketing list and update the &quot;last updated&quot;
96
+ date above.
97
+ </p>
98
+ </section>
99
+ </article>
100
+ );
101
+ }
@@ -0,0 +1,30 @@
1
+ import type { Metadata } from "next";
2
+ import { cacheTag, cacheLife } from "next/cache";
3
+ import { getServerClient, tags } from "@cimplify/sdk/server";
4
+ import { ShopClient } from "./shop-client";
5
+
6
+ export const metadata: Metadata = {
7
+ title: "Shop — Akua's Bakery",
8
+ description: "Browse the full catalogue: breads, pastries, cakes, drinks, merch, and more.",
9
+ };
10
+
11
+ async function getShopData() {
12
+ "use cache";
13
+ cacheTag(tags.products(), tags.categories());
14
+ cacheLife("hours");
15
+
16
+ const client = getServerClient();
17
+ const [p, c] = await Promise.all([
18
+ client.catalogue.getProducts({ limit: 50 }),
19
+ client.catalogue.getCategories(),
20
+ ]);
21
+ return {
22
+ products: p.ok ? p.value.items : [],
23
+ categories: c.ok ? c.value : [],
24
+ };
25
+ }
26
+
27
+ export default async function ShopPage() {
28
+ const { products, categories } = await getShopData();
29
+ return <ShopClient products={products} categories={categories} />;
30
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { CataloguePage } from "@cimplify/sdk/react";
4
+ import type { Category, Product } from "@cimplify/sdk";
5
+ import { StoreProductCard } from "@/components/store-product-card";
6
+
7
+ /**
8
+ * Client island for the shop page. Server-side fetches all products and
9
+ * categories (cached via `'use cache'` in `app/shop/page.tsx`), then hands
10
+ * them to `<CataloguePage>` which owns the interactive filter / sort state.
11
+ */
12
+ export function ShopClient({
13
+ products,
14
+ categories,
15
+ }: {
16
+ products: Product[];
17
+ categories: Category[];
18
+ }) {
19
+ return (
20
+ <CataloguePage
21
+ title="The Menu"
22
+ products={products}
23
+ categories={categories}
24
+ renderCard={(p) => <StoreProductCard product={p} />}
25
+ />
26
+ );
27
+ }
@@ -0,0 +1,117 @@
1
+ import type { Metadata } from "next";
2
+
3
+ export const metadata: Metadata = {
4
+ title: "Terms of Service — Currents Electronics",
5
+ description: "The rules of the road for ordering from Currents Electronics.",
6
+ };
7
+
8
+ export default function TermsPage() {
9
+ return (
10
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
11
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
12
+ Terms of service
13
+ </p>
14
+ <h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
15
+ Terms of Service
16
+ </h1>
17
+ <p className="text-sm text-muted-foreground not-prose mb-10">
18
+ Last updated: 1 May 2026
19
+ </p>
20
+
21
+ <section className="space-y-5 leading-relaxed text-foreground/90">
22
+ <h2 className="text-2xl font-semibold mt-0 -tracking-[0.02em]">
23
+ 1. Who we are
24
+ </h2>
25
+ <p>
26
+ Currents Electronics (&quot;we&quot;, &quot;us&quot;) is a consumer
27
+ electronics retailer based at Atomic Junction, East Legon, Accra,
28
+ Ghana. By placing an order, you (&quot;you&quot;, &quot;customer&quot;)
29
+ agree to these terms.
30
+ </p>
31
+
32
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
33
+ 2. Authorised dealer status
34
+ </h2>
35
+ <p>
36
+ We are authorised dealers for the brands listed on our About page.
37
+ Every product is sourced direct from the manufacturer or a certified
38
+ distributor. Serial numbers can be verified on the brand&apos;s
39
+ official portal at any time.
40
+ </p>
41
+
42
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
43
+ 3. Pricing and payment
44
+ </h2>
45
+ <p>
46
+ Prices are in Ghana Cedis (GH₵) and include VAT where applicable.
47
+ Payment is due at checkout via Mobile Money, card, or instalments via
48
+ our partner bank. Cash on delivery is available for Accra orders only.
49
+ </p>
50
+
51
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
52
+ 4. Warranty
53
+ </h2>
54
+ <p>
55
+ Every product carries a two-year warranty: the standard manufacturer
56
+ warranty in year one, plus a Currents-backed extension in year two.
57
+ Coverage includes manufacturing defects and battery degradation below
58
+ 80%. Accidental damage, liquid damage, and unauthorised modifications
59
+ are excluded.
60
+ </p>
61
+
62
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
63
+ 5. Returns
64
+ </h2>
65
+ <p>
66
+ Sealed products: 14 days, no questions asked. Opened products:
67
+ 7 days, subject to a 10% restocking fee for non-defective items.
68
+ Activated SIM-bundled phones cannot be returned once the SIM is
69
+ registered. Custom orders and bulk corporate orders are
70
+ non-refundable except for defects.
71
+ </p>
72
+
73
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
74
+ 6. Trade-in
75
+ </h2>
76
+ <p>
77
+ Trade-in quotes are estimates only. Final value is determined after
78
+ inspection by our refurbishment partner. We reserve the right to
79
+ revise the quote, and you may decline the revised offer and receive
80
+ your device back at our cost.
81
+ </p>
82
+
83
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
84
+ 7. Liability
85
+ </h2>
86
+ <p>
87
+ To the maximum extent permitted by Ghanaian law, our liability for
88
+ any claim arising from this website or your order is limited to the
89
+ value of the order. We&apos;re not liable for indirect or
90
+ consequential losses, including data loss.
91
+ </p>
92
+
93
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
94
+ 8. Governing law
95
+ </h2>
96
+ <p>
97
+ These terms are governed by the laws of Ghana. Disputes will be
98
+ resolved in the courts of Greater Accra.
99
+ </p>
100
+
101
+ <h2 className="text-2xl font-semibold -tracking-[0.02em]">
102
+ 9. Contact
103
+ </h2>
104
+ <p>
105
+ Questions? Email{" "}
106
+ <a
107
+ href="mailto:hello@currentselectronics.test"
108
+ className="text-primary font-semibold hover:underline"
109
+ >
110
+ hello@currentselectronics.test
111
+ </a>
112
+ .
113
+ </p>
114
+ </section>
115
+ </article>
116
+ );
117
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useCartCount } from "@/lib/cart";
5
+
6
+ /**
7
+ * Cart pill — dynamic island. Reads the live cart count via the SDK and
8
+ * renders a link to `/cart`. Wrap in `<Suspense fallback={<CartPillSkeleton/>}>`
9
+ * so the cached header chrome streams without blocking on the cart fetch.
10
+ */
11
+ export function CartPill() {
12
+ const { count } = useCartCount();
13
+ return (
14
+ <Link
15
+ href="/cart"
16
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-foreground text-background text-xs font-semibold tracking-wide transition-transform hover:scale-105"
17
+ >
18
+ Cart · {count}
19
+ </Link>
20
+ );
21
+ }
22
+
23
+ export function CartPillSkeleton() {
24
+ return (
25
+ <span
26
+ aria-hidden
27
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-foreground/80 text-background text-xs font-semibold tracking-wide"
28
+ >
29
+ Cart · …
30
+ </span>
31
+ );
32
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { CategoryGrid as SdkCategoryGrid } from "@cimplify/sdk/react";
5
+ import type { Category } from "@cimplify/sdk";
6
+
7
+ /**
8
+ * Homepage category tiles — defers to the SDK's <CategoryGrid/> for layout
9
+ * and accessibility, and routes selections to /categories/:slug.
10
+ */
11
+ export function CategoryGrid({ categories }: { categories?: Category[] }) {
12
+ const router = useRouter();
13
+ return (
14
+ <section className="max-w-7xl mx-auto px-8 pt-14">
15
+ <h2 className="font-serif text-[28px] font-semibold m-0 mb-5">Browse the menu</h2>
16
+ <SdkCategoryGrid
17
+ categories={categories}
18
+ onSelect={(c) => router.push(`/categories/${c.slug}`)}
19
+ classNames={{
20
+ item: "flex flex-col gap-1 p-6 bg-card border border-border rounded-2xl cursor-pointer text-left transition-all hover:border-primary hover:-translate-y-0.5",
21
+ name: "font-serif text-lg font-semibold text-foreground",
22
+ description: "text-xs text-muted-foreground",
23
+ count: "text-xs text-muted-foreground mt-1",
24
+ }}
25
+ />
26
+ </section>
27
+ );
28
+ }
@@ -0,0 +1,45 @@
1
+ import Link from "next/link";
2
+ import type { Collection, Product } from "@cimplify/sdk";
3
+ import { StoreProductCard } from "./store-product-card";
4
+
5
+ interface CollectionStripProps {
6
+ collection: Collection;
7
+ products: Product[];
8
+ collectionHref?: string;
9
+ }
10
+
11
+ /**
12
+ * Horizontal strip of products under a collection title. Cards come from the
13
+ * SDK so every variant (Food, Bundle, Composite, Service, …) renders
14
+ * correctly; clicks open the shared URL-driven product modal.
15
+ */
16
+ export function CollectionStrip({ collection, products, collectionHref }: CollectionStripProps) {
17
+ if (products.length === 0) return null;
18
+ return (
19
+ <section className="max-w-7xl mx-auto px-8 pt-12">
20
+ <header className="grid grid-cols-[1fr_auto] items-end gap-4 mb-5">
21
+ <h2 className="font-serif text-[28px] font-semibold m-0">{collection.name}</h2>
22
+ {collectionHref && (
23
+ <Link
24
+ href={collectionHref}
25
+ className="text-[13px] font-semibold text-primary hover:underline"
26
+ >
27
+ See all →
28
+ </Link>
29
+ )}
30
+ {collection.description && (
31
+ <p className="col-span-full m-0 mt-1 text-sm text-muted-foreground">
32
+ {collection.description}
33
+ </p>
34
+ )}
35
+ </header>
36
+ <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto snap-x snap-mandatory pb-2">
37
+ {products.slice(0, 8).map((p) => (
38
+ <div key={p.id} className="snap-start">
39
+ <StoreProductCard product={p} />
40
+ </div>
41
+ ))}
42
+ </div>
43
+ </section>
44
+ );
45
+ }