@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,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
+ }
@@ -58,6 +58,20 @@ export interface Brand {
58
58
  };
59
59
  terms: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
60
60
  privacy: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
61
+ shipping: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
62
+ returns: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
63
+ accessibility: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
64
+ account: {
65
+ loginEyebrow: string; loginTitle: string; loginSubtitle: string;
66
+ signupEyebrow: string; signupTitle: string; signupSubtitle: string;
67
+ accountEyebrow: string; accountTitle: string;
68
+ };
69
+ contactPage: {
70
+ eyebrow: string; title: string; body: string;
71
+ reasons: string[];
72
+ directLines: { label: string; value: string; href: string }[];
73
+ };
74
+ trackOrder: { eyebrow: string; title: string; body: string };
61
75
  footer: {
62
76
  blurb: string; sitemap: BrandSitemapSection[];
63
77
  poweredBy?: { label: string; href: string };
@@ -229,6 +243,69 @@ export const brand: Brand = {
229
243
  ],
230
244
  },
231
245
 
246
+ shipping: {
247
+ eyebrow: "Delivery",
248
+ title: "Delivery & Pickup",
249
+ lastUpdated: "1 May 2026",
250
+ sections: [
251
+ { heading: "Same-day Greater Accra", body: "Order before 2pm — in your kitchen by 5pm. Free over GH₵150." },
252
+ { heading: "Slot windows", body: "Choose 12pm, 3pm, or 5pm in Accra. Subscriber boxes deliver Tuesday or Friday morning." },
253
+ { heading: "Outside Accra", body: "Next-day shipping via partner courier; rates and ETA at checkout." },
254
+ { heading: "Pickup", body: "Achimota Market, stalls 14–22. Free; ready 30 minutes after order." },
255
+ ],
256
+ },
257
+
258
+ returns: {
259
+ eyebrow: "Quality guarantee",
260
+ title: "Quality Guarantee & Refunds",
261
+ lastUpdated: "1 May 2026",
262
+ sections: [
263
+ { heading: "Anything off, refund", body: "Wrong, spoiled, or not as described — we refund or replace, no questions asked. Send a photo within 24 hours of delivery." },
264
+ { heading: "Pricing by weight", body: "Final price is computed at packing. We never charge more than 10% over the listed estimate; if the actual is more, we trim or call you." },
265
+ { heading: "Subscription pause", body: "Pause, skip, or cancel any week before the next billing date." },
266
+ ],
267
+ },
268
+
269
+ accessibility: {
270
+ eyebrow: "Accessibility",
271
+ title: "Accessibility Statement",
272
+ lastUpdated: "1 May 2026",
273
+ sections: [
274
+ { heading: "Online", body: "We aim for WCAG 2.1 AA on this site." },
275
+ { heading: "At Achimota Market", body: "Our stalls are at ground level. The market itself has step-free pathways; ask any staff member to direct you." },
276
+ { heading: "Reporting issues", body: "Email accessibility@freshmart.test. We respond within 5 business days." },
277
+ ],
278
+ },
279
+
280
+ account: {
281
+ loginEyebrow: "Welcome back",
282
+ loginTitle: "Sign in to FreshMart",
283
+ loginSubtitle: "Manage orders, your weekly box, and your delivery preferences.",
284
+ signupEyebrow: "Welcome",
285
+ signupTitle: "Create your account",
286
+ signupSubtitle: "Save addresses, set dietary preferences, and manage your weekly basket box.",
287
+ accountEyebrow: "Your account",
288
+ accountTitle: "Welcome back",
289
+ },
290
+
291
+ contactPage: {
292
+ eyebrow: "Contact",
293
+ title: "How can we help?",
294
+ body: "Order question, subscription change, or wholesale enquiry — we'll reply within a business day.",
295
+ reasons: ["General question", "An order issue", "Weekly box subscription", "Wholesale", "Press / partnerships", "Something else"],
296
+ directLines: [
297
+ { label: "Email", value: "hello@freshmart.test", href: "mailto:hello@freshmart.test" },
298
+ { label: "Phone", value: "+233 244 555 100", href: "tel:+233244555100" },
299
+ { label: "WhatsApp", value: "+233 244 555 100", href: "https://wa.me/233244555100" },
300
+ ],
301
+ },
302
+
303
+ trackOrder: {
304
+ eyebrow: "Track an order",
305
+ title: "Where's my order?",
306
+ body: "Enter your order number and the email used at checkout. We'll show the picking status, the rider, and the live ETA.",
307
+ },
308
+
232
309
  footer: {
233
310
  blurb:
234
311
  "Local produce, weekly basket boxes, and pantry staples. Sourced from Ghanaian farms, delivered same-day across Accra.",
@@ -255,10 +332,20 @@ export const brand: Brand = {
255
332
  {
256
333
  title: "Help",
257
334
  links: [
258
- { label: "Contact", href: "mailto:hello@freshmart.test" },
335
+ { label: "Contact", href: "/contact" },
336
+ { label: "Track an order", href: "/track-order" },
337
+ { label: "Delivery", href: "/shipping" },
338
+ { label: "Quality guarantee", href: "/returns" },
259
339
  { label: "FAQ", href: "/faq" },
260
- { label: "Quality guarantee", href: "/faq" },
261
- { label: "Wholesale", href: "mailto:wholesale@freshmart.test" },
340
+ ],
341
+ },
342
+ {
343
+ title: "Account",
344
+ links: [
345
+ { label: "Sign in", href: "/login" },
346
+ { label: "Create account", href: "/signup" },
347
+ { label: "Your orders", href: "/account/orders" },
348
+ { label: "Settings", href: "/account/settings" },
262
349
  ],
263
350
  },
264
351
  {
@@ -266,6 +353,8 @@ export const brand: Brand = {
266
353
  links: [
267
354
  { label: "Terms of Service", href: "/terms" },
268
355
  { label: "Privacy Policy", href: "/privacy" },
356
+ { label: "Accessibility", href: "/accessibility" },
357
+ { label: "Sitemap", href: "/sitemap-page" },
269
358
  ],
270
359
  },
271
360
  ],
@@ -10,24 +10,75 @@ If you are an AI agent (Claude, Cursor, Aider, devin, …) working on this store
10
10
 
11
11
  ## Aesthetic
12
12
 
13
- Boutique restaurant — Sweetgreen / chef-table feel:
13
+ - **Lora serif + Inter** — warm, food-friendly headings + clean body.
14
+ - **Forest green + cream** — deep sage primary, warm cream background.
15
+ - **Mid radius**: `0.625rem`.
16
+ - Schema.org `@type` is `Restaurant`.
14
17
 
15
- - **Lora serif** for headings (warm, food-leaning).
16
- - **Inter** for body and UI.
17
- - **Forest green + cream**: deep sage primary, warm cream background, subtle warm accents.
18
- - **Mid-radius**: `0.625rem` — softer than retail, tighter than spa.
18
+ ## Page surface
19
19
 
20
- ## Industry-specific copy patterns
20
+ ```
21
+ app/
22
+ page.tsx Home — hero with daily-special badge,
23
+ collection strips, category grid
24
+ shop/page.tsx Full menu (SDK <CataloguePage/>)
25
+ search/page.tsx Search
26
+ collections/[slug]/page.tsx Collection landing (e.g. brunch, mains)
27
+ categories/[slug]/page.tsx Category landing
28
+
29
+ reservations/page.tsx ⭐ Restaurant-specific: reservation widget
30
+ (date + lunch/dinner slot + party size)
31
+ backed by service-type "seating" products
32
+
33
+ cart/page.tsx, checkout/page.tsx, orders/[id]/page.tsx
34
+
35
+ account/page.tsx <CimplifyAccount /> (iframe)
36
+ account/orders/page.tsx <CimplifyAccount section="orders" />
37
+ account/addresses/page.tsx <CimplifyAccount section="addresses" />
38
+ account/settings/page.tsx <CimplifyAccount section="settings" />
39
+ login/page.tsx, signup/page.tsx redirects → /account
40
+
41
+ contact/page.tsx, track-order/page.tsx
42
+
43
+ about/page.tsx, faq/page.tsx
44
+ shipping/page.tsx (delivery), returns/page.tsx (refunds), accessibility/page.tsx
45
+ terms/page.tsx, privacy/page.tsx
46
+
47
+ sitemap-page/page.tsx, sitemap.ts, robots.ts, llms.txt/route.ts, opensearch.xml/route.ts
48
+ error.tsx, not-found.tsx
49
+ ```
50
+
51
+ ## File ↔ brand-field map
52
+
53
+ | File | Reads from `brand` |
54
+ |---|---|
55
+ | `app/layout.tsx` | identity, contact, socials, Restaurant JSON-LD |
56
+ | `app/page.tsx` | `brand.hero` (daily-special badge) |
57
+ | `app/about/page.tsx` | `brand.about` |
58
+ | `app/faq/page.tsx` | `brand.faq` (reservations, dietary, delivery) |
59
+ | `app/shipping/page.tsx` | `brand.shipping` (delivery + pickup) |
60
+ | `app/returns/page.tsx` | `brand.returns` (refund + cancellation) |
61
+ | `app/accessibility/page.tsx` | `brand.accessibility` |
62
+ | `app/terms/page.tsx`, `app/privacy/page.tsx` | `brand.terms`, `brand.privacy` |
63
+ | `app/contact/page.tsx` | `brand.contactPage`, `brand.contact` |
64
+ | `app/track-order/page.tsx` | `brand.trackOrder` |
65
+ | `app/account/*/page.tsx` | `brand.account` (iframe owns the UI) |
66
+ | `app/reservations/*` | derives seating from service-type products in the catalogue |
67
+ | `app/llms.txt/route.ts` | `brand.llms`, contact, currency |
68
+ | `components/header.tsx`, `footer.tsx` | `brand.header`, `brand.footer`, `brand.contact`, `brand.socials` |
69
+
70
+ ## Restaurant-specific notes
21
71
 
22
- - `brand.hero.badge` is `"Tonight's special · Banku & Tilapia"` — a daily-special call-out.
23
- - `brand.hero.primaryCtaLabel` is `"Reserve a table"`.
24
- - `brand.tradeIn` is omitted.
25
- - `brand.faq` covers reservations, dietary, delivery, allergens.
26
- - Schema.org type is `Restaurant`.
72
+ - **Reservations are service-type products.** Add seating options like "Two-top", "Four-top", "Long table for 12" to the catalogue with `type: "service"`. The `/reservations` page filters them in.
73
+ - The reservation widget adds the chosen seating to cart with the date, time, party size, and notes as cart-item notes; checkout finalises the booking via Cimplify's order pipeline.
74
+ - Schema.org `@type` is `Restaurant`. `servesCuisine` etc. can be added to the JSON-LD object in `app/layout.tsx` if needed.
75
+ - Mock seed: `--seed restaurant` (Mama's Kitchen).
27
76
 
28
- ## Mock seed
77
+ ## Known TODOs
29
78
 
30
- Wired to `--seed restaurant` (Mama's Kitchen).
79
+ - `/reservations` widget assumes every slot is bookable. For production, wire `useAvailableSlots({ serviceId, date })` from `@cimplify/sdk/react` to filter to actually-free windows.
80
+ - Contact form + newsletter fake their submits.
81
+ - Hero / dish photography is Unsplash — replace with merchant photos.
31
82
 
32
83
  ## Quick start
33
84
 
@@ -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.accessibility.title} — ${brand.name}`,
7
+ };
8
+
9
+ export default function AccessibilityPage() {
10
+ return <PolicyPage policy={brand.accessibility} />;
11
+ }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from "next";
2
+ import { AccountIframe } from "@/components/account-iframe";
3
+ import { brand } from "@/lib/brand";
4
+
5
+ export const metadata: Metadata = {
6
+ title: `Addresses — ${brand.name}`,
7
+ };
8
+
9
+ export default function AddressesPage() {
10
+ return (
11
+ <article className="max-w-5xl mx-auto px-6 sm:px-8 py-12">
12
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
13
+ Account
14
+ </p>
15
+ <h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
16
+ Your addresses
17
+ </h1>
18
+ <AccountIframe section="addresses" />
19
+ </article>
20
+ );
21
+ }