@cimplify/sdk 0.38.5 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JTU5P6GM.mjs → chunk-3WD6U5RD.mjs} +1 -83
- package/dist/chunk-BGCEKFLA.js +89 -0
- package/dist/chunk-IRJFDKFV.mjs +83 -0
- package/dist/{chunk-5VHKZTFJ.js → chunk-OMAP327U.js} +0 -87
- package/dist/index.js +34 -33
- package/dist/index.mjs +2 -1
- package/dist/react.js +47 -46
- package/dist/react.mjs +3 -2
- package/dist/server.d.mts +97 -0
- package/dist/server.d.ts +97 -0
- package/dist/server.js +107 -0
- package/dist/server.mjs +92 -0
- package/dist/styles.css +1 -1
- package/package.json +7 -1
- package/templates/storefront/app/about/page.tsx +61 -0
- package/templates/storefront/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront/app/categories/[slug]/page.tsx +94 -57
- package/templates/storefront/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront/app/collections/[slug]/page.tsx +94 -57
- package/templates/storefront/app/faq/page.tsx +122 -0
- package/templates/storefront/app/page.tsx +77 -45
- package/templates/storefront/app/privacy/page.tsx +88 -0
- package/templates/storefront/app/shop/page.tsx +25 -41
- package/templates/storefront/app/shop/shop-client.tsx +27 -0
- package/templates/storefront/app/terms/page.tsx +102 -0
- package/templates/storefront/components/cart-pill.tsx +32 -0
- package/templates/storefront/components/footer.tsx +186 -15
- package/templates/storefront/components/header.tsx +32 -22
- package/templates/storefront/components/nav-link.tsx +20 -0
- package/templates/storefront/components/store-product-card.tsx +9 -25
- package/templates/storefront/lib/server-client.ts +27 -0
- package/templates/storefront/next.config.ts +4 -0
|
@@ -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
|
+
}
|
|
@@ -1,19 +1,190 @@
|
|
|
1
|
-
|
|
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();
|
|
2
96
|
return (
|
|
3
|
-
<footer className="mt-
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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>
|
|
17
188
|
</footer>
|
|
18
189
|
);
|
|
19
190
|
}
|
|
@@ -1,41 +1,51 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
1
|
import Link from "next/link";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { NavLink } from "./nav-link";
|
|
4
|
+
import { CartPill, CartPillSkeleton } from "./cart-pill";
|
|
6
5
|
|
|
7
6
|
const TAGLINE = "ACCRA · SINCE 2018";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
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
|
+
*/
|
|
16
13
|
export function Header() {
|
|
17
|
-
const pathname = usePathname();
|
|
18
|
-
const { count } = useCartCount();
|
|
19
14
|
return (
|
|
20
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">
|
|
21
16
|
<Link href="/" className="flex items-baseline gap-2">
|
|
22
17
|
<span className="font-serif text-[22px] font-semibold -tracking-[0.02em]">
|
|
23
|
-
Akua
|
|
18
|
+
Akua's Bakery
|
|
24
19
|
</span>
|
|
25
20
|
<span className="text-[11px] font-medium uppercase tracking-[0.12em] text-muted-foreground">
|
|
26
21
|
{TAGLINE}
|
|
27
22
|
</span>
|
|
28
23
|
</Link>
|
|
29
24
|
<nav className="flex items-center gap-6">
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
|
|
37
|
-
|
|
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>
|
|
38
40
|
</nav>
|
|
39
41
|
</header>
|
|
40
42
|
);
|
|
41
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
|
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createCimplifyClient, type CimplifyClient } from "@cimplify/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-side Cimplify client. Used by Server Components to fetch catalogue
|
|
5
|
+
* data during SSR/RSC for SEO-friendly product/category/collection pages.
|
|
6
|
+
*
|
|
7
|
+
* Cached per-request via React's cache mechanism — calling this multiple
|
|
8
|
+
* times within the same request returns the same client instance.
|
|
9
|
+
*
|
|
10
|
+
* Base-URL resolution:
|
|
11
|
+
* 1) NEXT_PUBLIC_CIMPLIFY_API_URL (matches the client-side default)
|
|
12
|
+
* 2) http://127.0.0.1:8787 — the local mock during dev
|
|
13
|
+
*/
|
|
14
|
+
let cached: CimplifyClient | undefined;
|
|
15
|
+
|
|
16
|
+
export function getServerClient(): CimplifyClient {
|
|
17
|
+
if (cached) return cached;
|
|
18
|
+
const baseUrl =
|
|
19
|
+
process.env.NEXT_PUBLIC_CIMPLIFY_API_URL?.trim() || "http://127.0.0.1:8787";
|
|
20
|
+
const publicKey = process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY ?? "mock-dev";
|
|
21
|
+
cached = createCimplifyClient({
|
|
22
|
+
baseUrl,
|
|
23
|
+
publicKey,
|
|
24
|
+
suppressPublicKeyWarning: true,
|
|
25
|
+
});
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
@@ -14,6 +14,10 @@ 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 `'use cache'` +
|
|
18
|
+
// `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
|
|
19
|
+
// dynamic Suspense boundaries fill in when ready.
|
|
20
|
+
cacheComponents: true,
|
|
17
21
|
async rewrites() {
|
|
18
22
|
return [
|
|
19
23
|
{ source: "/api/v1/:path*", destination: `${MOCK_URL}/api/v1/:path*` },
|