@cimplify/sdk 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/dispatcher.mjs +3 -2
  2. package/dist/mock/cli.mjs +30 -6
  3. package/dist/mock/library.js +30 -6
  4. package/dist/mock/library.mjs +30 -6
  5. package/dist/mock/msw.js +30 -6
  6. package/dist/mock/msw.mjs +30 -6
  7. package/dist/styles.css +1 -1
  8. package/package.json +1 -1
  9. package/templates/storefront-bakery/.env.example +5 -0
  10. package/templates/storefront-bakery/AGENTS.md +57 -0
  11. package/templates/storefront-bakery/app/about/page.tsx +21 -44
  12. package/templates/storefront-bakery/app/categories/[slug]/page.tsx +2 -1
  13. package/templates/storefront-bakery/app/collections/[slug]/page.tsx +2 -1
  14. package/templates/storefront-bakery/app/error.tsx +60 -0
  15. package/templates/storefront-bakery/app/faq/page.tsx +13 -89
  16. package/templates/storefront-bakery/app/layout.tsx +37 -2
  17. package/templates/storefront-bakery/app/llms.txt/route.ts +94 -0
  18. package/templates/storefront-bakery/app/not-found.tsx +39 -0
  19. package/templates/storefront-bakery/app/page.tsx +6 -6
  20. package/templates/storefront-bakery/app/privacy/page.tsx +24 -68
  21. package/templates/storefront-bakery/app/robots.ts +18 -0
  22. package/templates/storefront-bakery/app/shop/page.tsx +3 -2
  23. package/templates/storefront-bakery/app/sitemap.ts +62 -0
  24. package/templates/storefront-bakery/app/terms/page.tsx +24 -82
  25. package/templates/storefront-bakery/components/footer.tsx +88 -130
  26. package/templates/storefront-bakery/components/header.tsx +8 -16
  27. package/templates/storefront-bakery/lib/brand.ts +453 -0
  28. package/templates/storefront-fashion/.env.example +16 -0
  29. package/templates/storefront-fashion/AGENTS.md +52 -0
  30. package/templates/storefront-fashion/README.md +77 -0
  31. package/templates/storefront-fashion/app/about/page.tsx +41 -0
  32. package/templates/storefront-fashion/app/cart/page.tsx +9 -0
  33. package/templates/storefront-fashion/app/categories/[slug]/listing-client.tsx +19 -0
  34. package/templates/storefront-fashion/app/categories/[slug]/page.tsx +130 -0
  35. package/templates/storefront-fashion/app/checkout/page.tsx +17 -0
  36. package/templates/storefront-fashion/app/collections/[slug]/listing-client.tsx +20 -0
  37. package/templates/storefront-fashion/app/collections/[slug]/page.tsx +130 -0
  38. package/templates/storefront-fashion/app/error.tsx +61 -0
  39. package/templates/storefront-fashion/app/faq/page.tsx +46 -0
  40. package/templates/storefront-fashion/app/globals.css +46 -0
  41. package/templates/storefront-fashion/app/layout.tsx +76 -0
  42. package/templates/storefront-fashion/app/llms.txt/route.ts +94 -0
  43. package/templates/storefront-fashion/app/not-found.tsx +39 -0
  44. package/templates/storefront-fashion/app/orders/[id]/page.tsx +24 -0
  45. package/templates/storefront-fashion/app/page.tsx +183 -0
  46. package/templates/storefront-fashion/app/privacy/page.tsx +44 -0
  47. package/templates/storefront-fashion/app/products/[slug]/page.tsx +165 -0
  48. package/templates/storefront-fashion/app/products/[slug]/product-detail.tsx +71 -0
  49. package/templates/storefront-fashion/app/robots.ts +18 -0
  50. package/templates/storefront-fashion/app/shop/page.tsx +63 -0
  51. package/templates/storefront-fashion/app/shop/shop-client.tsx +32 -0
  52. package/templates/storefront-fashion/app/sitemap.ts +59 -0
  53. package/templates/storefront-fashion/app/terms/page.tsx +44 -0
  54. package/templates/storefront-fashion/components/brand-marquee.tsx +27 -0
  55. package/templates/storefront-fashion/components/cart-pill.tsx +32 -0
  56. package/templates/storefront-fashion/components/category-grid.tsx +28 -0
  57. package/templates/storefront-fashion/components/category-tiles.tsx +104 -0
  58. package/templates/storefront-fashion/components/collection-strip.tsx +45 -0
  59. package/templates/storefront-fashion/components/feature-hero.tsx +82 -0
  60. package/templates/storefront-fashion/components/footer.tsx +153 -0
  61. package/templates/storefront-fashion/components/header.tsx +43 -0
  62. package/templates/storefront-fashion/components/hero.tsx +28 -0
  63. package/templates/storefront-fashion/components/nav-link.tsx +20 -0
  64. package/templates/storefront-fashion/components/newsletter.tsx +50 -0
  65. package/templates/storefront-fashion/components/promo-banner.tsx +41 -0
  66. package/templates/storefront-fashion/components/providers.tsx +31 -0
  67. package/templates/storefront-fashion/components/section-heading.tsx +37 -0
  68. package/templates/storefront-fashion/components/store-product-card.tsx +83 -0
  69. package/templates/storefront-fashion/components/trade-in-cta.tsx +54 -0
  70. package/templates/storefront-fashion/components/trust-bar.tsx +66 -0
  71. package/templates/storefront-fashion/lib/brand.ts +421 -0
  72. package/templates/storefront-fashion/lib/cart.ts +27 -0
  73. package/templates/storefront-fashion/next.config.ts +40 -0
  74. package/templates/storefront-fashion/package.json +28 -0
  75. package/templates/storefront-fashion/postcss.config.mjs +7 -0
  76. package/templates/storefront-fashion/tsconfig.json +23 -0
  77. package/templates/storefront-grocery/.env.example +16 -0
  78. package/templates/storefront-grocery/AGENTS.md +39 -0
  79. package/templates/storefront-grocery/README.md +73 -0
  80. package/templates/storefront-grocery/app/about/page.tsx +38 -0
  81. package/templates/storefront-grocery/app/cart/page.tsx +9 -0
  82. package/templates/storefront-grocery/app/categories/[slug]/listing-client.tsx +19 -0
  83. package/templates/storefront-grocery/app/categories/[slug]/page.tsx +118 -0
  84. package/templates/storefront-grocery/app/checkout/page.tsx +17 -0
  85. package/templates/storefront-grocery/app/collections/[slug]/listing-client.tsx +20 -0
  86. package/templates/storefront-grocery/app/collections/[slug]/page.tsx +118 -0
  87. package/templates/storefront-grocery/app/error.tsx +60 -0
  88. package/templates/storefront-grocery/app/faq/page.tsx +46 -0
  89. package/templates/storefront-grocery/app/globals.css +45 -0
  90. package/templates/storefront-grocery/app/layout.tsx +75 -0
  91. package/templates/storefront-grocery/app/llms.txt/route.ts +94 -0
  92. package/templates/storefront-grocery/app/not-found.tsx +39 -0
  93. package/templates/storefront-grocery/app/orders/[id]/page.tsx +21 -0
  94. package/templates/storefront-grocery/app/page.tsx +97 -0
  95. package/templates/storefront-grocery/app/privacy/page.tsx +44 -0
  96. package/templates/storefront-grocery/app/robots.ts +18 -0
  97. package/templates/storefront-grocery/app/shop/page.tsx +31 -0
  98. package/templates/storefront-grocery/app/shop/shop-client.tsx +27 -0
  99. package/templates/storefront-grocery/app/sitemap.ts +62 -0
  100. package/templates/storefront-grocery/app/terms/page.tsx +44 -0
  101. package/templates/storefront-grocery/components/cart-pill.tsx +32 -0
  102. package/templates/storefront-grocery/components/category-grid.tsx +28 -0
  103. package/templates/storefront-grocery/components/collection-strip.tsx +45 -0
  104. package/templates/storefront-grocery/components/footer.tsx +148 -0
  105. package/templates/storefront-grocery/components/header.tsx +43 -0
  106. package/templates/storefront-grocery/components/hero.tsx +25 -0
  107. package/templates/storefront-grocery/components/nav-link.tsx +20 -0
  108. package/templates/storefront-grocery/components/providers.tsx +31 -0
  109. package/templates/storefront-grocery/components/store-product-card.tsx +83 -0
  110. package/templates/storefront-grocery/lib/brand.ts +284 -0
  111. package/templates/storefront-grocery/lib/cart.ts +27 -0
  112. package/templates/storefront-grocery/next.config.ts +40 -0
  113. package/templates/storefront-grocery/package.json +28 -0
  114. package/templates/storefront-grocery/postcss.config.mjs +7 -0
  115. package/templates/storefront-grocery/tsconfig.json +23 -0
  116. package/templates/storefront-restaurant/.env.example +16 -0
  117. package/templates/storefront-restaurant/AGENTS.md +39 -0
  118. package/templates/storefront-restaurant/README.md +73 -0
  119. package/templates/storefront-restaurant/app/about/page.tsx +38 -0
  120. package/templates/storefront-restaurant/app/cart/page.tsx +9 -0
  121. package/templates/storefront-restaurant/app/categories/[slug]/listing-client.tsx +19 -0
  122. package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +118 -0
  123. package/templates/storefront-restaurant/app/checkout/page.tsx +17 -0
  124. package/templates/storefront-restaurant/app/collections/[slug]/listing-client.tsx +20 -0
  125. package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +118 -0
  126. package/templates/storefront-restaurant/app/error.tsx +60 -0
  127. package/templates/storefront-restaurant/app/faq/page.tsx +46 -0
  128. package/templates/storefront-restaurant/app/globals.css +44 -0
  129. package/templates/storefront-restaurant/app/layout.tsx +80 -0
  130. package/templates/storefront-restaurant/app/llms.txt/route.ts +94 -0
  131. package/templates/storefront-restaurant/app/not-found.tsx +39 -0
  132. package/templates/storefront-restaurant/app/orders/[id]/page.tsx +21 -0
  133. package/templates/storefront-restaurant/app/page.tsx +97 -0
  134. package/templates/storefront-restaurant/app/privacy/page.tsx +44 -0
  135. package/templates/storefront-restaurant/app/robots.ts +18 -0
  136. package/templates/storefront-restaurant/app/shop/page.tsx +31 -0
  137. package/templates/storefront-restaurant/app/shop/shop-client.tsx +27 -0
  138. package/templates/storefront-restaurant/app/sitemap.ts +62 -0
  139. package/templates/storefront-restaurant/app/terms/page.tsx +44 -0
  140. package/templates/storefront-restaurant/components/cart-pill.tsx +32 -0
  141. package/templates/storefront-restaurant/components/category-grid.tsx +28 -0
  142. package/templates/storefront-restaurant/components/collection-strip.tsx +45 -0
  143. package/templates/storefront-restaurant/components/footer.tsx +148 -0
  144. package/templates/storefront-restaurant/components/header.tsx +43 -0
  145. package/templates/storefront-restaurant/components/hero.tsx +25 -0
  146. package/templates/storefront-restaurant/components/nav-link.tsx +20 -0
  147. package/templates/storefront-restaurant/components/product-modal.tsx +105 -0
  148. package/templates/storefront-restaurant/components/providers.tsx +31 -0
  149. package/templates/storefront-restaurant/components/store-product-card.tsx +83 -0
  150. package/templates/storefront-restaurant/lib/brand.ts +287 -0
  151. package/templates/storefront-restaurant/lib/cart.ts +27 -0
  152. package/templates/storefront-restaurant/next.config.ts +40 -0
  153. package/templates/storefront-restaurant/package.json +28 -0
  154. package/templates/storefront-restaurant/postcss.config.mjs +7 -0
  155. package/templates/storefront-restaurant/tsconfig.json +23 -0
  156. package/templates/storefront-retail/.env.example +5 -0
  157. package/templates/storefront-retail/AGENTS.md +64 -0
  158. package/templates/storefront-retail/app/about/page.tsx +24 -54
  159. package/templates/storefront-retail/app/categories/[slug]/page.tsx +50 -37
  160. package/templates/storefront-retail/app/collections/[slug]/page.tsx +50 -37
  161. package/templates/storefront-retail/app/error.tsx +61 -0
  162. package/templates/storefront-retail/app/faq/page.tsx +11 -92
  163. package/templates/storefront-retail/app/layout.tsx +37 -8
  164. package/templates/storefront-retail/app/llms.txt/route.ts +94 -0
  165. package/templates/storefront-retail/app/not-found.tsx +39 -0
  166. package/templates/storefront-retail/app/page.tsx +111 -26
  167. package/templates/storefront-retail/app/privacy/page.tsx +24 -81
  168. package/templates/storefront-retail/app/products/[slug]/page.tsx +165 -0
  169. package/templates/storefront-retail/app/products/[slug]/product-detail.tsx +71 -0
  170. package/templates/storefront-retail/app/robots.ts +18 -0
  171. package/templates/storefront-retail/app/shop/page.tsx +36 -3
  172. package/templates/storefront-retail/app/shop/shop-client.tsx +6 -1
  173. package/templates/storefront-retail/app/sitemap.ts +59 -0
  174. package/templates/storefront-retail/app/terms/page.tsx +24 -97
  175. package/templates/storefront-retail/components/brand-marquee.tsx +27 -0
  176. package/templates/storefront-retail/components/category-tiles.tsx +104 -0
  177. package/templates/storefront-retail/components/feature-hero.tsx +84 -0
  178. package/templates/storefront-retail/components/footer.tsx +85 -120
  179. package/templates/storefront-retail/components/header.tsx +10 -12
  180. package/templates/storefront-retail/components/newsletter.tsx +50 -0
  181. package/templates/storefront-retail/components/promo-banner.tsx +41 -0
  182. package/templates/storefront-retail/components/section-heading.tsx +37 -0
  183. package/templates/storefront-retail/components/store-product-card.tsx +7 -7
  184. package/templates/storefront-retail/components/trade-in-cta.tsx +54 -0
  185. package/templates/storefront-retail/components/trust-bar.tsx +66 -0
  186. package/templates/storefront-retail/lib/brand.ts +567 -0
  187. package/templates/storefront-services/.env.example +16 -0
  188. package/templates/storefront-services/AGENTS.md +41 -0
  189. package/templates/storefront-services/README.md +73 -0
  190. package/templates/storefront-services/app/about/page.tsx +38 -0
  191. package/templates/storefront-services/app/cart/page.tsx +9 -0
  192. package/templates/storefront-services/app/categories/[slug]/listing-client.tsx +19 -0
  193. package/templates/storefront-services/app/categories/[slug]/page.tsx +118 -0
  194. package/templates/storefront-services/app/checkout/page.tsx +17 -0
  195. package/templates/storefront-services/app/collections/[slug]/listing-client.tsx +20 -0
  196. package/templates/storefront-services/app/collections/[slug]/page.tsx +118 -0
  197. package/templates/storefront-services/app/error.tsx +60 -0
  198. package/templates/storefront-services/app/faq/page.tsx +46 -0
  199. package/templates/storefront-services/app/globals.css +45 -0
  200. package/templates/storefront-services/app/layout.tsx +80 -0
  201. package/templates/storefront-services/app/llms.txt/route.ts +94 -0
  202. package/templates/storefront-services/app/not-found.tsx +39 -0
  203. package/templates/storefront-services/app/orders/[id]/page.tsx +21 -0
  204. package/templates/storefront-services/app/page.tsx +97 -0
  205. package/templates/storefront-services/app/privacy/page.tsx +44 -0
  206. package/templates/storefront-services/app/robots.ts +18 -0
  207. package/templates/storefront-services/app/shop/page.tsx +31 -0
  208. package/templates/storefront-services/app/shop/shop-client.tsx +27 -0
  209. package/templates/storefront-services/app/sitemap.ts +62 -0
  210. package/templates/storefront-services/app/terms/page.tsx +44 -0
  211. package/templates/storefront-services/components/cart-pill.tsx +32 -0
  212. package/templates/storefront-services/components/category-grid.tsx +28 -0
  213. package/templates/storefront-services/components/collection-strip.tsx +45 -0
  214. package/templates/storefront-services/components/footer.tsx +148 -0
  215. package/templates/storefront-services/components/header.tsx +43 -0
  216. package/templates/storefront-services/components/hero.tsx +25 -0
  217. package/templates/storefront-services/components/nav-link.tsx +20 -0
  218. package/templates/storefront-services/components/product-modal.tsx +105 -0
  219. package/templates/storefront-services/components/providers.tsx +31 -0
  220. package/templates/storefront-services/components/store-product-card.tsx +83 -0
  221. package/templates/storefront-services/lib/brand.ts +303 -0
  222. package/templates/storefront-services/lib/cart.ts +27 -0
  223. package/templates/storefront-services/next.config.ts +40 -0
  224. package/templates/storefront-services/package.json +28 -0
  225. package/templates/storefront-services/postcss.config.mjs +7 -0
  226. package/templates/storefront-services/tsconfig.json +23 -0
  227. /package/templates/{storefront-retail → storefront-grocery}/components/product-modal.tsx +0 -0
@@ -2,30 +2,44 @@ import type { Metadata } from "next";
2
2
  import { Suspense } from "react";
3
3
  import { cacheTag, cacheLife } from "next/cache";
4
4
  import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
5
- import { Hero } from "@/components/hero";
5
+ import { FeatureHero } from "@/components/feature-hero";
6
+ import { CategoryTiles } from "@/components/category-tiles";
6
7
  import { CollectionStrip } from "@/components/collection-strip";
7
- import { CategoryGrid } from "@/components/category-grid";
8
+ import { PromoBanner } from "@/components/promo-banner";
9
+ import { TrustBar } from "@/components/trust-bar";
10
+ import { BrandMarquee } from "@/components/brand-marquee";
11
+ import { TradeInCta } from "@/components/trade-in-cta";
12
+ import { Newsletter } from "@/components/newsletter";
13
+ import { SectionHeading } from "@/components/section-heading";
14
+ import { StoreProductCard } from "@/components/store-product-card";
15
+ import { brand } from "@/lib/brand";
8
16
 
9
17
  export const metadata: Metadata = {
10
- title: "Currents Electronics — Tech, in stock, in Accra",
11
- description:
12
- "The latest laptops, phones, audio, and accessories — curated, in-stock, shipped same-day from Accra.",
18
+ title: brand.hero.title,
19
+ description: brand.description,
13
20
  };
14
21
 
22
+ interface CollectionWithProducts {
23
+ collection: { id: string; slug: string; name: string; description?: string };
24
+ products: Product[];
25
+ }
26
+
15
27
  async function getHomeData() {
16
28
  "use cache";
17
29
  cacheTag(tags.collections(), tags.categories(), tags.products());
18
30
  cacheLife("hours");
19
31
 
20
32
  const client = getServerClient();
21
- const [colRes, catRes] = await Promise.all([
33
+ const [colRes, catRes, productsRes] = await Promise.all([
22
34
  client.catalogue.getCollections(),
23
35
  client.catalogue.getCategories(),
36
+ client.catalogue.getProducts({ limit: 12 }),
24
37
  ]);
25
38
  const collections = colRes.ok ? colRes.value : [];
26
39
  const categories = catRes.ok ? catRes.value : [];
40
+ const allProducts = productsRes.ok ? productsRes.value.items : [];
27
41
 
28
- const collectionsWithProducts = await Promise.all(
42
+ const collectionsWithProducts: CollectionWithProducts[] = await Promise.all(
29
43
  collections.map(async (col) => {
30
44
  const r = await client.catalogue.getCollectionProducts(col.id);
31
45
  const items = r.ok
@@ -38,19 +52,64 @@ async function getHomeData() {
38
52
  return {
39
53
  collections: collectionsWithProducts.filter((x) => x.products.length > 0),
40
54
  categories,
55
+ featured: allProducts.slice(0, 4),
56
+ newArrivals: allProducts.slice(4, 12),
41
57
  };
42
58
  }
43
59
 
60
+ const HERO_FALLBACK_IMAGE =
61
+ "https://images.unsplash.com/photo-1592899677977-9c10ca588bbd?w=1200&h=1200&fit=crop&auto=format&q=85";
62
+
44
63
  export default async function HomePage() {
45
- const { collections, categories } = await getHomeData();
64
+ const { collections, categories, featured, newArrivals } = await getHomeData();
65
+ const heroProduct = featured[0];
46
66
 
47
67
  return (
48
68
  <>
49
- <Hero
50
- badge="LAPTOPS · PHONES · AUDIO · ACCESSORIES"
51
- title="The tech you want, in stock today."
52
- subtitle="Authorised dealer for Apple, Samsung, Sony, Bose. Same-day delivery in Accra. Two-year warranty on every product."
69
+ <FeatureHero
70
+ eyebrow="Featured · in stock"
71
+ badge="Just landed"
72
+ title={brand.hero.title}
73
+ description={heroProduct?.description ?? brand.hero.subtitle}
74
+ primaryCta={{
75
+ label: heroProduct ? `Shop ${heroProduct.name}` : brand.hero.primaryCtaLabel,
76
+ href: heroProduct ? `/products/${heroProduct.slug ?? heroProduct.id}` : "/shop",
77
+ }}
78
+ secondaryCta={
79
+ brand.hero.secondaryCtaLabel && brand.hero.secondaryCtaHref
80
+ ? { label: brand.hero.secondaryCtaLabel, href: brand.hero.secondaryCtaHref }
81
+ : undefined
82
+ }
83
+ imageUrl={heroProduct?.image_url ?? heroProduct?.images?.[0] ?? HERO_FALLBACK_IMAGE}
84
+ imageAlt={heroProduct?.name ?? "Featured product"}
53
85
  />
86
+
87
+ <TrustBar />
88
+
89
+ <Suspense fallback={<CategoryTilesSkeleton />}>
90
+ <CategoryTiles categories={categories} />
91
+ </Suspense>
92
+
93
+ <PromoBanner />
94
+
95
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
96
+ <SectionHeading
97
+ eyebrow="Just dropped"
98
+ title="New this week."
99
+ description="Hand-picked launches and freshly restocked staples."
100
+ link={{ label: "Browse all", href: "/shop" }}
101
+ />
102
+ <Suspense fallback={<GridSkeleton count={4} />}>
103
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
104
+ {newArrivals.slice(0, 4).map((p) => (
105
+ <StoreProductCard key={p.id} product={p} />
106
+ ))}
107
+ </div>
108
+ </Suspense>
109
+ </section>
110
+
111
+ <BrandMarquee />
112
+
54
113
  {collections.map(({ collection, products }) => (
55
114
  <Suspense
56
115
  key={collection.id}
@@ -63,33 +122,59 @@ export default async function HomePage() {
63
122
  />
64
123
  </Suspense>
65
124
  ))}
66
- <Suspense fallback={<CategoryGridSkeleton />}>
67
- <CategoryGrid categories={categories} />
68
- </Suspense>
125
+
126
+ <TradeInCta />
127
+
128
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
129
+ <SectionHeading
130
+ eyebrow="Best sellers"
131
+ title="What everyone&apos;s buying."
132
+ link={{ label: "See more", href: "/shop" }}
133
+ />
134
+ <Suspense fallback={<GridSkeleton count={4} />}>
135
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
136
+ {newArrivals.slice(4, 8).map((p) => (
137
+ <StoreProductCard key={p.id} product={p} />
138
+ ))}
139
+ </div>
140
+ </Suspense>
141
+ </section>
142
+
143
+ <Newsletter />
69
144
  </>
70
145
  );
71
146
  }
72
147
 
73
- function StripSkeleton({ title }: { title: string }) {
148
+ function CategoryTilesSkeleton() {
74
149
  return (
75
- <section className="max-w-7xl mx-auto px-8 pt-12">
76
- <h2 className="text-[26px] font-semibold m-0 mb-5 -tracking-[0.02em]">{title}</h2>
77
- <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto pb-2">
150
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
151
+ <div className="h-8 w-64 bg-muted rounded mb-8 animate-pulse" />
152
+ <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
78
153
  {Array.from({ length: 5 }).map((_, i) => (
79
- <div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
154
+ <div key={i} className="h-40 bg-muted rounded-2xl animate-pulse" />
80
155
  ))}
81
156
  </div>
82
157
  </section>
83
158
  );
84
159
  }
85
160
 
86
- function CategoryGridSkeleton() {
161
+ function GridSkeleton({ count }: { count: number }) {
87
162
  return (
88
- <section className="max-w-7xl mx-auto px-8 pt-14">
89
- <div className="h-8 w-48 bg-muted rounded mb-5 animate-pulse" />
90
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
91
- {Array.from({ length: 6 }).map((_, i) => (
92
- <div key={i} className="h-32 bg-muted rounded-2xl animate-pulse" />
163
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
164
+ {Array.from({ length: count }).map((_, i) => (
165
+ <div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
166
+ ))}
167
+ </div>
168
+ );
169
+ }
170
+
171
+ function StripSkeleton({ title }: { title: string }) {
172
+ return (
173
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-12">
174
+ <h2 className="text-[26px] font-semibold m-0 mb-5 -tracking-[0.02em]">{title}</h2>
175
+ <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto pb-2">
176
+ {Array.from({ length: 5 }).map((_, i) => (
177
+ <div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
93
178
  ))}
94
179
  </div>
95
180
  </section>
@@ -1,100 +1,43 @@
1
1
  import type { Metadata } from "next";
2
+ import { brand } from "@/lib/brand";
2
3
 
3
4
  export const metadata: Metadata = {
4
- title: "Privacy Policy — Currents Electronics",
5
- description:
6
- "How Currents Electronics collects, uses, and protects your personal data.",
5
+ title: `Privacy Policy — ${brand.name}`,
6
+ description: `How ${brand.name} collects, uses, and protects your personal data.`,
7
7
  };
8
8
 
9
9
  export default function PrivacyPage() {
10
+ const p = brand.privacy;
10
11
  return (
11
12
  <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
12
13
  <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
13
- Privacy
14
+ {p.eyebrow}
14
15
  </p>
15
16
  <h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
16
- Privacy Policy
17
+ {p.title}
17
18
  </h1>
18
19
  <p className="text-sm text-muted-foreground not-prose mb-10">
19
- Last updated: 1 May 2026
20
+ Last updated: {p.lastUpdated}
20
21
  </p>
21
22
 
22
23
  <section className="space-y-5 leading-relaxed text-foreground/90">
23
- <h2 className="text-2xl font-semibold mt-0 -tracking-[0.02em]">
24
- What we collect
25
- </h2>
26
- <p>
27
- We collect what we need to fulfil your order: name, phone, delivery
28
- address, email, and the items in your order. For warranty service
29
- and trade-ins, we also collect device serial numbers. Payment details
30
- are processed by our payment partners (Paystack, Stripe, Fidelity
31
- Bank) and never stored on our servers.
32
- </p>
33
-
34
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">
35
- How we use it
36
- </h2>
37
- <ul className="list-disc pl-6 space-y-2">
38
- <li>To prepare, deliver, and confirm your order.</li>
39
- <li>To register product warranties on your behalf.</li>
40
- <li>To send order updates by SMS and email.</li>
41
- <li>
42
- To send occasional product news and deals — only if you opted in.
43
- Unsubscribe anytime in one click.
44
- </li>
45
- <li>
46
- To analyse aggregated, anonymised browsing patterns so we can
47
- improve the site and recommend products.
48
- </li>
49
- </ul>
50
-
51
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">
52
- Who we share it with
53
- </h2>
54
- <p>
55
- We share data only with the partners required to fulfil your order
56
- and warranty: payment processors (Paystack, Stripe, Fidelity Bank),
57
- delivery couriers (DHL, local riders), manufacturer warranty portals
58
- (for serial registration), and our email/SMS providers. We do not
59
- sell or rent personal data to third parties — ever.
60
- </p>
61
-
62
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">Cookies</h2>
63
- <p>
64
- We use a small number of strictly-necessary cookies to keep your cart
65
- working and your session signed in, plus optional analytics cookies
66
- (you can disable these in our cookie banner).
67
- </p>
68
-
69
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">
70
- Your rights
71
- </h2>
72
- <p>
73
- Under the Ghana Data Protection Act, 2012 (Act 843), you have the
74
- right to access the data we hold about you, correct it, or have it
75
- deleted (subject to our tax-record retention obligations). Email{" "}
76
- <a
77
- href="mailto:privacy@currentselectronics.test"
78
- className="text-primary font-semibold hover:underline"
79
- >
80
- privacy@currentselectronics.test
81
- </a>{" "}
82
- and we&apos;ll respond within 14 days.
83
- </p>
84
-
85
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">Retention</h2>
86
- <p>
87
- We keep order records for 7 years to comply with Ghanaian tax law.
88
- Warranty records are retained for the life of the warranty plus 24
89
- months. Marketing-list opt-ins are kept until you unsubscribe.
90
- </p>
91
-
92
- <h2 className="text-2xl font-semibold -tracking-[0.02em]">Changes</h2>
93
- <p>
94
- If we make material changes to this policy, we&apos;ll email
95
- customers on our marketing list and update the &quot;last updated&quot;
96
- date above.
97
- </p>
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
+ ))}
98
41
  </section>
99
42
  </article>
100
43
  );
@@ -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: product.currency ?? 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,71 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import Image from "next/image";
5
+ import { ProductPage as SdkProductPage } from "@cimplify/sdk/react";
6
+ import type { Product, ProductWithDetails } from "@cimplify/sdk";
7
+ import { StoreProductCard } from "@/components/store-product-card";
8
+
9
+ /**
10
+ * Client island for the product detail page.
11
+ *
12
+ * - Receives a server-fetched `ProductWithDetails` (no client refetch).
13
+ * - Renders the SDK `<ProductPage>` which picks the right layout
14
+ * (Default / Wholesale / Service / Bundle / Composite) automatically.
15
+ * - On add-to-cart success, routes to `/cart`.
16
+ * - Custom Next.js Image renderer for optimised, lazy-loaded gallery shots.
17
+ * - Renders a "You may also like" rail of in-category products below.
18
+ */
19
+ export function ProductDetail({
20
+ product,
21
+ related,
22
+ }: {
23
+ product: ProductWithDetails;
24
+ related: Product[];
25
+ }) {
26
+ const router = useRouter();
27
+
28
+ return (
29
+ <>
30
+ <SdkProductPage
31
+ product={product}
32
+ showRelated={false}
33
+ onAddToCart={() => {
34
+ router.push("/cart");
35
+ }}
36
+ renderImage={({ src, alt, className }) => (
37
+ <Image
38
+ src={src}
39
+ alt={alt}
40
+ width={1200}
41
+ height={1200}
42
+ className={className}
43
+ style={{ width: "100%", height: "auto", objectFit: "cover" }}
44
+ priority
45
+ />
46
+ )}
47
+ className="max-w-7xl mx-auto px-6 sm:px-8 py-8 sm:py-10"
48
+ />
49
+
50
+ {related.length > 0 && (
51
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-16 border-t border-border mt-8">
52
+ <div className="flex items-end justify-between gap-6 mb-8">
53
+ <div>
54
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
55
+ You may also like
56
+ </p>
57
+ <h2 className="text-[clamp(1.5rem,2.5vw,2rem)] font-bold m-0 -tracking-[0.025em]">
58
+ More from this category.
59
+ </h2>
60
+ </div>
61
+ </div>
62
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
63
+ {related.map((p) => (
64
+ <StoreProductCard key={p.id} product={p} />
65
+ ))}
66
+ </div>
67
+ </section>
68
+ )}
69
+ </>
70
+ );
71
+ }
@@ -0,0 +1,18 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ const SITE_URL =
4
+ process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
5
+
6
+ export default function robots(): MetadataRoute.Robots {
7
+ return {
8
+ rules: [
9
+ {
10
+ userAgent: "*",
11
+ allow: "/",
12
+ disallow: ["/cart", "/checkout", "/orders/", "/api/"],
13
+ },
14
+ ],
15
+ sitemap: `${SITE_URL}/sitemap.xml`,
16
+ host: SITE_URL,
17
+ };
18
+ }
@@ -1,11 +1,13 @@
1
1
  import type { Metadata } from "next";
2
+ import { Suspense } from "react";
2
3
  import { cacheTag, cacheLife } from "next/cache";
3
4
  import { getServerClient, tags } from "@cimplify/sdk/server";
4
5
  import { ShopClient } from "./shop-client";
6
+ import { brand } from "@/lib/brand";
5
7
 
6
8
  export const metadata: Metadata = {
7
- title: "Shop — Akua's Bakery",
8
- description: "Browse the full catalogue: breads, pastries, cakes, drinks, merch, and more.",
9
+ title: `Shop — ${brand.name}`,
10
+ description: brand.description,
9
11
  };
10
12
 
11
13
  async function getShopData() {
@@ -26,5 +28,36 @@ async function getShopData() {
26
28
 
27
29
  export default async function ShopPage() {
28
30
  const { products, categories } = await getShopData();
29
- return <ShopClient products={products} categories={categories} />;
31
+ return (
32
+ <>
33
+ <section className="bg-foreground text-background relative overflow-hidden">
34
+ <div className="absolute inset-0 opacity-[0.04] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:32px_32px]" />
35
+ <div className="relative max-w-7xl mx-auto px-6 sm:px-8 py-12 sm:py-14">
36
+ <p className="text-[11px] font-mono uppercase tracking-[0.2em] text-background/60 mb-2">
37
+ Catalogue · {products.length} products
38
+ </p>
39
+ <h1 className="text-[clamp(2rem,4vw,3rem)] font-bold m-0 -tracking-[0.025em] leading-[1.05]">
40
+ Every product we stock.
41
+ </h1>
42
+ <p className="mt-3 max-w-xl text-base text-background/75">
43
+ Filter, sort, search. Authorised dealer pricing, two-year warranty,
44
+ same-day Accra delivery on every order.
45
+ </p>
46
+ </div>
47
+ </section>
48
+ <Suspense
49
+ fallback={
50
+ <div className="max-w-7xl mx-auto px-6 sm:px-8 py-10">
51
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
52
+ {Array.from({ length: 8 }).map((_, i) => (
53
+ <div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
54
+ ))}
55
+ </div>
56
+ </div>
57
+ }
58
+ >
59
+ <ShopClient products={products} categories={categories} />
60
+ </Suspense>
61
+ </>
62
+ );
30
63
  }
@@ -8,6 +8,10 @@ import { StoreProductCard } from "@/components/store-product-card";
8
8
  * Client island for the shop page. Server-side fetches all products and
9
9
  * categories (cached via `'use cache'` in `app/shop/page.tsx`), then hands
10
10
  * them to `<CataloguePage>` which owns the interactive filter / sort state.
11
+ *
12
+ * The page hero (in `app/shop/page.tsx`) already provides the title; we
13
+ * pass an empty `title` and override the SDK heading via className so the
14
+ * page reads as one continuous design.
11
15
  */
12
16
  export function ShopClient({
13
17
  products,
@@ -18,10 +22,11 @@ export function ShopClient({
18
22
  }) {
19
23
  return (
20
24
  <CataloguePage
21
- title="The Menu"
25
+ title="All products"
22
26
  products={products}
23
27
  categories={categories}
24
28
  renderCard={(p) => <StoreProductCard product={p} />}
29
+ className="max-w-7xl mx-auto px-6 sm:px-8 py-10 sm:py-12"
25
30
  />
26
31
  );
27
32
  }