@cimplify/sdk 0.39.0 → 0.40.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 (72) hide show
  1. package/dist/dispatcher.mjs +39 -5
  2. package/dist/styles.css +1 -1
  3. package/package.json +1 -1
  4. package/templates/{storefront → storefront-bakery}/README.md +8 -1
  5. package/templates/storefront-bakery/app/about/page.tsx +61 -0
  6. package/templates/storefront-bakery/app/categories/[slug]/listing-client.tsx +19 -0
  7. package/templates/{storefront → storefront-bakery}/app/categories/[slug]/page.tsx +42 -10
  8. package/templates/storefront-bakery/app/collections/[slug]/listing-client.tsx +20 -0
  9. package/templates/{storefront → storefront-bakery}/app/collections/[slug]/page.tsx +42 -10
  10. package/templates/storefront-bakery/app/faq/page.tsx +122 -0
  11. package/templates/{storefront → storefront-bakery}/app/page.tsx +39 -6
  12. package/templates/storefront-bakery/app/privacy/page.tsx +88 -0
  13. package/templates/storefront-bakery/app/terms/page.tsx +102 -0
  14. package/templates/storefront-bakery/components/cart-pill.tsx +32 -0
  15. package/templates/storefront-bakery/components/footer.tsx +190 -0
  16. package/templates/storefront-bakery/components/header.tsx +51 -0
  17. package/templates/storefront-bakery/components/nav-link.tsx +20 -0
  18. package/templates/{storefront → storefront-bakery}/components/store-product-card.tsx +9 -25
  19. package/templates/{storefront → storefront-bakery}/next.config.ts +3 -4
  20. package/templates/{storefront → storefront-bakery}/package.json +1 -1
  21. package/templates/storefront-retail/.env.example +11 -0
  22. package/templates/storefront-retail/README.md +77 -0
  23. package/templates/storefront-retail/app/about/page.tsx +71 -0
  24. package/templates/storefront-retail/app/cart/page.tsx +9 -0
  25. package/templates/storefront-retail/app/categories/[slug]/listing-client.tsx +19 -0
  26. package/templates/storefront-retail/app/categories/[slug]/page.tsx +117 -0
  27. package/templates/storefront-retail/app/checkout/page.tsx +17 -0
  28. package/templates/storefront-retail/app/collections/[slug]/listing-client.tsx +20 -0
  29. package/templates/storefront-retail/app/collections/[slug]/page.tsx +117 -0
  30. package/templates/storefront-retail/app/faq/page.tsx +127 -0
  31. package/templates/storefront-retail/app/globals.css +47 -0
  32. package/templates/storefront-retail/app/layout.tsx +46 -0
  33. package/templates/storefront-retail/app/orders/[id]/page.tsx +24 -0
  34. package/templates/storefront-retail/app/page.tsx +97 -0
  35. package/templates/storefront-retail/app/privacy/page.tsx +101 -0
  36. package/templates/storefront-retail/app/shop/page.tsx +30 -0
  37. package/templates/storefront-retail/app/shop/shop-client.tsx +27 -0
  38. package/templates/storefront-retail/app/terms/page.tsx +117 -0
  39. package/templates/storefront-retail/components/cart-pill.tsx +32 -0
  40. package/templates/storefront-retail/components/category-grid.tsx +28 -0
  41. package/templates/storefront-retail/components/collection-strip.tsx +45 -0
  42. package/templates/storefront-retail/components/footer.tsx +188 -0
  43. package/templates/storefront-retail/components/header.tsx +47 -0
  44. package/templates/storefront-retail/components/hero.tsx +28 -0
  45. package/templates/storefront-retail/components/nav-link.tsx +20 -0
  46. package/templates/storefront-retail/components/product-modal.tsx +105 -0
  47. package/templates/storefront-retail/components/providers.tsx +31 -0
  48. package/templates/storefront-retail/components/store-product-card.tsx +83 -0
  49. package/templates/storefront-retail/lib/cart.ts +27 -0
  50. package/templates/storefront-retail/next.config.ts +40 -0
  51. package/templates/storefront-retail/package.json +28 -0
  52. package/templates/storefront-retail/postcss.config.mjs +7 -0
  53. package/templates/storefront-retail/tsconfig.json +23 -0
  54. package/templates/storefront/components/footer.tsx +0 -19
  55. package/templates/storefront/components/header.tsx +0 -41
  56. package/templates/storefront/lib/server-client.ts +0 -27
  57. /package/templates/{storefront → storefront-bakery}/.env.example +0 -0
  58. /package/templates/{storefront → storefront-bakery}/app/cart/page.tsx +0 -0
  59. /package/templates/{storefront → storefront-bakery}/app/checkout/page.tsx +0 -0
  60. /package/templates/{storefront → storefront-bakery}/app/globals.css +0 -0
  61. /package/templates/{storefront → storefront-bakery}/app/layout.tsx +0 -0
  62. /package/templates/{storefront → storefront-bakery}/app/orders/[id]/page.tsx +0 -0
  63. /package/templates/{storefront → storefront-bakery}/app/shop/page.tsx +0 -0
  64. /package/templates/{storefront → storefront-bakery}/app/shop/shop-client.tsx +0 -0
  65. /package/templates/{storefront → storefront-bakery}/components/category-grid.tsx +0 -0
  66. /package/templates/{storefront → storefront-bakery}/components/collection-strip.tsx +0 -0
  67. /package/templates/{storefront → storefront-bakery}/components/hero.tsx +0 -0
  68. /package/templates/{storefront → storefront-bakery}/components/product-modal.tsx +0 -0
  69. /package/templates/{storefront → storefront-bakery}/components/providers.tsx +0 -0
  70. /package/templates/{storefront → storefront-bakery}/lib/cart.ts +0 -0
  71. /package/templates/{storefront → storefront-bakery}/postcss.config.mjs +0 -0
  72. /package/templates/{storefront → storefront-bakery}/tsconfig.json +0 -0
@@ -1,4 +1,5 @@
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, type Product } from "@cimplify/sdk/server";
4
5
  import { Hero } from "@/components/hero";
@@ -51,14 +52,46 @@ export default async function HomePage() {
51
52
  subtitle="Sourdough, croissants, cakes, and more — handcrafted in East Legon, delivered across Accra."
52
53
  />
53
54
  {collections.map(({ collection, products }) => (
54
- <CollectionStrip
55
+ <Suspense
55
56
  key={collection.id}
56
- collection={collection}
57
- products={products}
58
- collectionHref={`/collections/${collection.slug}`}
59
- />
57
+ fallback={<StripSkeleton title={collection.name} />}
58
+ >
59
+ <CollectionStrip
60
+ collection={collection}
61
+ products={products}
62
+ collectionHref={`/collections/${collection.slug}`}
63
+ />
64
+ </Suspense>
60
65
  ))}
61
- <CategoryGrid categories={categories} />
66
+ <Suspense fallback={<CategoryGridSkeleton />}>
67
+ <CategoryGrid categories={categories} />
68
+ </Suspense>
62
69
  </>
63
70
  );
64
71
  }
72
+
73
+ function StripSkeleton({ title }: { title: string }) {
74
+ return (
75
+ <section className="max-w-7xl mx-auto px-8 pt-12">
76
+ <h2 className="font-serif text-[28px] font-semibold m-0 mb-5">{title}</h2>
77
+ <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto pb-2">
78
+ {Array.from({ length: 5 }).map((_, i) => (
79
+ <div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
80
+ ))}
81
+ </div>
82
+ </section>
83
+ );
84
+ }
85
+
86
+ function CategoryGridSkeleton() {
87
+ return (
88
+ <section className="max-w-7xl mx-auto px-8 pt-14">
89
+ <div className="h-8 w-48 bg-muted rounded mb-5 animate-pulse" />
90
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
91
+ {Array.from({ length: 6 }).map((_, i) => (
92
+ <div key={i} className="h-32 bg-muted rounded-2xl animate-pulse" />
93
+ ))}
94
+ </div>
95
+ </section>
96
+ );
97
+ }
@@ -0,0 +1,88 @@
1
+ import type { Metadata } from "next";
2
+
3
+ export const metadata: Metadata = {
4
+ title: "Privacy Policy — Akua's Bakery",
5
+ description:
6
+ "How Akua's Bakery collects, uses, and protects your personal data.",
7
+ };
8
+
9
+ export default function PrivacyPage() {
10
+ return (
11
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
12
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
13
+ Privacy
14
+ </p>
15
+ <h1 className="font-serif text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
16
+ Privacy Policy
17
+ </h1>
18
+ <p className="text-sm text-muted-foreground not-prose mb-10">
19
+ Last updated: 1 May 2026
20
+ </p>
21
+
22
+ <section className="space-y-5 leading-relaxed text-foreground/90">
23
+ <h2 className="font-serif text-2xl font-semibold mt-0">What we collect</h2>
24
+ <p>
25
+ We collect only what we need to fulfil your order: your name, phone,
26
+ delivery address, email, and the items in your order. Payment details
27
+ are processed by our payment partners (Paystack, Stripe) and never
28
+ stored on our servers.
29
+ </p>
30
+
31
+ <h2 className="font-serif text-2xl font-semibold">How we use it</h2>
32
+ <ul className="list-disc pl-6 space-y-2">
33
+ <li>To prepare, deliver, and confirm your order.</li>
34
+ <li>To send order updates by SMS and email.</li>
35
+ <li>
36
+ To send occasional product news — only if you opted in. Unsubscribe
37
+ anytime in one click.
38
+ </li>
39
+ <li>
40
+ To analyse aggregated, anonymised browsing patterns so we can
41
+ improve the site.
42
+ </li>
43
+ </ul>
44
+
45
+ <h2 className="font-serif text-2xl font-semibold">Who we share it with</h2>
46
+ <p>
47
+ We share data only with the partners required to fulfil your order:
48
+ payment processors (Paystack, Stripe), delivery riders, and our
49
+ email/SMS providers. We do not sell or rent personal data to third
50
+ parties — ever.
51
+ </p>
52
+
53
+ <h2 className="font-serif text-2xl font-semibold">Cookies</h2>
54
+ <p>
55
+ We use a small number of strictly-necessary cookies to keep your cart
56
+ working and your session signed in. We don't use third-party
57
+ advertising or tracking cookies.
58
+ </p>
59
+
60
+ <h2 className="font-serif text-2xl font-semibold">Your rights</h2>
61
+ <p>
62
+ Under the Ghana Data Protection Act, 2012 (Act 843), you have the
63
+ right to access the data we hold about you, correct it, or have it
64
+ deleted. Email{" "}
65
+ <a
66
+ href="mailto:privacy@akuasbakery.test"
67
+ className="text-primary font-semibold hover:underline"
68
+ >
69
+ privacy@akuasbakery.test
70
+ </a>{" "}
71
+ and we'll respond within 14 days.
72
+ </p>
73
+
74
+ <h2 className="font-serif text-2xl font-semibold">Retention</h2>
75
+ <p>
76
+ We keep order records for 7 years to comply with Ghanaian tax law.
77
+ Marketing-list opt-ins are kept until you unsubscribe.
78
+ </p>
79
+
80
+ <h2 className="font-serif text-2xl font-semibold">Changes</h2>
81
+ <p>
82
+ If we make material changes to this policy, we'll email customers on
83
+ our marketing list and update the &quot;last updated&quot; date above.
84
+ </p>
85
+ </section>
86
+ </article>
87
+ );
88
+ }
@@ -0,0 +1,102 @@
1
+ import type { Metadata } from "next";
2
+
3
+ export const metadata: Metadata = {
4
+ title: "Terms of Service — Akua's Bakery",
5
+ description: "The rules of the road for ordering from Akua's Bakery.",
6
+ };
7
+
8
+ export default function TermsPage() {
9
+ return (
10
+ <article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
11
+ <p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
12
+ Terms of service
13
+ </p>
14
+ <h1 className="font-serif text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
15
+ Terms of Service
16
+ </h1>
17
+ <p className="text-sm text-muted-foreground not-prose mb-10">
18
+ Last updated: 1 May 2026
19
+ </p>
20
+
21
+ <section className="space-y-5 leading-relaxed text-foreground/90">
22
+ <h2 className="font-serif text-2xl font-semibold mt-0">1. Who we are</h2>
23
+ <p>
24
+ Akua's Bakery (&quot;we&quot;, &quot;us&quot;) is a bakery and pâtisserie operating
25
+ at 12 Independence Avenue, Accra, Ghana. By placing an order through this
26
+ website, you (&quot;you&quot;, &quot;customer&quot;) agree to these terms.
27
+ </p>
28
+
29
+ <h2 className="font-serif text-2xl font-semibold">2. Orders and acceptance</h2>
30
+ <p>
31
+ Placing an order constitutes an offer to buy. We confirm acceptance by
32
+ sending an order confirmation email or SMS. Until acceptance, we may
33
+ decline an order — we'll refund any payment in full, immediately.
34
+ </p>
35
+
36
+ <h2 className="font-serif text-2xl font-semibold">3. Pricing and payment</h2>
37
+ <p>
38
+ Prices are in Ghana Cedis (GH₵) and include VAT where applicable.
39
+ Payment is due at checkout via Mobile Money, card, or cash on delivery.
40
+ We reserve the right to correct typographical pricing errors before
41
+ fulfilment.
42
+ </p>
43
+
44
+ <h2 className="font-serif text-2xl font-semibold">4. Delivery and pickup</h2>
45
+ <p>
46
+ Delivery times are estimates, not guarantees. We're not liable for
47
+ delays caused by weather, traffic, or events outside our control. If
48
+ a rider can't reach you within 30 minutes of arrival, the order may be
49
+ returned to the kitchen — please make sure your address and phone are
50
+ correct.
51
+ </p>
52
+
53
+ <h2 className="font-serif text-2xl font-semibold">5. Custom cakes</h2>
54
+ <p>
55
+ Custom cake orders require a 50% deposit at booking and the balance
56
+ before pickup or dispatch. Cancellations more than 72 hours before the
57
+ event date receive a full refund of the deposit; within 72 hours,
58
+ the deposit is non-refundable.
59
+ </p>
60
+
61
+ <h2 className="font-serif text-2xl font-semibold">6. Allergens</h2>
62
+ <p>
63
+ Our kitchen handles wheat, dairy, eggs, and tree nuts. We can't
64
+ guarantee freedom from cross-contamination. Customers with severe
65
+ allergies order at their own risk.
66
+ </p>
67
+
68
+ <h2 className="font-serif text-2xl font-semibold">7. Returns</h2>
69
+ <p>
70
+ Baked goods are non-returnable for food-safety reasons. If something
71
+ is wrong with your order, contact us within 24 hours of delivery and
72
+ we'll replace it, refund it, or credit your account — your choice.
73
+ </p>
74
+
75
+ <h2 className="font-serif text-2xl font-semibold">8. Liability</h2>
76
+ <p>
77
+ To the maximum extent permitted by Ghanaian law, our liability for any
78
+ claim arising from this website or your order is limited to the value
79
+ of the order. We're not liable for indirect or consequential losses.
80
+ </p>
81
+
82
+ <h2 className="font-serif text-2xl font-semibold">9. Governing law</h2>
83
+ <p>
84
+ These terms are governed by the laws of Ghana. Disputes will be
85
+ resolved in the courts of Greater Accra.
86
+ </p>
87
+
88
+ <h2 className="font-serif text-2xl font-semibold">10. Contact</h2>
89
+ <p>
90
+ Questions? Email{" "}
91
+ <a
92
+ href="mailto:hello@akuasbakery.test"
93
+ className="text-primary font-semibold hover:underline"
94
+ >
95
+ hello@akuasbakery.test
96
+ </a>
97
+ .
98
+ </p>
99
+ </section>
100
+ </article>
101
+ );
102
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useCartCount } from "@/lib/cart";
5
+
6
+ /**
7
+ * Cart pill — dynamic island. Reads the live cart count via the SDK and
8
+ * renders a link to `/cart`. Wrap in `<Suspense fallback={<CartPillSkeleton/>}>`
9
+ * so the cached header chrome streams without blocking on the cart fetch.
10
+ */
11
+ export function CartPill() {
12
+ const { count } = useCartCount();
13
+ return (
14
+ <Link
15
+ href="/cart"
16
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-foreground text-background text-xs font-semibold tracking-wide transition-transform hover:scale-105"
17
+ >
18
+ Cart · {count}
19
+ </Link>
20
+ );
21
+ }
22
+
23
+ export function CartPillSkeleton() {
24
+ return (
25
+ <span
26
+ aria-hidden
27
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-foreground/80 text-background text-xs font-semibold tracking-wide"
28
+ >
29
+ Cart · …
30
+ </span>
31
+ );
32
+ }
@@ -0,0 +1,190 @@
1
+ import Link from "next/link";
2
+
3
+ const SOCIALS: { label: string; href: string; svg: React.ReactNode }[] = [
4
+ {
5
+ label: "Instagram",
6
+ href: "https://instagram.com/akuasbakery",
7
+ svg: (
8
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden className="w-5 h-5">
9
+ <rect x="3" y="3" width="18" height="18" rx="5" />
10
+ <circle cx="12" cy="12" r="4" />
11
+ <circle cx="17.5" cy="6.5" r="0.75" fill="currentColor" />
12
+ </svg>
13
+ ),
14
+ },
15
+ {
16
+ label: "TikTok",
17
+ href: "https://tiktok.com/@akuasbakery",
18
+ svg: (
19
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
20
+ <path d="M16 3v3.5a4.5 4.5 0 0 0 4.5 4.5V14a7.5 7.5 0 0 1-4.5-1.5V16a5 5 0 1 1-5-5v3a2 2 0 1 0 2 2V3z" />
21
+ </svg>
22
+ ),
23
+ },
24
+ {
25
+ label: "X",
26
+ href: "https://x.com/akuasbakery",
27
+ svg: (
28
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
29
+ <path d="M18 2h3l-7.5 8.6L22 22h-6.6l-5-6.5L4 22H1l8-9.2L1.4 2H8l4.6 6 5.4-6z" />
30
+ </svg>
31
+ ),
32
+ },
33
+ {
34
+ label: "Facebook",
35
+ href: "https://facebook.com/akuasbakery",
36
+ svg: (
37
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
38
+ <path d="M14 9V7a1 1 0 0 1 1-1h2V3h-3a4 4 0 0 0-4 4v2H8v3h2v9h3v-9h2.5l.5-3H13z" />
39
+ </svg>
40
+ ),
41
+ },
42
+ {
43
+ label: "WhatsApp",
44
+ href: "https://wa.me/233244000000",
45
+ svg: (
46
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
47
+ <path d="M12 2a10 10 0 0 0-8.6 15l-1.4 5 5.2-1.4A10 10 0 1 0 12 2zm5 14.2c-.2.6-1.2 1.2-1.7 1.2-.5.1-1.1.1-1.7-.1-.4-.1-.9-.3-1.5-.6a8.4 8.4 0 0 1-3.7-3.4c-.7-1-1-1.8-1-2.5 0-.7.4-1.1.6-1.3.2-.2.4-.2.5-.2h.4c.1 0 .3 0 .4.3l.6 1.4c.1.2 0 .3 0 .4l-.3.4-.3.3c-.1.1-.2.2-.1.4.2.4.7 1.1 1.4 1.8.9.8 1.7 1.1 1.9 1.2.2.1.3.1.5-.1l.6-.7c.2-.2.3-.2.5-.1l1.4.7c.2.1.3.2.4.3.1.2.1.6 0 1z" />
48
+ </svg>
49
+ ),
50
+ },
51
+ ];
52
+
53
+ const SITEMAP: { title: string; links: { label: string; href: string }[] }[] = [
54
+ {
55
+ title: "Shop",
56
+ links: [
57
+ { label: "All products", href: "/shop" },
58
+ { label: "Breads", href: "/categories/breads" },
59
+ { label: "Pastries", href: "/categories/pastries" },
60
+ { label: "Cakes", href: "/categories/cakes" },
61
+ { label: "Drinks", href: "/categories/drinks" },
62
+ { label: "Merch & More", href: "/categories/merch" },
63
+ { label: "Classes & Stays", href: "/categories/classes" },
64
+ ],
65
+ },
66
+ {
67
+ title: "Bakery",
68
+ links: [
69
+ { label: "Our story", href: "/about" },
70
+ { label: "FAQ", href: "/faq" },
71
+ { label: "Visit us", href: "/about#visit" },
72
+ { label: "Wholesale", href: "/categories/merch" },
73
+ ],
74
+ },
75
+ {
76
+ title: "Help",
77
+ links: [
78
+ { label: "Contact", href: "mailto:hello@akuasbakery.test" },
79
+ { label: "Custom cakes", href: "/faq#custom-cakes--celebrations" },
80
+ { label: "Allergens", href: "/faq#allergens--ingredients" },
81
+ { label: "Delivery", href: "/faq#ordering--delivery" },
82
+ ],
83
+ },
84
+ {
85
+ title: "Legal",
86
+ links: [
87
+ { label: "Terms of Service", href: "/terms" },
88
+ { label: "Privacy Policy", href: "/privacy" },
89
+ ],
90
+ },
91
+ ];
92
+
93
+ export async function Footer() {
94
+ "use cache";
95
+ const year = new Date().getFullYear();
96
+ return (
97
+ <footer className="mt-16 border-t border-border bg-card text-sm">
98
+ <div className="max-w-7xl mx-auto px-8 pt-12 pb-8">
99
+ <div className="grid gap-10 md:grid-cols-[1.4fr_repeat(4,1fr)]">
100
+ <div>
101
+ <p className="font-serif text-2xl text-foreground m-0 mb-2">Akua&apos;s Bakery</p>
102
+ <p className="text-muted-foreground leading-relaxed mb-4 max-w-sm">
103
+ Family-run bakery in East Legon. Sourdough, croissants, layered
104
+ cakes — handcrafted since 2018, delivered across Accra.
105
+ </p>
106
+ <address className="not-italic text-muted-foreground space-y-1">
107
+ <p className="m-0">12 Independence Ave, Accra</p>
108
+ <p className="m-0">
109
+ <a href="tel:+233244000000" className="hover:text-foreground transition-colors">
110
+ +233 244 000 000
111
+ </a>
112
+ </p>
113
+ <p className="m-0">
114
+ <a
115
+ href="mailto:hello@akuasbakery.test"
116
+ className="hover:text-foreground transition-colors"
117
+ >
118
+ hello@akuasbakery.test
119
+ </a>
120
+ </p>
121
+ <p className="m-0 text-xs">Tue–Sun · 8am–8pm · closed Mondays</p>
122
+ </address>
123
+ <div className="flex items-center gap-3 mt-5">
124
+ {SOCIALS.map((s) => (
125
+ <a
126
+ key={s.label}
127
+ href={s.href}
128
+ aria-label={s.label}
129
+ target="_blank"
130
+ rel="noopener noreferrer"
131
+ className="inline-flex items-center justify-center w-9 h-9 rounded-full border border-border text-muted-foreground hover:text-foreground hover:border-foreground transition-colors"
132
+ >
133
+ {s.svg}
134
+ </a>
135
+ ))}
136
+ </div>
137
+ </div>
138
+ {SITEMAP.map((section) => (
139
+ <nav key={section.title} aria-labelledby={`footer-${section.title}`}>
140
+ <p
141
+ id={`footer-${section.title}`}
142
+ className="font-semibold text-foreground mb-3 text-[13px] uppercase tracking-[0.08em]"
143
+ >
144
+ {section.title}
145
+ </p>
146
+ <ul className="space-y-2 m-0 p-0 list-none">
147
+ {section.links.map((link) => (
148
+ <li key={link.label}>
149
+ <Link
150
+ href={link.href}
151
+ className="text-muted-foreground hover:text-foreground transition-colors"
152
+ >
153
+ {link.label}
154
+ </Link>
155
+ </li>
156
+ ))}
157
+ </ul>
158
+ </nav>
159
+ ))}
160
+ </div>
161
+
162
+ <div className="mt-12 pt-6 border-t border-border flex flex-col sm:flex-row items-center justify-between gap-3 text-xs text-muted-foreground">
163
+ <p className="m-0">© {year} Akua&apos;s Bakery. All rights reserved.</p>
164
+ <p className="m-0 inline-flex items-center gap-1.5">
165
+ <span className="text-muted-foreground/80">Powered by</span>
166
+ <a
167
+ href="https://app.cimplify.io"
168
+ target="_blank"
169
+ rel="noopener noreferrer"
170
+ aria-label="Cimplify"
171
+ className="inline-flex items-center gap-1 font-serif text-foreground hover:text-primary transition-colors"
172
+ >
173
+ <span className="font-semibold tracking-tight">Cimplify</span>
174
+ <svg
175
+ viewBox="0 0 12 12"
176
+ aria-hidden
177
+ className="w-3 h-3 opacity-70"
178
+ fill="none"
179
+ stroke="currentColor"
180
+ strokeWidth="1.5"
181
+ >
182
+ <path d="M3 9L9 3M9 3H4M9 3v5" strokeLinecap="round" strokeLinejoin="round" />
183
+ </svg>
184
+ </a>
185
+ </p>
186
+ </div>
187
+ </div>
188
+ </footer>
189
+ );
190
+ }
@@ -0,0 +1,51 @@
1
+ import Link from "next/link";
2
+ import { Suspense } from "react";
3
+ import { NavLink } from "./nav-link";
4
+ import { CartPill, CartPillSkeleton } from "./cart-pill";
5
+
6
+ const TAGLINE = "ACCRA · SINCE 2018";
7
+
8
+ /**
9
+ * Server-rendered header chrome. Brand mark + nav layout streams from the
10
+ * cache; the active-link styling and live cart count are dynamic islands
11
+ * mounted in their own Suspense boundaries so the chrome never blocks.
12
+ */
13
+ export function Header() {
14
+ return (
15
+ <header className="sticky top-0 z-30 flex items-center justify-between px-8 py-4 border-b border-border bg-background/90 backdrop-blur-md">
16
+ <Link href="/" className="flex items-baseline gap-2">
17
+ <span className="font-serif text-[22px] font-semibold -tracking-[0.02em]">
18
+ Akua&apos;s Bakery
19
+ </span>
20
+ <span className="text-[11px] font-medium uppercase tracking-[0.12em] text-muted-foreground">
21
+ {TAGLINE}
22
+ </span>
23
+ </Link>
24
+ <nav className="flex items-center gap-6">
25
+ <Suspense fallback={<NavLinkFallback>Home</NavLinkFallback>}>
26
+ <NavLink href="/">Home</NavLink>
27
+ </Suspense>
28
+ <Suspense fallback={<NavLinkFallback>Shop</NavLinkFallback>}>
29
+ <NavLink href="/shop">Shop</NavLink>
30
+ </Suspense>
31
+ <Suspense fallback={<NavLinkFallback>About</NavLinkFallback>}>
32
+ <NavLink href="/about">About</NavLink>
33
+ </Suspense>
34
+ <Suspense fallback={<NavLinkFallback>FAQ</NavLinkFallback>}>
35
+ <NavLink href="/faq">FAQ</NavLink>
36
+ </Suspense>
37
+ <Suspense fallback={<CartPillSkeleton />}>
38
+ <CartPill />
39
+ </Suspense>
40
+ </nav>
41
+ </header>
42
+ );
43
+ }
44
+
45
+ function NavLinkFallback({ children }: { children: React.ReactNode }) {
46
+ return (
47
+ <span className="text-[13px] font-medium tracking-wide text-muted-foreground">
48
+ {children}
49
+ </span>
50
+ );
51
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+
6
+ export function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
7
+ const pathname = usePathname();
8
+ const active = pathname === href;
9
+ return (
10
+ <Link
11
+ href={href}
12
+ className={[
13
+ "text-[13px] font-medium tracking-wide transition-colors",
14
+ active ? "text-primary" : "text-muted-foreground hover:text-foreground",
15
+ ].join(" ")}
16
+ >
17
+ {children}
18
+ </Link>
19
+ );
20
+ }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useRouter, useSearchParams, usePathname } from "next/navigation";
3
+ import Link from "next/link";
4
4
  import {
5
5
  CardVariant,
6
6
  FoodProductCard,
@@ -60,39 +60,23 @@ interface Props {
60
60
 
61
61
  /**
62
62
  * Variant-aware product card that opens the URL-driven product modal on
63
- * click (`?product=<slug>`). Renders the underlying SDK variant card
64
- * (Food, Bundle, Composite, …) directly and supplies a `renderLink` that
65
- * intercepts navigation — so a single `<CardShell>`-rendered `<a>` tag
66
- * wraps the card (no nested anchors, no hydration error).
63
+ * click. Uses `next/link` with `?product=<slug>` so the link is statically
64
+ * pre-renderable — no `useSearchParams` dependency means cards don't
65
+ * become dynamic islands. The modal (which already reads `useSearchParams`
66
+ * inside a Suspense boundary in the root layout) handles the rest.
67
67
  */
68
68
  export function StoreProductCard({ product, variant }: Props) {
69
- const router = useRouter();
70
- const pathname = usePathname();
71
- const searchParams = useSearchParams();
72
-
73
69
  const slug = product.slug || product.id;
74
70
  const Card = VARIANT_CARDS[variant ?? resolveVariant(product)];
75
-
76
- const open = () => {
77
- const next = new URLSearchParams(searchParams?.toString() ?? "");
78
- next.set("product", slug);
79
- router.push(`${pathname}?${next.toString()}`, { scroll: false });
80
- };
71
+ const href = `?product=${encodeURIComponent(slug)}`;
81
72
 
82
73
  return (
83
74
  <Card
84
75
  product={product}
85
- renderLink={({ href, className, children }) => (
86
- <a
87
- href={href}
88
- className={className}
89
- onClick={(e) => {
90
- e.preventDefault();
91
- open();
92
- }}
93
- >
76
+ renderLink={({ className, children }) => (
77
+ <Link href={href} scroll={false} prefetch={false} className={className}>
94
78
  {children}
95
- </a>
79
+ </Link>
96
80
  )}
97
81
  />
98
82
  );
@@ -14,10 +14,9 @@ const MOCK_URL = process.env.NEXT_PUBLIC_CIMPLIFY_API_URL?.trim() || "http://127
14
14
  * pass the absolute URL through `<CimplifyProvider/>`).
15
15
  */
16
16
  const nextConfig: NextConfig = {
17
- // Enable Next 16's `cacheComponents` mode so we can use the `'use cache'`
18
- // directive + `cacheTag` / `cacheLife` from `next/cache` for SSR caching.
19
- // The Cimplify storefront's RSC pages depend on this — disable only if
20
- // you've migrated every server fetch to a different caching strategy.
17
+ // Enable Next 16's `cacheComponents` mode so we can use `'use cache'` +
18
+ // `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
19
+ // dynamic Suspense boundaries fill in when ready.
21
20
  cacheComponents: true,
22
21
  async rewrites() {
23
22
  return [
@@ -11,7 +11,7 @@
11
11
  "lint": "next lint"
12
12
  },
13
13
  "dependencies": {
14
- "@cimplify/sdk": "^0.38.1",
14
+ "@cimplify/sdk": "^0.39.1",
15
15
  "next": "^16.2.4",
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0"
@@ -0,0 +1,11 @@
1
+ # Cimplify API base URL.
2
+ # Dev: leave empty so the SDK uses the current origin (localhost:3000),
3
+ # which Next.js rewrites to the mock at 127.0.0.1:8787 (see next.config.ts).
4
+ # Production: set to your Cimplify host (e.g. https://api.cimplify.io).
5
+ NEXT_PUBLIC_CIMPLIFY_API_URL=
6
+
7
+ # Tenant public key. The mock accepts any value.
8
+ NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY=mock-dev
9
+
10
+ # Business id used by the mock seed (retail: Currents Electronics).
11
+ NEXT_PUBLIC_CIMPLIFY_BUSINESS_ID=bus_currents_electronics