@cimplify/sdk 0.40.1 → 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 (219) hide show
  1. package/dist/dispatcher.mjs +3 -2
  2. package/dist/styles.css +1 -1
  3. package/package.json +1 -1
  4. package/templates/storefront-bakery/.env.example +5 -0
  5. package/templates/storefront-bakery/AGENTS.md +57 -0
  6. package/templates/storefront-bakery/app/about/page.tsx +21 -44
  7. package/templates/storefront-bakery/app/categories/[slug]/page.tsx +2 -1
  8. package/templates/storefront-bakery/app/collections/[slug]/page.tsx +2 -1
  9. package/templates/storefront-bakery/app/error.tsx +60 -0
  10. package/templates/storefront-bakery/app/faq/page.tsx +13 -89
  11. package/templates/storefront-bakery/app/layout.tsx +37 -2
  12. package/templates/storefront-bakery/app/llms.txt/route.ts +94 -0
  13. package/templates/storefront-bakery/app/not-found.tsx +39 -0
  14. package/templates/storefront-bakery/app/page.tsx +6 -6
  15. package/templates/storefront-bakery/app/privacy/page.tsx +24 -68
  16. package/templates/storefront-bakery/app/robots.ts +18 -0
  17. package/templates/storefront-bakery/app/shop/page.tsx +3 -2
  18. package/templates/storefront-bakery/app/sitemap.ts +62 -0
  19. package/templates/storefront-bakery/app/terms/page.tsx +24 -82
  20. package/templates/storefront-bakery/components/footer.tsx +88 -130
  21. package/templates/storefront-bakery/components/header.tsx +8 -16
  22. package/templates/storefront-bakery/lib/brand.ts +453 -0
  23. package/templates/storefront-fashion/.env.example +16 -0
  24. package/templates/storefront-fashion/AGENTS.md +52 -0
  25. package/templates/storefront-fashion/README.md +77 -0
  26. package/templates/storefront-fashion/app/about/page.tsx +41 -0
  27. package/templates/storefront-fashion/app/cart/page.tsx +9 -0
  28. package/templates/storefront-fashion/app/categories/[slug]/listing-client.tsx +19 -0
  29. package/templates/storefront-fashion/app/categories/[slug]/page.tsx +130 -0
  30. package/templates/storefront-fashion/app/checkout/page.tsx +17 -0
  31. package/templates/storefront-fashion/app/collections/[slug]/listing-client.tsx +20 -0
  32. package/templates/storefront-fashion/app/collections/[slug]/page.tsx +130 -0
  33. package/templates/storefront-fashion/app/error.tsx +61 -0
  34. package/templates/storefront-fashion/app/faq/page.tsx +46 -0
  35. package/templates/storefront-fashion/app/globals.css +46 -0
  36. package/templates/storefront-fashion/app/layout.tsx +76 -0
  37. package/templates/storefront-fashion/app/llms.txt/route.ts +94 -0
  38. package/templates/storefront-fashion/app/not-found.tsx +39 -0
  39. package/templates/storefront-fashion/app/orders/[id]/page.tsx +24 -0
  40. package/templates/storefront-fashion/app/page.tsx +183 -0
  41. package/templates/storefront-fashion/app/privacy/page.tsx +44 -0
  42. package/templates/storefront-fashion/app/products/[slug]/page.tsx +165 -0
  43. package/templates/storefront-fashion/app/products/[slug]/product-detail.tsx +71 -0
  44. package/templates/storefront-fashion/app/robots.ts +18 -0
  45. package/templates/storefront-fashion/app/shop/page.tsx +63 -0
  46. package/templates/storefront-fashion/app/shop/shop-client.tsx +32 -0
  47. package/templates/storefront-fashion/app/sitemap.ts +59 -0
  48. package/templates/storefront-fashion/app/terms/page.tsx +44 -0
  49. package/templates/storefront-fashion/components/brand-marquee.tsx +27 -0
  50. package/templates/storefront-fashion/components/cart-pill.tsx +32 -0
  51. package/templates/storefront-fashion/components/category-grid.tsx +28 -0
  52. package/templates/storefront-fashion/components/category-tiles.tsx +104 -0
  53. package/templates/storefront-fashion/components/collection-strip.tsx +45 -0
  54. package/templates/storefront-fashion/components/feature-hero.tsx +82 -0
  55. package/templates/storefront-fashion/components/footer.tsx +153 -0
  56. package/templates/storefront-fashion/components/header.tsx +43 -0
  57. package/templates/storefront-fashion/components/hero.tsx +28 -0
  58. package/templates/storefront-fashion/components/nav-link.tsx +20 -0
  59. package/templates/storefront-fashion/components/newsletter.tsx +50 -0
  60. package/templates/storefront-fashion/components/promo-banner.tsx +41 -0
  61. package/templates/storefront-fashion/components/providers.tsx +31 -0
  62. package/templates/storefront-fashion/components/section-heading.tsx +37 -0
  63. package/templates/storefront-fashion/components/store-product-card.tsx +83 -0
  64. package/templates/storefront-fashion/components/trade-in-cta.tsx +54 -0
  65. package/templates/storefront-fashion/components/trust-bar.tsx +66 -0
  66. package/templates/storefront-fashion/lib/brand.ts +421 -0
  67. package/templates/storefront-fashion/lib/cart.ts +27 -0
  68. package/templates/storefront-fashion/next.config.ts +40 -0
  69. package/templates/storefront-fashion/package.json +28 -0
  70. package/templates/storefront-fashion/postcss.config.mjs +7 -0
  71. package/templates/storefront-fashion/tsconfig.json +23 -0
  72. package/templates/storefront-grocery/.env.example +16 -0
  73. package/templates/storefront-grocery/AGENTS.md +39 -0
  74. package/templates/storefront-grocery/README.md +73 -0
  75. package/templates/storefront-grocery/app/about/page.tsx +38 -0
  76. package/templates/storefront-grocery/app/cart/page.tsx +9 -0
  77. package/templates/storefront-grocery/app/categories/[slug]/listing-client.tsx +19 -0
  78. package/templates/storefront-grocery/app/categories/[slug]/page.tsx +118 -0
  79. package/templates/storefront-grocery/app/checkout/page.tsx +17 -0
  80. package/templates/storefront-grocery/app/collections/[slug]/listing-client.tsx +20 -0
  81. package/templates/storefront-grocery/app/collections/[slug]/page.tsx +118 -0
  82. package/templates/storefront-grocery/app/error.tsx +60 -0
  83. package/templates/storefront-grocery/app/faq/page.tsx +46 -0
  84. package/templates/storefront-grocery/app/globals.css +45 -0
  85. package/templates/storefront-grocery/app/layout.tsx +75 -0
  86. package/templates/storefront-grocery/app/llms.txt/route.ts +94 -0
  87. package/templates/storefront-grocery/app/not-found.tsx +39 -0
  88. package/templates/storefront-grocery/app/orders/[id]/page.tsx +21 -0
  89. package/templates/storefront-grocery/app/page.tsx +97 -0
  90. package/templates/storefront-grocery/app/privacy/page.tsx +44 -0
  91. package/templates/storefront-grocery/app/robots.ts +18 -0
  92. package/templates/storefront-grocery/app/shop/page.tsx +31 -0
  93. package/templates/storefront-grocery/app/shop/shop-client.tsx +27 -0
  94. package/templates/storefront-grocery/app/sitemap.ts +62 -0
  95. package/templates/storefront-grocery/app/terms/page.tsx +44 -0
  96. package/templates/storefront-grocery/components/cart-pill.tsx +32 -0
  97. package/templates/storefront-grocery/components/category-grid.tsx +28 -0
  98. package/templates/storefront-grocery/components/collection-strip.tsx +45 -0
  99. package/templates/storefront-grocery/components/footer.tsx +148 -0
  100. package/templates/storefront-grocery/components/header.tsx +43 -0
  101. package/templates/storefront-grocery/components/hero.tsx +25 -0
  102. package/templates/storefront-grocery/components/nav-link.tsx +20 -0
  103. package/templates/storefront-grocery/components/providers.tsx +31 -0
  104. package/templates/storefront-grocery/components/store-product-card.tsx +83 -0
  105. package/templates/storefront-grocery/lib/brand.ts +284 -0
  106. package/templates/storefront-grocery/lib/cart.ts +27 -0
  107. package/templates/storefront-grocery/next.config.ts +40 -0
  108. package/templates/storefront-grocery/package.json +28 -0
  109. package/templates/storefront-grocery/postcss.config.mjs +7 -0
  110. package/templates/storefront-grocery/tsconfig.json +23 -0
  111. package/templates/storefront-restaurant/.env.example +16 -0
  112. package/templates/storefront-restaurant/AGENTS.md +39 -0
  113. package/templates/storefront-restaurant/README.md +73 -0
  114. package/templates/storefront-restaurant/app/about/page.tsx +38 -0
  115. package/templates/storefront-restaurant/app/cart/page.tsx +9 -0
  116. package/templates/storefront-restaurant/app/categories/[slug]/listing-client.tsx +19 -0
  117. package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +118 -0
  118. package/templates/storefront-restaurant/app/checkout/page.tsx +17 -0
  119. package/templates/storefront-restaurant/app/collections/[slug]/listing-client.tsx +20 -0
  120. package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +118 -0
  121. package/templates/storefront-restaurant/app/error.tsx +60 -0
  122. package/templates/storefront-restaurant/app/faq/page.tsx +46 -0
  123. package/templates/storefront-restaurant/app/globals.css +44 -0
  124. package/templates/storefront-restaurant/app/layout.tsx +80 -0
  125. package/templates/storefront-restaurant/app/llms.txt/route.ts +94 -0
  126. package/templates/storefront-restaurant/app/not-found.tsx +39 -0
  127. package/templates/storefront-restaurant/app/orders/[id]/page.tsx +21 -0
  128. package/templates/storefront-restaurant/app/page.tsx +97 -0
  129. package/templates/storefront-restaurant/app/privacy/page.tsx +44 -0
  130. package/templates/storefront-restaurant/app/robots.ts +18 -0
  131. package/templates/storefront-restaurant/app/shop/page.tsx +31 -0
  132. package/templates/storefront-restaurant/app/shop/shop-client.tsx +27 -0
  133. package/templates/storefront-restaurant/app/sitemap.ts +62 -0
  134. package/templates/storefront-restaurant/app/terms/page.tsx +44 -0
  135. package/templates/storefront-restaurant/components/cart-pill.tsx +32 -0
  136. package/templates/storefront-restaurant/components/category-grid.tsx +28 -0
  137. package/templates/storefront-restaurant/components/collection-strip.tsx +45 -0
  138. package/templates/storefront-restaurant/components/footer.tsx +148 -0
  139. package/templates/storefront-restaurant/components/header.tsx +43 -0
  140. package/templates/storefront-restaurant/components/hero.tsx +25 -0
  141. package/templates/storefront-restaurant/components/nav-link.tsx +20 -0
  142. package/templates/storefront-restaurant/components/product-modal.tsx +105 -0
  143. package/templates/storefront-restaurant/components/providers.tsx +31 -0
  144. package/templates/storefront-restaurant/components/store-product-card.tsx +83 -0
  145. package/templates/storefront-restaurant/lib/brand.ts +287 -0
  146. package/templates/storefront-restaurant/lib/cart.ts +27 -0
  147. package/templates/storefront-restaurant/next.config.ts +40 -0
  148. package/templates/storefront-restaurant/package.json +28 -0
  149. package/templates/storefront-restaurant/postcss.config.mjs +7 -0
  150. package/templates/storefront-restaurant/tsconfig.json +23 -0
  151. package/templates/storefront-retail/.env.example +5 -0
  152. package/templates/storefront-retail/AGENTS.md +64 -0
  153. package/templates/storefront-retail/app/about/page.tsx +24 -54
  154. package/templates/storefront-retail/app/categories/[slug]/page.tsx +2 -1
  155. package/templates/storefront-retail/app/collections/[slug]/page.tsx +2 -1
  156. package/templates/storefront-retail/app/error.tsx +61 -0
  157. package/templates/storefront-retail/app/faq/page.tsx +11 -92
  158. package/templates/storefront-retail/app/layout.tsx +37 -8
  159. package/templates/storefront-retail/app/llms.txt/route.ts +94 -0
  160. package/templates/storefront-retail/app/not-found.tsx +39 -0
  161. package/templates/storefront-retail/app/page.tsx +16 -73
  162. package/templates/storefront-retail/app/privacy/page.tsx +24 -81
  163. package/templates/storefront-retail/app/products/[slug]/page.tsx +165 -0
  164. package/templates/storefront-retail/app/products/[slug]/product-detail.tsx +71 -0
  165. package/templates/storefront-retail/app/robots.ts +18 -0
  166. package/templates/storefront-retail/app/shop/page.tsx +3 -3
  167. package/templates/storefront-retail/app/sitemap.ts +59 -0
  168. package/templates/storefront-retail/app/terms/page.tsx +24 -97
  169. package/templates/storefront-retail/components/brand-marquee.tsx +6 -15
  170. package/templates/storefront-retail/components/feature-hero.tsx +2 -1
  171. package/templates/storefront-retail/components/footer.tsx +85 -120
  172. package/templates/storefront-retail/components/header.tsx +10 -12
  173. package/templates/storefront-retail/components/newsletter.tsx +7 -9
  174. package/templates/storefront-retail/components/promo-banner.tsx +12 -15
  175. package/templates/storefront-retail/components/store-product-card.tsx +7 -7
  176. package/templates/storefront-retail/components/trade-in-cta.tsx +11 -17
  177. package/templates/storefront-retail/components/trust-bar.tsx +45 -5
  178. package/templates/storefront-retail/lib/brand.ts +567 -0
  179. package/templates/storefront-services/.env.example +16 -0
  180. package/templates/storefront-services/AGENTS.md +41 -0
  181. package/templates/storefront-services/README.md +73 -0
  182. package/templates/storefront-services/app/about/page.tsx +38 -0
  183. package/templates/storefront-services/app/cart/page.tsx +9 -0
  184. package/templates/storefront-services/app/categories/[slug]/listing-client.tsx +19 -0
  185. package/templates/storefront-services/app/categories/[slug]/page.tsx +118 -0
  186. package/templates/storefront-services/app/checkout/page.tsx +17 -0
  187. package/templates/storefront-services/app/collections/[slug]/listing-client.tsx +20 -0
  188. package/templates/storefront-services/app/collections/[slug]/page.tsx +118 -0
  189. package/templates/storefront-services/app/error.tsx +60 -0
  190. package/templates/storefront-services/app/faq/page.tsx +46 -0
  191. package/templates/storefront-services/app/globals.css +45 -0
  192. package/templates/storefront-services/app/layout.tsx +80 -0
  193. package/templates/storefront-services/app/llms.txt/route.ts +94 -0
  194. package/templates/storefront-services/app/not-found.tsx +39 -0
  195. package/templates/storefront-services/app/orders/[id]/page.tsx +21 -0
  196. package/templates/storefront-services/app/page.tsx +97 -0
  197. package/templates/storefront-services/app/privacy/page.tsx +44 -0
  198. package/templates/storefront-services/app/robots.ts +18 -0
  199. package/templates/storefront-services/app/shop/page.tsx +31 -0
  200. package/templates/storefront-services/app/shop/shop-client.tsx +27 -0
  201. package/templates/storefront-services/app/sitemap.ts +62 -0
  202. package/templates/storefront-services/app/terms/page.tsx +44 -0
  203. package/templates/storefront-services/components/cart-pill.tsx +32 -0
  204. package/templates/storefront-services/components/category-grid.tsx +28 -0
  205. package/templates/storefront-services/components/collection-strip.tsx +45 -0
  206. package/templates/storefront-services/components/footer.tsx +148 -0
  207. package/templates/storefront-services/components/header.tsx +43 -0
  208. package/templates/storefront-services/components/hero.tsx +25 -0
  209. package/templates/storefront-services/components/nav-link.tsx +20 -0
  210. package/templates/storefront-services/components/product-modal.tsx +105 -0
  211. package/templates/storefront-services/components/providers.tsx +31 -0
  212. package/templates/storefront-services/components/store-product-card.tsx +83 -0
  213. package/templates/storefront-services/lib/brand.ts +303 -0
  214. package/templates/storefront-services/lib/cart.ts +27 -0
  215. package/templates/storefront-services/next.config.ts +40 -0
  216. package/templates/storefront-services/package.json +28 -0
  217. package/templates/storefront-services/postcss.config.mjs +7 -0
  218. package/templates/storefront-services/tsconfig.json +23 -0
  219. /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 menu 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}/shop?product=${slug}) (${price})${desc}`);
61
+ }
62
+ }
63
+
64
+ lines.push("");
65
+ lines.push("## Information");
66
+ lines.push(`- [About](${SITE_URL}/about)`);
67
+ lines.push(`- [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,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-8 py-20 text-center">
13
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-3">
14
+ 404
15
+ </p>
16
+ <h1 className="font-serif text-[clamp(2.5rem,5vw,3.5rem)] font-semibold mb-4">
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&apos;s sold out for the day. 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-block px-6 py-3 rounded-full bg-primary text-primary-foreground font-semibold text-sm transition-colors hover:bg-primary/90"
27
+ >
28
+ Browse the menu
29
+ </Link>
30
+ <Link
31
+ href="/"
32
+ className="inline-block px-6 py-3 rounded-full 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,21 @@
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="font-serif text-3xl mt-0 mb-3">Thanks — your order is confirmed</h1>
8
+ <p className="text-muted-foreground">
9
+ Order <code className="font-mono text-foreground">{id}</code>
10
+ </p>
11
+ <p className="mt-6">
12
+ <Link
13
+ href="/"
14
+ className="inline-block px-6 py-3 rounded-full bg-primary text-primary-foreground font-semibold text-sm transition-colors hover:bg-primary/90"
15
+ >
16
+ Continue shopping
17
+ </Link>
18
+ </p>
19
+ </section>
20
+ );
21
+ }
@@ -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
+ import { brand } from "@/lib/brand";
9
+
10
+ export const metadata: Metadata = {
11
+ title: `${brand.name} — ${brand.hero.title}`,
12
+ description: brand.description,
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={brand.hero.badge}
51
+ title={brand.hero.title}
52
+ subtitle={brand.hero.subtitle}
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="font-serif text-[28px] font-semibold m-0 mb-5">{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,44 @@
1
+ import type { Metadata } from "next";
2
+ import { brand } from "@/lib/brand";
3
+
4
+ export const metadata: Metadata = {
5
+ title: `Privacy Policy — ${brand.name}`,
6
+ description: `How ${brand.name} collects, uses, and protects your personal data.`,
7
+ };
8
+
9
+ export default function PrivacyPage() {
10
+ const p = brand.privacy;
11
+ return (
12
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
13
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
14
+ {p.eyebrow}
15
+ </p>
16
+ <h1 className="font-serif text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
17
+ {p.title}
18
+ </h1>
19
+ <p className="text-sm text-muted-foreground not-prose mb-10">
20
+ Last updated: {p.lastUpdated}
21
+ </p>
22
+
23
+ <section className="space-y-5 leading-relaxed text-foreground/90">
24
+ {p.sections.map((s) => (
25
+ <div key={s.heading}>
26
+ <h2 className="font-serif text-2xl font-semibold mt-0">{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
+ ))}
41
+ </section>
42
+ </article>
43
+ );
44
+ }
@@ -0,0 +1,18 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ const SITE_URL =
4
+ process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
5
+
6
+ export default function robots(): MetadataRoute.Robots {
7
+ return {
8
+ rules: [
9
+ {
10
+ userAgent: "*",
11
+ allow: "/",
12
+ disallow: ["/cart", "/checkout", "/orders/", "/api/"],
13
+ },
14
+ ],
15
+ sitemap: `${SITE_URL}/sitemap.xml`,
16
+ host: SITE_URL,
17
+ };
18
+ }
@@ -0,0 +1,31 @@
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
+ import { brand } from "@/lib/brand";
6
+
7
+ export const metadata: Metadata = {
8
+ title: `Shop — ${brand.name}`,
9
+ description: brand.description,
10
+ };
11
+
12
+ async function getShopData() {
13
+ "use cache";
14
+ cacheTag(tags.products(), tags.categories());
15
+ cacheLife("hours");
16
+
17
+ const client = getServerClient();
18
+ const [p, c] = await Promise.all([
19
+ client.catalogue.getProducts({ limit: 50 }),
20
+ client.catalogue.getCategories(),
21
+ ]);
22
+ return {
23
+ products: p.ok ? p.value.items : [],
24
+ categories: c.ok ? c.value : [],
25
+ };
26
+ }
27
+
28
+ export default async function ShopPage() {
29
+ const { products, categories } = await getShopData();
30
+ return <ShopClient products={products} categories={categories} />;
31
+ }
@@ -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,62 @@
1
+ import type { MetadataRoute } from "next";
2
+ import { getServerClient, type Product } from "@cimplify/sdk/server";
3
+
4
+ const SITE_URL =
5
+ process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
6
+
7
+ const STATIC_ROUTES: { path: string; priority: number; changeFrequency: "daily" | "weekly" | "monthly" }[] = [
8
+ { path: "/", priority: 1.0, changeFrequency: "daily" },
9
+ { path: "/shop", priority: 0.9, changeFrequency: "daily" },
10
+ { path: "/about", priority: 0.5, changeFrequency: "monthly" },
11
+ { path: "/faq", priority: 0.4, changeFrequency: "monthly" },
12
+ { path: "/terms", priority: 0.2, changeFrequency: "monthly" },
13
+ { path: "/privacy", priority: 0.2, changeFrequency: "monthly" },
14
+ ];
15
+
16
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
17
+ const now = new Date();
18
+ const client = getServerClient();
19
+
20
+ const [productsRes, categoriesRes, collectionsRes] = await Promise.all([
21
+ client.catalogue.getProducts({ limit: 500 }),
22
+ client.catalogue.getCategories(),
23
+ client.catalogue.getCollections(),
24
+ ]);
25
+
26
+ const products = productsRes.ok ? productsRes.value.items : [];
27
+ const categories = categoriesRes.ok ? categoriesRes.value : [];
28
+ const collections = collectionsRes.ok ? collectionsRes.value : [];
29
+
30
+ const staticEntries: MetadataRoute.Sitemap = STATIC_ROUTES.map((r) => ({
31
+ url: `${SITE_URL}${r.path}`,
32
+ lastModified: now,
33
+ changeFrequency: r.changeFrequency,
34
+ priority: r.priority,
35
+ }));
36
+
37
+ // Bakery uses a URL-driven product modal (`?product=<slug>` on the home or
38
+ // shop pages). Emit those as deep-linkable URLs so search engines and LLMs
39
+ // can index each product canonically.
40
+ const productEntries: MetadataRoute.Sitemap = products.map((p: Product) => ({
41
+ url: `${SITE_URL}/shop?product=${encodeURIComponent(p.slug ?? p.id)}`,
42
+ lastModified: p.updated_at ? new Date(p.updated_at) : now,
43
+ changeFrequency: "weekly",
44
+ priority: 0.7,
45
+ }));
46
+
47
+ const categoryEntries: MetadataRoute.Sitemap = categories.map((c) => ({
48
+ url: `${SITE_URL}/categories/${c.slug}`,
49
+ lastModified: now,
50
+ changeFrequency: "weekly",
51
+ priority: 0.6,
52
+ }));
53
+
54
+ const collectionEntries: MetadataRoute.Sitemap = collections.map((c) => ({
55
+ url: `${SITE_URL}/collections/${c.slug}`,
56
+ lastModified: now,
57
+ changeFrequency: "weekly",
58
+ priority: 0.6,
59
+ }));
60
+
61
+ return [...staticEntries, ...categoryEntries, ...collectionEntries, ...productEntries];
62
+ }
@@ -0,0 +1,44 @@
1
+ import type { Metadata } from "next";
2
+ import { brand } from "@/lib/brand";
3
+
4
+ export const metadata: Metadata = {
5
+ title: `Terms of Service — ${brand.name}`,
6
+ description: `The rules of the road for ordering from ${brand.name}.`,
7
+ };
8
+
9
+ export default function TermsPage() {
10
+ const t = brand.terms;
11
+ return (
12
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
13
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
14
+ {t.eyebrow}
15
+ </p>
16
+ <h1 className="font-serif text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
17
+ {t.title}
18
+ </h1>
19
+ <p className="text-sm text-muted-foreground not-prose mb-10">
20
+ Last updated: {t.lastUpdated}
21
+ </p>
22
+
23
+ <section className="space-y-5 leading-relaxed text-foreground/90">
24
+ {t.sections.map((s) => (
25
+ <div key={s.heading}>
26
+ <h2 className="font-serif text-2xl font-semibold mt-0">{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
+ ))}
41
+ </section>
42
+ </article>
43
+ );
44
+ }
@@ -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
+ }