@cimplify/sdk 0.41.0 → 0.42.1

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 (148) hide show
  1. package/dist/cli/commands/deploy.mjs +561 -0
  2. package/dist/cli/commands/dev.mjs +434 -0
  3. package/dist/cli/commands/domains.mjs +479 -0
  4. package/dist/cli/commands/env.mjs +610 -0
  5. package/dist/cli/commands/link.mjs +325 -0
  6. package/dist/cli/commands/login.mjs +515 -0
  7. package/dist/cli/commands/logout.mjs +54 -0
  8. package/dist/cli/commands/logs.mjs +358 -0
  9. package/dist/cli/commands/projects.mjs +327 -0
  10. package/dist/cli/commands/rollback.mjs +477 -0
  11. package/dist/cli/commands/status.mjs +380 -0
  12. package/dist/cli/commands/unlink.mjs +58 -0
  13. package/dist/cli/commands/whoami.mjs +234 -0
  14. package/dist/dispatcher.mjs +91 -0
  15. package/dist/styles.css +1 -1
  16. package/package.json +1 -1
  17. package/templates/storefront-bakery/AGENTS.md +61 -10
  18. package/templates/storefront-bakery/app/accessibility/page.tsx +11 -0
  19. package/templates/storefront-bakery/app/account/addresses/page.tsx +21 -0
  20. package/templates/storefront-bakery/app/account/orders/page.tsx +21 -0
  21. package/templates/storefront-bakery/app/account/page.tsx +22 -0
  22. package/templates/storefront-bakery/app/account/settings/page.tsx +21 -0
  23. package/templates/storefront-bakery/app/contact/contact-form.tsx +109 -0
  24. package/templates/storefront-bakery/app/contact/page.tsx +54 -0
  25. package/templates/storefront-bakery/app/login/page.tsx +17 -0
  26. package/templates/storefront-bakery/app/opensearch.xml/route.ts +37 -0
  27. package/templates/storefront-bakery/app/returns/page.tsx +11 -0
  28. package/templates/storefront-bakery/app/search/page.tsx +38 -0
  29. package/templates/storefront-bakery/app/search/search-client.tsx +7 -0
  30. package/templates/storefront-bakery/app/shipping/page.tsx +16 -0
  31. package/templates/storefront-bakery/app/signup/page.tsx +17 -0
  32. package/templates/storefront-bakery/app/sitemap-page/page.tsx +167 -0
  33. package/templates/storefront-bakery/app/track-order/page.tsx +24 -0
  34. package/templates/storefront-bakery/app/track-order/track-order-form.tsx +69 -0
  35. package/templates/storefront-bakery/components/account-iframe.tsx +13 -0
  36. package/templates/storefront-bakery/components/policy-page.tsx +49 -0
  37. package/templates/storefront-bakery/lib/brand.ts +119 -4
  38. package/templates/storefront-fashion/AGENTS.md +80 -26
  39. package/templates/storefront-fashion/app/accessibility/page.tsx +11 -0
  40. package/templates/storefront-fashion/app/account/addresses/page.tsx +21 -0
  41. package/templates/storefront-fashion/app/account/orders/page.tsx +21 -0
  42. package/templates/storefront-fashion/app/account/page.tsx +22 -0
  43. package/templates/storefront-fashion/app/account/settings/page.tsx +21 -0
  44. package/templates/storefront-fashion/app/contact/contact-form.tsx +109 -0
  45. package/templates/storefront-fashion/app/contact/page.tsx +54 -0
  46. package/templates/storefront-fashion/app/login/page.tsx +17 -0
  47. package/templates/storefront-fashion/app/lookbook/page.tsx +132 -0
  48. package/templates/storefront-fashion/app/opensearch.xml/route.ts +37 -0
  49. package/templates/storefront-fashion/app/returns/page.tsx +11 -0
  50. package/templates/storefront-fashion/app/search/page.tsx +38 -0
  51. package/templates/storefront-fashion/app/search/search-client.tsx +7 -0
  52. package/templates/storefront-fashion/app/shipping/page.tsx +16 -0
  53. package/templates/storefront-fashion/app/signup/page.tsx +17 -0
  54. package/templates/storefront-fashion/app/sitemap-page/page.tsx +167 -0
  55. package/templates/storefront-fashion/app/size-guide/page.tsx +155 -0
  56. package/templates/storefront-fashion/app/track-order/page.tsx +24 -0
  57. package/templates/storefront-fashion/app/track-order/track-order-form.tsx +69 -0
  58. package/templates/storefront-fashion/components/account-iframe.tsx +13 -0
  59. package/templates/storefront-fashion/components/policy-page.tsx +49 -0
  60. package/templates/storefront-fashion/lib/brand.ts +99 -4
  61. package/templates/storefront-grocery/AGENTS.md +56 -11
  62. package/templates/storefront-grocery/app/accessibility/page.tsx +11 -0
  63. package/templates/storefront-grocery/app/account/addresses/page.tsx +21 -0
  64. package/templates/storefront-grocery/app/account/orders/page.tsx +21 -0
  65. package/templates/storefront-grocery/app/account/page.tsx +22 -0
  66. package/templates/storefront-grocery/app/account/settings/page.tsx +21 -0
  67. package/templates/storefront-grocery/app/contact/contact-form.tsx +109 -0
  68. package/templates/storefront-grocery/app/contact/page.tsx +54 -0
  69. package/templates/storefront-grocery/app/login/page.tsx +17 -0
  70. package/templates/storefront-grocery/app/opensearch.xml/route.ts +37 -0
  71. package/templates/storefront-grocery/app/returns/page.tsx +11 -0
  72. package/templates/storefront-grocery/app/search/page.tsx +38 -0
  73. package/templates/storefront-grocery/app/search/search-client.tsx +7 -0
  74. package/templates/storefront-grocery/app/shipping/page.tsx +16 -0
  75. package/templates/storefront-grocery/app/signup/page.tsx +17 -0
  76. package/templates/storefront-grocery/app/sitemap-page/page.tsx +167 -0
  77. package/templates/storefront-grocery/app/track-order/page.tsx +24 -0
  78. package/templates/storefront-grocery/app/track-order/track-order-form.tsx +69 -0
  79. package/templates/storefront-grocery/components/account-iframe.tsx +13 -0
  80. package/templates/storefront-grocery/components/policy-page.tsx +49 -0
  81. package/templates/storefront-grocery/lib/brand.ts +92 -3
  82. package/templates/storefront-restaurant/AGENTS.md +64 -13
  83. package/templates/storefront-restaurant/app/accessibility/page.tsx +11 -0
  84. package/templates/storefront-restaurant/app/account/addresses/page.tsx +21 -0
  85. package/templates/storefront-restaurant/app/account/orders/page.tsx +21 -0
  86. package/templates/storefront-restaurant/app/account/page.tsx +22 -0
  87. package/templates/storefront-restaurant/app/account/settings/page.tsx +21 -0
  88. package/templates/storefront-restaurant/app/contact/contact-form.tsx +109 -0
  89. package/templates/storefront-restaurant/app/contact/page.tsx +54 -0
  90. package/templates/storefront-restaurant/app/login/page.tsx +17 -0
  91. package/templates/storefront-restaurant/app/opensearch.xml/route.ts +37 -0
  92. package/templates/storefront-restaurant/app/reservations/page.tsx +66 -0
  93. package/templates/storefront-restaurant/app/reservations/reservations-client.tsx +234 -0
  94. package/templates/storefront-restaurant/app/returns/page.tsx +11 -0
  95. package/templates/storefront-restaurant/app/search/page.tsx +38 -0
  96. package/templates/storefront-restaurant/app/search/search-client.tsx +7 -0
  97. package/templates/storefront-restaurant/app/shipping/page.tsx +16 -0
  98. package/templates/storefront-restaurant/app/signup/page.tsx +17 -0
  99. package/templates/storefront-restaurant/app/sitemap-page/page.tsx +167 -0
  100. package/templates/storefront-restaurant/app/track-order/page.tsx +24 -0
  101. package/templates/storefront-restaurant/app/track-order/track-order-form.tsx +69 -0
  102. package/templates/storefront-restaurant/components/account-iframe.tsx +13 -0
  103. package/templates/storefront-restaurant/components/policy-page.tsx +49 -0
  104. package/templates/storefront-restaurant/lib/brand.ts +91 -3
  105. package/templates/storefront-retail/AGENTS.md +65 -24
  106. package/templates/storefront-retail/app/accessibility/page.tsx +11 -0
  107. package/templates/storefront-retail/app/account/addresses/page.tsx +21 -0
  108. package/templates/storefront-retail/app/account/orders/page.tsx +21 -0
  109. package/templates/storefront-retail/app/account/page.tsx +22 -0
  110. package/templates/storefront-retail/app/account/settings/page.tsx +21 -0
  111. package/templates/storefront-retail/app/contact/contact-form.tsx +109 -0
  112. package/templates/storefront-retail/app/contact/page.tsx +54 -0
  113. package/templates/storefront-retail/app/login/page.tsx +17 -0
  114. package/templates/storefront-retail/app/opensearch.xml/route.ts +37 -0
  115. package/templates/storefront-retail/app/returns/page.tsx +11 -0
  116. package/templates/storefront-retail/app/search/page.tsx +38 -0
  117. package/templates/storefront-retail/app/search/search-client.tsx +7 -0
  118. package/templates/storefront-retail/app/shipping/page.tsx +16 -0
  119. package/templates/storefront-retail/app/signup/page.tsx +17 -0
  120. package/templates/storefront-retail/app/sitemap-page/page.tsx +167 -0
  121. package/templates/storefront-retail/app/track-order/page.tsx +24 -0
  122. package/templates/storefront-retail/app/track-order/track-order-form.tsx +69 -0
  123. package/templates/storefront-retail/components/account-iframe.tsx +13 -0
  124. package/templates/storefront-retail/components/policy-page.tsx +49 -0
  125. package/templates/storefront-retail/lib/brand.ts +105 -9
  126. package/templates/storefront-services/AGENTS.md +64 -16
  127. package/templates/storefront-services/app/accessibility/page.tsx +11 -0
  128. package/templates/storefront-services/app/account/addresses/page.tsx +21 -0
  129. package/templates/storefront-services/app/account/orders/page.tsx +21 -0
  130. package/templates/storefront-services/app/account/page.tsx +22 -0
  131. package/templates/storefront-services/app/account/settings/page.tsx +21 -0
  132. package/templates/storefront-services/app/book/book-client.tsx +195 -0
  133. package/templates/storefront-services/app/book/page.tsx +65 -0
  134. package/templates/storefront-services/app/contact/contact-form.tsx +109 -0
  135. package/templates/storefront-services/app/contact/page.tsx +54 -0
  136. package/templates/storefront-services/app/login/page.tsx +17 -0
  137. package/templates/storefront-services/app/opensearch.xml/route.ts +37 -0
  138. package/templates/storefront-services/app/returns/page.tsx +11 -0
  139. package/templates/storefront-services/app/search/page.tsx +38 -0
  140. package/templates/storefront-services/app/search/search-client.tsx +7 -0
  141. package/templates/storefront-services/app/shipping/page.tsx +16 -0
  142. package/templates/storefront-services/app/signup/page.tsx +17 -0
  143. package/templates/storefront-services/app/sitemap-page/page.tsx +167 -0
  144. package/templates/storefront-services/app/track-order/page.tsx +24 -0
  145. package/templates/storefront-services/app/track-order/track-order-form.tsx +69 -0
  146. package/templates/storefront-services/components/account-iframe.tsx +13 -0
  147. package/templates/storefront-services/components/policy-page.tsx +49 -0
  148. package/templates/storefront-services/lib/brand.ts +93 -2
@@ -0,0 +1,54 @@
1
+ import type { Metadata } from "next";
2
+ import { ContactForm } from "./contact-form";
3
+ import { brand } from "@/lib/brand";
4
+
5
+ export const metadata: Metadata = {
6
+ title: `Contact — ${brand.name}`,
7
+ description: brand.contactPage.body,
8
+ };
9
+
10
+ export default function ContactPage() {
11
+ const c = brand.contactPage;
12
+ return (
13
+ <article className="max-w-5xl mx-auto px-6 sm:px-8 py-16">
14
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
15
+ {c.eyebrow}
16
+ </p>
17
+ <h1 className="font-serif text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-4 -tracking-[0.02em]">
18
+ {c.title}
19
+ </h1>
20
+ <p className="text-lg text-muted-foreground max-w-2xl mb-12 leading-relaxed">{c.body}</p>
21
+
22
+ <div className="grid grid-cols-1 lg:grid-cols-[1.5fr_1fr] gap-10 items-start">
23
+ <ContactForm reasons={c.reasons} />
24
+ <aside className="space-y-5 lg:pl-10 lg:border-l border-border">
25
+ <div>
26
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground mb-3">
27
+ Direct lines
28
+ </p>
29
+ <ul className="space-y-3 list-none p-0 m-0">
30
+ {c.directLines.map((line) => (
31
+ <li key={line.label}>
32
+ <p className="text-xs text-muted-foreground m-0">{line.label}</p>
33
+ <a
34
+ href={line.href}
35
+ className="text-foreground hover:text-primary transition-colors"
36
+ >
37
+ {line.value}
38
+ </a>
39
+ </li>
40
+ ))}
41
+ </ul>
42
+ </div>
43
+ <div>
44
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground mb-3">
45
+ Visit
46
+ </p>
47
+ <p className="text-foreground m-0">{brand.contact.address}</p>
48
+ <p className="text-xs text-muted-foreground mt-1">{brand.contact.hours}</p>
49
+ </div>
50
+ </aside>
51
+ </div>
52
+ </article>
53
+ );
54
+ }
@@ -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,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,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
+ }
@@ -0,0 +1,38 @@
1
+ import type { Metadata } from "next";
2
+ import { Suspense } from "react";
3
+ import { SearchClient } from "./search-client";
4
+ import { brand } from "@/lib/brand";
5
+
6
+ export const metadata: Metadata = {
7
+ title: `Search — ${brand.name}`,
8
+ description: `Search ${brand.name} — products, collections, categories.`,
9
+ };
10
+
11
+ export default function SearchPage() {
12
+ return (
13
+ <article className="max-w-7xl mx-auto px-6 sm:px-8 py-10">
14
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
15
+ Search
16
+ </p>
17
+ <h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
18
+ Find anything.
19
+ </h1>
20
+ <Suspense fallback={<SearchSkeleton />}>
21
+ <SearchClient />
22
+ </Suspense>
23
+ </article>
24
+ );
25
+ }
26
+
27
+ function SearchSkeleton() {
28
+ return (
29
+ <div>
30
+ <div className="h-12 w-full max-w-xl bg-muted rounded animate-pulse mb-8" />
31
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
32
+ {Array.from({ length: 8 }).map((_, i) => (
33
+ <div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
34
+ ))}
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,7 @@
1
+ "use client";
2
+
3
+ import { SearchPage } from "@cimplify/sdk/react";
4
+
5
+ export function SearchClient() {
6
+ return <SearchPage />;
7
+ }
@@ -0,0 +1,16 @@
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.shipping.title} — ${brand.name}`,
7
+ description: brand.shipping.sections[0]?.body
8
+ ? typeof brand.shipping.sections[0].body === "string"
9
+ ? brand.shipping.sections[0].body
10
+ : brand.shipping.sections[0].body.intro
11
+ : undefined,
12
+ };
13
+
14
+ export default function ShippingPage() {
15
+ return <PolicyPage policy={brand.shipping} />;
16
+ }
@@ -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: `Create an account — ${brand.name}`,
7
+ description: brand.account.signupSubtitle,
8
+ };
9
+
10
+ /**
11
+ * Cimplify Link handles sign-up inside the `<CimplifyAccount>` iframe.
12
+ * We bounce /signup to /account; the iframe shows the create-account UI
13
+ * for visitors with no session.
14
+ */
15
+ export default function SignupPage(): never {
16
+ redirect("/account");
17
+ }
@@ -0,0 +1,167 @@
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+ import { cacheTag, cacheLife } from "next/cache";
4
+ import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
+ import { brand } from "@/lib/brand";
6
+
7
+ export const metadata: Metadata = {
8
+ title: `Sitemap — ${brand.name}`,
9
+ description: "A human-readable index of every page on this site.",
10
+ };
11
+
12
+ interface SitemapData {
13
+ products: { slug: string; name: string }[];
14
+ categories: { slug: string; name: string }[];
15
+ collections: { slug: string; name: string }[];
16
+ }
17
+
18
+ async function getSitemap(): Promise<SitemapData> {
19
+ "use cache";
20
+ cacheTag(tags.products(), tags.categories(), tags.collections());
21
+ cacheLife("hours");
22
+
23
+ const client = getServerClient();
24
+ const [pRes, cRes, colRes] = await Promise.all([
25
+ client.catalogue.getProducts({ limit: 500 }),
26
+ client.catalogue.getCategories(),
27
+ client.catalogue.getCollections(),
28
+ ]);
29
+ return {
30
+ products: (pRes.ok ? pRes.value.items : []).map((p: Product) => ({
31
+ slug: p.slug ?? p.id,
32
+ name: p.name,
33
+ })),
34
+ categories: (cRes.ok ? cRes.value : []).map((c) => ({ slug: c.slug, name: c.name })),
35
+ collections: (colRes.ok ? colRes.value : []).map((c) => ({ slug: c.slug, name: c.name })),
36
+ };
37
+ }
38
+
39
+ const STATIC_LINKS: { title: string; links: { href: string; label: string }[] }[] = [
40
+ {
41
+ title: "Browse",
42
+ links: [
43
+ { href: "/", label: "Home" },
44
+ { href: "/shop", label: "Shop" },
45
+ { href: "/search", label: "Search" },
46
+ ],
47
+ },
48
+ {
49
+ title: "Account",
50
+ links: [
51
+ { href: "/account", label: "Account" },
52
+ { href: "/account/orders", label: "Orders" },
53
+ { href: "/account/addresses", label: "Addresses" },
54
+ { href: "/account/settings", label: "Settings" },
55
+ { href: "/login", label: "Sign in" },
56
+ { href: "/signup", label: "Create account" },
57
+ { href: "/track-order", label: "Track an order" },
58
+ { href: "/cart", label: "Cart" },
59
+ { href: "/checkout", label: "Checkout" },
60
+ ],
61
+ },
62
+ {
63
+ title: "About",
64
+ links: [
65
+ { href: "/about", label: "About" },
66
+ { href: "/faq", label: "FAQ" },
67
+ { href: "/contact", label: "Contact" },
68
+ ],
69
+ },
70
+ {
71
+ title: "Policies",
72
+ links: [
73
+ { href: "/shipping", label: "Shipping" },
74
+ { href: "/returns", label: "Returns" },
75
+ { href: "/accessibility", label: "Accessibility" },
76
+ { href: "/terms", label: "Terms of Service" },
77
+ { href: "/privacy", label: "Privacy Policy" },
78
+ ],
79
+ },
80
+ {
81
+ title: "Machine-readable",
82
+ links: [
83
+ { href: "/sitemap.xml", label: "sitemap.xml (search engines)" },
84
+ { href: "/llms.txt", label: "llms.txt (LLM agents)" },
85
+ { href: "/robots.txt", label: "robots.txt" },
86
+ { href: "/opensearch.xml", label: "opensearch.xml (browser search)" },
87
+ ],
88
+ },
89
+ ];
90
+
91
+ export default async function SitemapHtmlPage() {
92
+ const { products, categories, collections } = await getSitemap();
93
+
94
+ return (
95
+ <article className="max-w-5xl mx-auto px-6 sm:px-8 py-16">
96
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
97
+ Sitemap
98
+ </p>
99
+ <h1 className="text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-2 -tracking-[0.02em]">
100
+ Every page, in one place.
101
+ </h1>
102
+ <p className="text-muted-foreground mb-12">
103
+ For search engines, see <Link href="/sitemap.xml" className="text-primary hover:underline">/sitemap.xml</Link>.
104
+ For LLM agents, see <Link href="/llms.txt" className="text-primary hover:underline">/llms.txt</Link>.
105
+ </p>
106
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
107
+ {STATIC_LINKS.map((s) => (
108
+ <Section key={s.title} title={s.title}>
109
+ {s.links.map((l) => (
110
+ <li key={l.href}>
111
+ <Link href={l.href} className="hover:text-primary transition-colors">
112
+ {l.label}
113
+ </Link>
114
+ </li>
115
+ ))}
116
+ </Section>
117
+ ))}
118
+ {categories.length > 0 && (
119
+ <Section title="Categories">
120
+ {categories.map((c) => (
121
+ <li key={c.slug}>
122
+ <Link href={`/categories/${c.slug}`} className="hover:text-primary transition-colors">
123
+ {c.name}
124
+ </Link>
125
+ </li>
126
+ ))}
127
+ </Section>
128
+ )}
129
+ {collections.length > 0 && (
130
+ <Section title="Collections">
131
+ {collections.map((c) => (
132
+ <li key={c.slug}>
133
+ <Link href={`/collections/${c.slug}`} className="hover:text-primary transition-colors">
134
+ {c.name}
135
+ </Link>
136
+ </li>
137
+ ))}
138
+ </Section>
139
+ )}
140
+ {products.length > 0 && (
141
+ <Section title={`Products (${products.length})`}>
142
+ {products.map((p) => (
143
+ <li key={p.slug}>
144
+ <Link href={`/shop?product=${encodeURIComponent(p.slug)}`} className="hover:text-primary transition-colors">
145
+ {p.name}
146
+ </Link>
147
+ </li>
148
+ ))}
149
+ </Section>
150
+ )}
151
+ </div>
152
+ </article>
153
+ );
154
+ }
155
+
156
+ function Section({ title, children }: { title: string; children: React.ReactNode }) {
157
+ return (
158
+ <div>
159
+ <p className="font-semibold text-[12px] uppercase tracking-[0.12em] text-foreground mb-3">
160
+ {title}
161
+ </p>
162
+ <ul className="space-y-2 m-0 p-0 list-none text-sm text-muted-foreground">
163
+ {children}
164
+ </ul>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,24 @@
1
+ import type { Metadata } from "next";
2
+ import { TrackOrderForm } from "./track-order-form";
3
+ import { brand } from "@/lib/brand";
4
+
5
+ export const metadata: Metadata = {
6
+ title: `Track an order — ${brand.name}`,
7
+ description: brand.trackOrder.body,
8
+ };
9
+
10
+ export default function TrackOrderPage() {
11
+ const t = brand.trackOrder;
12
+ return (
13
+ <article className="max-w-2xl mx-auto px-6 sm:px-8 py-16">
14
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
15
+ {t.eyebrow}
16
+ </p>
17
+ <h1 className="font-serif text-[clamp(2rem,5vw,3rem)] font-semibold mb-4 -tracking-[0.02em]">
18
+ {t.title}
19
+ </h1>
20
+ <p className="text-muted-foreground leading-relaxed mb-8">{t.body}</p>
21
+ <TrackOrderForm />
22
+ </article>
23
+ );
24
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+
6
+ /**
7
+ * Guest order tracker. Routes /orders/<id> with the entered id; the
8
+ * post-checkout confirmation page already lives at /orders/[id] so this
9
+ * just sends the visitor there. Replace the redirect with a real lookup
10
+ * (e.g. Cimplify orders API + email-match guard) for production.
11
+ */
12
+ export function TrackOrderForm() {
13
+ const router = useRouter();
14
+ const [orderId, setOrderId] = useState("");
15
+ const [email, setEmail] = useState("");
16
+
17
+ return (
18
+ <form
19
+ onSubmit={(e) => {
20
+ e.preventDefault();
21
+ if (!orderId.trim()) return;
22
+ router.push(`/orders/${encodeURIComponent(orderId.trim())}`);
23
+ }}
24
+ className="space-y-4 rounded-2xl border border-border bg-card p-6"
25
+ >
26
+ <div>
27
+ <label
28
+ htmlFor="orderId"
29
+ className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5"
30
+ >
31
+ Order number
32
+ </label>
33
+ <input
34
+ id="orderId"
35
+ name="orderId"
36
+ required
37
+ value={orderId}
38
+ onChange={(e) => setOrderId(e.target.value)}
39
+ placeholder="ord_abc123…"
40
+ className="w-full px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
41
+ />
42
+ </div>
43
+ <div>
44
+ <label
45
+ htmlFor="email"
46
+ className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5"
47
+ >
48
+ Order email
49
+ </label>
50
+ <input
51
+ id="email"
52
+ name="email"
53
+ type="email"
54
+ required
55
+ value={email}
56
+ onChange={(e) => setEmail(e.target.value)}
57
+ placeholder="you@email.com"
58
+ className="w-full px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
59
+ />
60
+ </div>
61
+ <button
62
+ type="submit"
63
+ className="w-full inline-flex items-center justify-center px-5 py-3 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
64
+ >
65
+ Track order
66
+ </button>
67
+ </form>
68
+ );
69
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import { CimplifyAccount } from "@cimplify/sdk/react";
4
+
5
+ /**
6
+ * Cimplify Account portal — iframe-mounted UI hosted by Cimplify Link.
7
+ * Handles sign-in, sign-up, OTP, addresses, payment methods, sessions,
8
+ * and order history. The iframe owns auth state; we just choose which
9
+ * `section` to land on.
10
+ */
11
+ export function AccountIframe({ section }: { section?: string }) {
12
+ return <CimplifyAccount section={section} />;
13
+ }
@@ -0,0 +1,49 @@
1
+ import type { BrandPolicySection } from "@/lib/brand";
2
+
3
+ interface PolicyShape {
4
+ eyebrow: string;
5
+ title: string;
6
+ lastUpdated?: string;
7
+ sections: BrandPolicySection[];
8
+ }
9
+
10
+ /**
11
+ * Shared layout for shipping / returns / accessibility / terms / privacy.
12
+ * Reads a `{ eyebrow, title, lastUpdated, sections[] }` block from brand.
13
+ */
14
+ export function PolicyPage({ policy }: { policy: PolicyShape }) {
15
+ return (
16
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
17
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
18
+ {policy.eyebrow}
19
+ </p>
20
+ <h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
21
+ {policy.title}
22
+ </h1>
23
+ {policy.lastUpdated && (
24
+ <p className="text-sm text-muted-foreground not-prose mb-10">
25
+ Last updated: {policy.lastUpdated}
26
+ </p>
27
+ )}
28
+ <section className="space-y-5 leading-relaxed text-foreground/90">
29
+ {policy.sections.map((s) => (
30
+ <div key={s.heading}>
31
+ <h2 className="text-2xl font-semibold mt-0">{s.heading}</h2>
32
+ {typeof s.body === "string" ? (
33
+ <p>{s.body}</p>
34
+ ) : (
35
+ <>
36
+ <p>{s.body.intro}</p>
37
+ <ul className="list-disc pl-6 space-y-2">
38
+ {s.body.bullets.map((b) => (
39
+ <li key={b}>{b}</li>
40
+ ))}
41
+ </ul>
42
+ </>
43
+ )}
44
+ </div>
45
+ ))}
46
+ </section>
47
+ </article>
48
+ );
49
+ }