@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.
- package/dist/dispatcher.mjs +39 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/{storefront → storefront-bakery}/README.md +8 -1
- package/templates/storefront-bakery/app/about/page.tsx +61 -0
- package/templates/storefront-bakery/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/{storefront → storefront-bakery}/app/categories/[slug]/page.tsx +42 -10
- package/templates/storefront-bakery/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/{storefront → storefront-bakery}/app/collections/[slug]/page.tsx +42 -10
- package/templates/storefront-bakery/app/faq/page.tsx +122 -0
- package/templates/{storefront → storefront-bakery}/app/page.tsx +39 -6
- package/templates/storefront-bakery/app/privacy/page.tsx +88 -0
- package/templates/storefront-bakery/app/terms/page.tsx +102 -0
- package/templates/storefront-bakery/components/cart-pill.tsx +32 -0
- package/templates/storefront-bakery/components/footer.tsx +190 -0
- package/templates/storefront-bakery/components/header.tsx +51 -0
- package/templates/storefront-bakery/components/nav-link.tsx +20 -0
- package/templates/{storefront → storefront-bakery}/components/store-product-card.tsx +9 -25
- package/templates/{storefront → storefront-bakery}/next.config.ts +3 -4
- package/templates/{storefront → storefront-bakery}/package.json +1 -1
- package/templates/storefront-retail/.env.example +11 -0
- package/templates/storefront-retail/README.md +77 -0
- package/templates/storefront-retail/app/about/page.tsx +71 -0
- package/templates/storefront-retail/app/cart/page.tsx +9 -0
- package/templates/storefront-retail/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-retail/app/categories/[slug]/page.tsx +117 -0
- package/templates/storefront-retail/app/checkout/page.tsx +17 -0
- package/templates/storefront-retail/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-retail/app/collections/[slug]/page.tsx +117 -0
- package/templates/storefront-retail/app/faq/page.tsx +127 -0
- package/templates/storefront-retail/app/globals.css +47 -0
- package/templates/storefront-retail/app/layout.tsx +46 -0
- package/templates/storefront-retail/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-retail/app/page.tsx +97 -0
- package/templates/storefront-retail/app/privacy/page.tsx +101 -0
- package/templates/storefront-retail/app/shop/page.tsx +30 -0
- package/templates/storefront-retail/app/shop/shop-client.tsx +27 -0
- package/templates/storefront-retail/app/terms/page.tsx +117 -0
- package/templates/storefront-retail/components/cart-pill.tsx +32 -0
- package/templates/storefront-retail/components/category-grid.tsx +28 -0
- package/templates/storefront-retail/components/collection-strip.tsx +45 -0
- package/templates/storefront-retail/components/footer.tsx +188 -0
- package/templates/storefront-retail/components/header.tsx +47 -0
- package/templates/storefront-retail/components/hero.tsx +28 -0
- package/templates/storefront-retail/components/nav-link.tsx +20 -0
- package/templates/storefront-retail/components/product-modal.tsx +105 -0
- package/templates/storefront-retail/components/providers.tsx +31 -0
- package/templates/storefront-retail/components/store-product-card.tsx +83 -0
- package/templates/storefront-retail/lib/cart.ts +27 -0
- package/templates/storefront-retail/next.config.ts +40 -0
- package/templates/storefront-retail/package.json +28 -0
- package/templates/storefront-retail/postcss.config.mjs +7 -0
- package/templates/storefront-retail/tsconfig.json +23 -0
- package/templates/storefront/components/footer.tsx +0 -19
- package/templates/storefront/components/header.tsx +0 -41
- package/templates/storefront/lib/server-client.ts +0 -27
- /package/templates/{storefront → storefront-bakery}/.env.example +0 -0
- /package/templates/{storefront → storefront-bakery}/app/cart/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/checkout/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/globals.css +0 -0
- /package/templates/{storefront → storefront-bakery}/app/layout.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/orders/[id]/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/shop/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/shop/shop-client.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/category-grid.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/collection-strip.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/hero.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/product-modal.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/providers.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/lib/cart.ts +0 -0
- /package/templates/{storefront → storefront-bakery}/postcss.config.mjs +0 -0
- /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
|
-
<
|
|
55
|
+
<Suspense
|
|
55
56
|
key={collection.id}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
<
|
|
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 "last updated" 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 ("we", "us") is a bakery and pâtisserie operating
|
|
25
|
+
at 12 Independence Avenue, Accra, Ghana. By placing an order through this
|
|
26
|
+
website, you ("you", "customer") 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'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'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'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
|
|
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
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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={({
|
|
86
|
-
<
|
|
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
|
-
</
|
|
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
|
|
18
|
-
//
|
|
19
|
-
//
|
|
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 [
|
|
@@ -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
|