@cimplify/cli 0.2.8 → 0.3.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 (229) hide show
  1. package/dist/{add-7PTWJV4F.mjs → add-ZJNXWN2B.mjs} +10 -10
  2. package/dist/assets-DMK2QOPD.mjs +208 -0
  3. package/dist/chunk-5IAYN7AJ.mjs +259 -0
  4. package/dist/{chunk-4SBJVRGM.mjs → chunk-C4M3DXKC.mjs} +3 -1
  5. package/dist/{chunk-NC3GKHDD.mjs → chunk-D7WMSGKK.mjs} +1 -1
  6. package/dist/{chunk-NZ4RG62Z.mjs → chunk-I3XQSSOT.mjs} +4 -1
  7. package/dist/{chunk-JJYWETGA.mjs → chunk-LS2VTSMQ.mjs} +8 -2
  8. package/dist/{chunk-H2HJQGFY.mjs → chunk-MHK4WVNF.mjs} +2392 -596
  9. package/dist/{chunk-JOUXICGV.mjs → chunk-MOZQODQS.mjs} +1 -1
  10. package/dist/{chunk-KPGRCXQY.mjs → chunk-QGBXGDA5.mjs} +5 -5
  11. package/dist/chunk-RRY3NEZZ.mjs +79 -0
  12. package/dist/{chunk-L6474RPL.mjs → chunk-RZQTHTXX.mjs} +1 -1
  13. package/dist/{chunk-4YSOZ6LY.mjs → chunk-YI7UMMM7.mjs} +1 -1
  14. package/dist/{chunk-UPEHLREA.mjs → chunk-YQVMG62Z.mjs} +3 -3
  15. package/dist/{deploy-6KVOROT3.mjs → deploy-UKOOPJAE.mjs} +8 -82
  16. package/dist/{dev-AQP6TMYK.mjs → dev-FD4PM3UD.mjs} +5 -5
  17. package/dist/dispatcher.mjs +34 -22
  18. package/dist/doctor-5LBLYT7M.mjs +314 -0
  19. package/dist/{domains-2ZQ7AG27.mjs → domains-JQMV6GAP.mjs} +5 -5
  20. package/dist/{env-FDBPGU3W.mjs → env-EVMYQUIK.mjs} +6 -6
  21. package/dist/explain-3KBMWL6M.mjs +223 -0
  22. package/dist/introspect-PFBI3JHO.mjs +8 -0
  23. package/dist/{link-P4K2HRXY.mjs → link-X3E4UZBF.mjs} +4 -4
  24. package/dist/{list-44MLIFI2.mjs → list-TE54SJIB.mjs} +3 -3
  25. package/dist/{login-RSKGT6GU.mjs → login-WSAW4BEA.mjs} +4 -4
  26. package/dist/{logout-ZFZLSJ32.mjs → logout-DJDINVDF.mjs} +2 -2
  27. package/dist/{logs-E2AGTDCF.mjs → logs-KUKGEXR2.mjs} +4 -4
  28. package/dist/{projects-5CJOZ3MT.mjs → projects-364HGWHO.mjs} +13 -11
  29. package/dist/repo-26N2CHF6.mjs +8 -0
  30. package/dist/{rollback-36O4NOEL.mjs → rollback-5YALPQXL.mjs} +5 -5
  31. package/dist/{status-6AT4HF63.mjs → status-W4HW3CX3.mjs} +4 -4
  32. package/dist/{unlink-5ABCT7B6.mjs → unlink-HIIW57OO.mjs} +2 -2
  33. package/dist/{update-6KEG7EWK.mjs → update-5MRKRVZC.mjs} +7 -7
  34. package/dist/{whoami-DIJZYZIN.mjs → whoami-LACWBSNL.mjs} +3 -3
  35. package/package.json +3 -3
  36. package/templates/storefront-auto/.claude/skills/cimplify-storefront/SKILL.md +145 -0
  37. package/templates/storefront-auto/.cursor/rules/cimplify-storefront.mdc +25 -0
  38. package/templates/storefront-auto/.env.example +22 -0
  39. package/templates/storefront-auto/AGENTS.md +95 -0
  40. package/templates/storefront-auto/CLAUDE.md +22 -0
  41. package/templates/storefront-auto/README.md +48 -0
  42. package/templates/storefront-auto/__tests__/brand.test.ts +4 -0
  43. package/templates/storefront-auto/__tests__/cart-flow.test.ts +4 -0
  44. package/templates/storefront-auto/__tests__/contract.test.ts +4 -0
  45. package/templates/storefront-auto/app/.well-known/ucp/route.ts +65 -0
  46. package/templates/storefront-auto/app/about/page.tsx +41 -0
  47. package/templates/storefront-auto/app/accessibility/page.tsx +11 -0
  48. package/templates/storefront-auto/app/account/addresses/page.tsx +21 -0
  49. package/templates/storefront-auto/app/account/orders/page.tsx +21 -0
  50. package/templates/storefront-auto/app/account/page.tsx +22 -0
  51. package/templates/storefront-auto/app/account/settings/page.tsx +21 -0
  52. package/templates/storefront-auto/app/cart/page.tsx +9 -0
  53. package/templates/storefront-auto/app/categories/[slug]/listing-client.tsx +19 -0
  54. package/templates/storefront-auto/app/categories/[slug]/page.tsx +130 -0
  55. package/templates/storefront-auto/app/checkout/page.tsx +17 -0
  56. package/templates/storefront-auto/app/collections/[slug]/listing-client.tsx +20 -0
  57. package/templates/storefront-auto/app/collections/[slug]/page.tsx +130 -0
  58. package/templates/storefront-auto/app/contact/contact-form.tsx +109 -0
  59. package/templates/storefront-auto/app/contact/page.tsx +54 -0
  60. package/templates/storefront-auto/app/error.tsx +61 -0
  61. package/templates/storefront-auto/app/faq/page.tsx +46 -0
  62. package/templates/storefront-auto/app/globals.css +47 -0
  63. package/templates/storefront-auto/app/layout.tsx +77 -0
  64. package/templates/storefront-auto/app/llms.txt/route.ts +94 -0
  65. package/templates/storefront-auto/app/login/page.tsx +17 -0
  66. package/templates/storefront-auto/app/not-found.tsx +39 -0
  67. package/templates/storefront-auto/app/opensearch.xml/route.ts +37 -0
  68. package/templates/storefront-auto/app/orders/[id]/page.tsx +24 -0
  69. package/templates/storefront-auto/app/page.tsx +94 -0
  70. package/templates/storefront-auto/app/privacy/page.tsx +44 -0
  71. package/templates/storefront-auto/app/products/[slug]/page.tsx +165 -0
  72. package/templates/storefront-auto/app/products/[slug]/product-detail.tsx +70 -0
  73. package/templates/storefront-auto/app/returns/page.tsx +11 -0
  74. package/templates/storefront-auto/app/robots.ts +18 -0
  75. package/templates/storefront-auto/app/search/page.tsx +38 -0
  76. package/templates/storefront-auto/app/search/search-client.tsx +7 -0
  77. package/templates/storefront-auto/app/shipping/page.tsx +16 -0
  78. package/templates/storefront-auto/app/shop/page.tsx +63 -0
  79. package/templates/storefront-auto/app/shop/shop-client.tsx +32 -0
  80. package/templates/storefront-auto/app/signup/page.tsx +17 -0
  81. package/templates/storefront-auto/app/sitemap-page/page.tsx +167 -0
  82. package/templates/storefront-auto/app/sitemap.ts +59 -0
  83. package/templates/storefront-auto/app/terms/page.tsx +44 -0
  84. package/templates/storefront-auto/app/track-order/page.tsx +24 -0
  85. package/templates/storefront-auto/app/track-order/track-order-form.tsx +69 -0
  86. package/templates/storefront-auto/components/account-iframe.tsx +13 -0
  87. package/templates/storefront-auto/components/auto-hero.tsx +85 -0
  88. package/templates/storefront-auto/components/brand-marquee.tsx +27 -0
  89. package/templates/storefront-auto/components/cart-drawer.tsx +14 -0
  90. package/templates/storefront-auto/components/cart-pill.tsx +36 -0
  91. package/templates/storefront-auto/components/category-grid.tsx +28 -0
  92. package/templates/storefront-auto/components/category-tiles.tsx +104 -0
  93. package/templates/storefront-auto/components/collection-strip.tsx +45 -0
  94. package/templates/storefront-auto/components/feature-hero.tsx +84 -0
  95. package/templates/storefront-auto/components/fitment-finder.tsx +184 -0
  96. package/templates/storefront-auto/components/footer.tsx +153 -0
  97. package/templates/storefront-auto/components/header.tsx +45 -0
  98. package/templates/storefront-auto/components/hero.tsx +28 -0
  99. package/templates/storefront-auto/components/nav-link.tsx +20 -0
  100. package/templates/storefront-auto/components/newsletter.tsx +50 -0
  101. package/templates/storefront-auto/components/policy-page.tsx +49 -0
  102. package/templates/storefront-auto/components/promo-banner.tsx +41 -0
  103. package/templates/storefront-auto/components/providers.tsx +35 -0
  104. package/templates/storefront-auto/components/section-heading.tsx +37 -0
  105. package/templates/storefront-auto/components/service-brief.tsx +65 -0
  106. package/templates/storefront-auto/components/store-product-card.tsx +88 -0
  107. package/templates/storefront-auto/components/trade-in-cta.tsx +54 -0
  108. package/templates/storefront-auto/components/trust-bar.tsx +66 -0
  109. package/templates/storefront-auto/lib/brand.ts +744 -0
  110. package/templates/storefront-auto/lib/cart.ts +12 -0
  111. package/templates/storefront-auto/lib/cimplify-loader.ts +19 -0
  112. package/templates/storefront-auto/next.config.ts +45 -0
  113. package/templates/storefront-auto/package.json +35 -0
  114. package/templates/storefront-auto/postcss.config.mjs +7 -0
  115. package/templates/storefront-auto/tsconfig.json +23 -0
  116. package/templates/storefront-auto/vitest.config.ts +9 -0
  117. package/templates/storefront-bakery/.env.example +2 -2
  118. package/templates/storefront-bakery/README.md +1 -1
  119. package/templates/storefront-bakery/lib/cimplify-loader.ts +19 -0
  120. package/templates/storefront-bakery/next.config.ts +3 -0
  121. package/templates/storefront-bakery/package.json +1 -1
  122. package/templates/storefront-fashion/.env.example +2 -2
  123. package/templates/storefront-fashion/README.md +1 -1
  124. package/templates/storefront-fashion/lib/cimplify-loader.ts +19 -0
  125. package/templates/storefront-fashion/next.config.ts +3 -0
  126. package/templates/storefront-fashion/package.json +1 -1
  127. package/templates/storefront-grocery/.env.example +2 -2
  128. package/templates/storefront-grocery/README.md +1 -1
  129. package/templates/storefront-grocery/lib/cimplify-loader.ts +19 -0
  130. package/templates/storefront-grocery/next.config.ts +3 -0
  131. package/templates/storefront-grocery/package.json +1 -1
  132. package/templates/storefront-pharmacy/.claude/skills/cimplify-storefront/SKILL.md +145 -0
  133. package/templates/storefront-pharmacy/.cursor/rules/cimplify-storefront.mdc +25 -0
  134. package/templates/storefront-pharmacy/.env.example +22 -0
  135. package/templates/storefront-pharmacy/AGENTS.md +118 -0
  136. package/templates/storefront-pharmacy/CLAUDE.md +22 -0
  137. package/templates/storefront-pharmacy/README.md +87 -0
  138. package/templates/storefront-pharmacy/__tests__/brand.test.ts +4 -0
  139. package/templates/storefront-pharmacy/__tests__/cart-flow.test.ts +4 -0
  140. package/templates/storefront-pharmacy/__tests__/contract.test.ts +4 -0
  141. package/templates/storefront-pharmacy/app/.well-known/ucp/route.ts +65 -0
  142. package/templates/storefront-pharmacy/app/about/page.tsx +41 -0
  143. package/templates/storefront-pharmacy/app/accessibility/page.tsx +11 -0
  144. package/templates/storefront-pharmacy/app/account/addresses/page.tsx +21 -0
  145. package/templates/storefront-pharmacy/app/account/orders/page.tsx +21 -0
  146. package/templates/storefront-pharmacy/app/account/page.tsx +22 -0
  147. package/templates/storefront-pharmacy/app/account/settings/page.tsx +21 -0
  148. package/templates/storefront-pharmacy/app/cart/page.tsx +9 -0
  149. package/templates/storefront-pharmacy/app/categories/[slug]/listing-client.tsx +19 -0
  150. package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +130 -0
  151. package/templates/storefront-pharmacy/app/checkout/page.tsx +17 -0
  152. package/templates/storefront-pharmacy/app/collections/[slug]/listing-client.tsx +20 -0
  153. package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +130 -0
  154. package/templates/storefront-pharmacy/app/contact/contact-form.tsx +109 -0
  155. package/templates/storefront-pharmacy/app/contact/page.tsx +54 -0
  156. package/templates/storefront-pharmacy/app/error.tsx +61 -0
  157. package/templates/storefront-pharmacy/app/faq/page.tsx +46 -0
  158. package/templates/storefront-pharmacy/app/globals.css +47 -0
  159. package/templates/storefront-pharmacy/app/layout.tsx +77 -0
  160. package/templates/storefront-pharmacy/app/llms.txt/route.ts +94 -0
  161. package/templates/storefront-pharmacy/app/login/page.tsx +17 -0
  162. package/templates/storefront-pharmacy/app/not-found.tsx +39 -0
  163. package/templates/storefront-pharmacy/app/opensearch.xml/route.ts +37 -0
  164. package/templates/storefront-pharmacy/app/orders/[id]/page.tsx +24 -0
  165. package/templates/storefront-pharmacy/app/page.tsx +78 -0
  166. package/templates/storefront-pharmacy/app/privacy/page.tsx +44 -0
  167. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +165 -0
  168. package/templates/storefront-pharmacy/app/products/[slug]/product-detail.tsx +70 -0
  169. package/templates/storefront-pharmacy/app/returns/page.tsx +11 -0
  170. package/templates/storefront-pharmacy/app/robots.ts +18 -0
  171. package/templates/storefront-pharmacy/app/search/page.tsx +38 -0
  172. package/templates/storefront-pharmacy/app/search/search-client.tsx +7 -0
  173. package/templates/storefront-pharmacy/app/shipping/page.tsx +16 -0
  174. package/templates/storefront-pharmacy/app/shop/page.tsx +63 -0
  175. package/templates/storefront-pharmacy/app/shop/shop-client.tsx +32 -0
  176. package/templates/storefront-pharmacy/app/signup/page.tsx +17 -0
  177. package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +167 -0
  178. package/templates/storefront-pharmacy/app/sitemap.ts +59 -0
  179. package/templates/storefront-pharmacy/app/terms/page.tsx +44 -0
  180. package/templates/storefront-pharmacy/app/track-order/page.tsx +24 -0
  181. package/templates/storefront-pharmacy/app/track-order/track-order-form.tsx +69 -0
  182. package/templates/storefront-pharmacy/components/account-iframe.tsx +13 -0
  183. package/templates/storefront-pharmacy/components/brand-marquee.tsx +27 -0
  184. package/templates/storefront-pharmacy/components/cart-drawer.tsx +14 -0
  185. package/templates/storefront-pharmacy/components/cart-pill.tsx +36 -0
  186. package/templates/storefront-pharmacy/components/category-grid.tsx +28 -0
  187. package/templates/storefront-pharmacy/components/category-tiles.tsx +104 -0
  188. package/templates/storefront-pharmacy/components/collection-strip.tsx +45 -0
  189. package/templates/storefront-pharmacy/components/feature-hero.tsx +84 -0
  190. package/templates/storefront-pharmacy/components/footer.tsx +153 -0
  191. package/templates/storefront-pharmacy/components/header.tsx +45 -0
  192. package/templates/storefront-pharmacy/components/health-brief.tsx +65 -0
  193. package/templates/storefront-pharmacy/components/hero.tsx +28 -0
  194. package/templates/storefront-pharmacy/components/nav-link.tsx +20 -0
  195. package/templates/storefront-pharmacy/components/newsletter.tsx +50 -0
  196. package/templates/storefront-pharmacy/components/pharmacy-hero.tsx +95 -0
  197. package/templates/storefront-pharmacy/components/policy-page.tsx +49 -0
  198. package/templates/storefront-pharmacy/components/promo-banner.tsx +41 -0
  199. package/templates/storefront-pharmacy/components/providers.tsx +35 -0
  200. package/templates/storefront-pharmacy/components/section-heading.tsx +37 -0
  201. package/templates/storefront-pharmacy/components/store-product-card.tsx +88 -0
  202. package/templates/storefront-pharmacy/components/symptom-finder.tsx +108 -0
  203. package/templates/storefront-pharmacy/components/trade-in-cta.tsx +54 -0
  204. package/templates/storefront-pharmacy/components/trust-bar.tsx +66 -0
  205. package/templates/storefront-pharmacy/components/urgent-ctas.tsx +117 -0
  206. package/templates/storefront-pharmacy/lib/brand.ts +790 -0
  207. package/templates/storefront-pharmacy/lib/cart.ts +12 -0
  208. package/templates/storefront-pharmacy/lib/cimplify-loader.ts +19 -0
  209. package/templates/storefront-pharmacy/next.config.ts +45 -0
  210. package/templates/storefront-pharmacy/package.json +35 -0
  211. package/templates/storefront-pharmacy/postcss.config.mjs +7 -0
  212. package/templates/storefront-pharmacy/tsconfig.json +23 -0
  213. package/templates/storefront-pharmacy/vitest.config.ts +9 -0
  214. package/templates/storefront-restaurant/.env.example +2 -2
  215. package/templates/storefront-restaurant/README.md +1 -1
  216. package/templates/storefront-restaurant/lib/cimplify-loader.ts +19 -0
  217. package/templates/storefront-restaurant/next.config.ts +3 -0
  218. package/templates/storefront-restaurant/package.json +1 -1
  219. package/templates/storefront-retail/.env.example +2 -2
  220. package/templates/storefront-retail/README.md +1 -1
  221. package/templates/storefront-retail/lib/cimplify-loader.ts +19 -0
  222. package/templates/storefront-retail/next.config.ts +3 -0
  223. package/templates/storefront-retail/package.json +1 -1
  224. package/templates/storefront-services/.env.example +2 -2
  225. package/templates/storefront-services/README.md +1 -1
  226. package/templates/storefront-services/lib/cimplify-loader.ts +19 -0
  227. package/templates/storefront-services/next.config.ts +3 -0
  228. package/templates/storefront-services/package.json +1 -1
  229. package/dist/repo-E6SBKVDG.mjs +0 -8
@@ -0,0 +1,77 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter, JetBrains_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+ import { Providers } from "@/components/providers";
5
+ import { Header } from "@/components/header";
6
+ import { Footer } from "@/components/footer";
7
+ import { CartDrawer } from "@/components/cart-drawer";
8
+ import { brand } from "@/lib/brand";
9
+
10
+ const inter = Inter({
11
+ subsets: ["latin"],
12
+ variable: "--font-sans",
13
+ display: "swap",
14
+ });
15
+
16
+ const mono = JetBrains_Mono({
17
+ subsets: ["latin"],
18
+ variable: "--font-mono",
19
+ display: "swap",
20
+ });
21
+
22
+ const SITE_URL =
23
+ process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
24
+
25
+ export const metadata: Metadata = {
26
+ metadataBase: new URL(SITE_URL),
27
+ title: {
28
+ default: brand.name,
29
+ template: `%s — ${brand.name}`,
30
+ },
31
+ description: brand.description,
32
+ openGraph: {
33
+ type: "website",
34
+ siteName: brand.name,
35
+ locale: brand.locale,
36
+ },
37
+ twitter: { card: "summary_large_image" },
38
+ };
39
+
40
+ const ORGANIZATION_LD = {
41
+ "@context": "https://schema.org",
42
+ "@type": brand.schemaType,
43
+ name: brand.name,
44
+ url: SITE_URL,
45
+ description: brand.description,
46
+ email: brand.contact.email,
47
+ telephone: brand.contact.phoneTel,
48
+ address: {
49
+ "@type": "PostalAddress",
50
+ streetAddress: brand.contact.streetAddress,
51
+ addressLocality: brand.contact.city,
52
+ addressCountry: brand.contact.countryCode,
53
+ },
54
+ sameAs: brand.socials.map((s) => s.href),
55
+ };
56
+
57
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
58
+ return (
59
+ <html lang="en" suppressHydrationWarning className={`${inter.variable} ${mono.variable}`}>
60
+ <body
61
+ suppressHydrationWarning
62
+ className="min-h-screen flex flex-col bg-background text-foreground font-sans"
63
+ >
64
+ <script
65
+ type="application/ld+json"
66
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(ORGANIZATION_LD) }}
67
+ />
68
+ <Providers>
69
+ <Header />
70
+ <main className="flex-1 pb-12 w-full">{children}</main>
71
+ <Footer />
72
+ <CartDrawer />
73
+ </Providers>
74
+ </body>
75
+ </html>
76
+ );
77
+ }
@@ -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 = `${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&apos;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, "&amp;")
33
+ .replace(/</g, "&lt;")
34
+ .replace(/>/g, "&gt;")
35
+ .replace(/"/g, "&quot;")
36
+ .replace(/'/g, "&apos;");
37
+ }
@@ -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,94 @@
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 { AutoHero } from "@/components/auto-hero";
6
+ import { FitmentFinder } from "@/components/fitment-finder";
7
+ import { TrustBar } from "@/components/trust-bar";
8
+ import { ServiceBrief } from "@/components/service-brief";
9
+ import { Newsletter } from "@/components/newsletter";
10
+ import { SectionHeading } from "@/components/section-heading";
11
+ import { StoreProductCard } from "@/components/store-product-card";
12
+ import { brand } from "@/lib/brand";
13
+
14
+ export const metadata: Metadata = {
15
+ title: brand.hero.title,
16
+ description: brand.description,
17
+ };
18
+
19
+ async function getHomeData() {
20
+ "use cache";
21
+ cacheTag(tags.products());
22
+ cacheLife("hours");
23
+
24
+ const client = getServerClient();
25
+ const productsRes = await client.catalogue.getProducts({ limit: 12 });
26
+ const allProducts: Product[] = productsRes.ok ? productsRes.value.items : [];
27
+
28
+ return {
29
+ mostOrdered: allProducts.slice(0, 8),
30
+ };
31
+ }
32
+
33
+ export default async function HomePage() {
34
+ const { mostOrdered } = await getHomeData();
35
+
36
+ return (
37
+ <>
38
+ <AutoHero />
39
+
40
+ <Suspense fallback={<FitmentFinderSkeleton />}>
41
+ <FitmentFinder />
42
+ </Suspense>
43
+
44
+ <TrustBar />
45
+
46
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
47
+ <SectionHeading
48
+ eyebrow="Most ordered"
49
+ title="What our garages keep on the shelf."
50
+ description="The parts Driveline ships daily — oil, filters, brake pads, batteries. Every fitment verified by our parts team."
51
+ link={{ label: "Shop everything", href: "/shop" }}
52
+ />
53
+ <Suspense fallback={<GridSkeleton count={8} />}>
54
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
55
+ {mostOrdered.map((p) => (
56
+ <StoreProductCard key={p.id} product={p} />
57
+ ))}
58
+ </div>
59
+ </Suspense>
60
+ </section>
61
+
62
+ <ServiceBrief />
63
+
64
+ <Newsletter />
65
+ </>
66
+ );
67
+ }
68
+
69
+ function GridSkeleton({ count }: { count: number }) {
70
+ return (
71
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
72
+ {Array.from({ length: count }).map((_, i) => (
73
+ <div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
74
+ ))}
75
+ </div>
76
+ );
77
+ }
78
+
79
+ function FitmentFinderSkeleton() {
80
+ return (
81
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 -mt-10 sm:-mt-14 relative z-10">
82
+ <div className="bg-card border border-border rounded-2xl p-6 sm:p-8 shadow-[0_8px_30px_rgb(0_0_0/0.06)]">
83
+ <div className="h-7 w-48 bg-muted rounded mb-3 animate-pulse" />
84
+ <div className="h-5 w-80 bg-muted rounded mb-6 animate-pulse" />
85
+ <div className="grid grid-cols-1 md:grid-cols-[1.2fr_1.2fr_1fr_auto] gap-3">
86
+ <div className="h-11 bg-muted rounded-xl animate-pulse" />
87
+ <div className="h-11 bg-muted rounded-xl animate-pulse" />
88
+ <div className="h-11 bg-muted rounded-xl animate-pulse" />
89
+ <div className="h-11 w-36 bg-primary/30 rounded-xl animate-pulse" />
90
+ </div>
91
+ </div>
92
+ </section>
93
+ );
94
+ }
@@ -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-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
14
+ {p.eyebrow}
15
+ </p>
16
+ <h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
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="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
+ ))}
41
+ </section>
42
+ </article>
43
+ );
44
+ }
@@ -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: 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,70 @@
1
+ "use client";
2
+
3
+ import Image from "next/image";
4
+ import { ProductPage as SdkProductPage, useCart } from "@cimplify/sdk/react";
5
+ import type { Product, ProductWithDetails } from "@cimplify/sdk";
6
+ import { StoreProductCard } from "@/components/store-product-card";
7
+
8
+ /**
9
+ * Client island for the product detail page.
10
+ *
11
+ * - Receives a server-fetched `ProductWithDetails` (no client refetch).
12
+ * - Renders the SDK `<ProductPage>` which picks the right layout
13
+ * (Default / Wholesale / Service / Bundle / Composite) automatically.
14
+ * - On add-to-cart success, routes to `/cart`.
15
+ * - Custom Next.js Image renderer for optimised, lazy-loaded gallery shots.
16
+ * - Renders a "You may also like" rail of in-category products below.
17
+ */
18
+ export function ProductDetail({
19
+ product,
20
+ related,
21
+ }: {
22
+ product: ProductWithDetails;
23
+ related: Product[];
24
+ }) {
25
+ const { addItem } = useCart();
26
+
27
+ return (
28
+ <>
29
+ <SdkProductPage
30
+ product={product}
31
+ showRelated={false}
32
+ onAddToCart={async (p, qty, options) => {
33
+ await addItem(p, qty, options);
34
+ }}
35
+ renderImage={({ src, alt, className }) => (
36
+ <Image
37
+ src={src}
38
+ alt={alt}
39
+ width={1200}
40
+ height={1200}
41
+ className={className}
42
+ style={{ width: "100%", height: "auto", objectFit: "cover" }}
43
+ priority
44
+ />
45
+ )}
46
+ className="max-w-7xl mx-auto px-6 sm:px-8 py-8 sm:py-10"
47
+ />
48
+
49
+ {related.length > 0 && (
50
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-16 border-t border-border mt-8">
51
+ <div className="flex items-end justify-between gap-6 mb-8">
52
+ <div>
53
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
54
+ You may also like
55
+ </p>
56
+ <h2 className="text-[clamp(1.5rem,2.5vw,2rem)] font-bold m-0 -tracking-[0.025em]">
57
+ More from this category.
58
+ </h2>
59
+ </div>
60
+ </div>
61
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
62
+ {related.map((p) => (
63
+ <StoreProductCard key={p.id} product={p} />
64
+ ))}
65
+ </div>
66
+ </section>
67
+ )}
68
+ </>
69
+ );
70
+ }
@@ -0,0 +1,11 @@
1
+ import type { Metadata } from "next";
2
+ import { brand } from "@/lib/brand";
3
+ import { PolicyPage } from "@/components/policy-page";
4
+
5
+ export const metadata: Metadata = {
6
+ title: `${brand.returns.title} — ${brand.name}`,
7
+ };
8
+
9
+ export default function ReturnsPage() {
10
+ return <PolicyPage policy={brand.returns} />;
11
+ }