@cimplify/sdk 0.39.1 → 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 → 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/lib/server-client.ts +0 -27
- /package/templates/{storefront → storefront-bakery}/.env.example +0 -0
- /package/templates/{storefront → storefront-bakery}/app/about/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/cart/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/categories/[slug]/listing-client.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/categories/[slug]/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/checkout/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/collections/[slug]/listing-client.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/collections/[slug]/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/faq/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/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/app/privacy/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}/app/terms/page.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/cart-pill.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/footer.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/header.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/hero.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/components/nav-link.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}/components/store-product-card.tsx +0 -0
- /package/templates/{storefront → storefront-bakery}/lib/cart.ts +0 -0
- /package/templates/{storefront → storefront-bakery}/next.config.ts +0 -0
- /package/templates/{storefront → storefront-bakery}/postcss.config.mjs +0 -0
- /package/templates/{storefront → storefront-bakery}/tsconfig.json +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { notFound } from "next/navigation";
|
|
5
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
6
|
+
import {
|
|
7
|
+
getServerClient,
|
|
8
|
+
tags,
|
|
9
|
+
type Collection,
|
|
10
|
+
type Product,
|
|
11
|
+
} from "@cimplify/sdk/server";
|
|
12
|
+
import { ListingClient } from "./listing-client";
|
|
13
|
+
|
|
14
|
+
interface CollectionData {
|
|
15
|
+
collection: Collection;
|
|
16
|
+
products: Product[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getCollection(slug: string): Promise<CollectionData | null> {
|
|
20
|
+
"use cache";
|
|
21
|
+
cacheTag(tags.collections());
|
|
22
|
+
cacheLife("hours");
|
|
23
|
+
|
|
24
|
+
const client = getServerClient();
|
|
25
|
+
const colRes = await client.catalogue.getCollectionBySlug(slug);
|
|
26
|
+
if (!colRes.ok) return null;
|
|
27
|
+
|
|
28
|
+
cacheTag(tags.collection(colRes.value.id), tags.collectionProducts(colRes.value.id));
|
|
29
|
+
const r = await client.catalogue.getCollectionProducts(colRes.value.id);
|
|
30
|
+
const products = r.ok
|
|
31
|
+
? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
|
|
32
|
+
: [];
|
|
33
|
+
return { collection: colRes.value, products };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function generateMetadata({
|
|
37
|
+
params,
|
|
38
|
+
}: {
|
|
39
|
+
params: Promise<{ slug: string }>;
|
|
40
|
+
}): Promise<Metadata> {
|
|
41
|
+
const { slug } = await params;
|
|
42
|
+
const data = await getCollection(slug);
|
|
43
|
+
if (!data) return {};
|
|
44
|
+
return {
|
|
45
|
+
title: `${data.collection.name} — Akua's Bakery`,
|
|
46
|
+
description: data.collection.description ?? undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default async function CollectionPage({
|
|
51
|
+
params,
|
|
52
|
+
}: {
|
|
53
|
+
params: Promise<{ slug: string }>;
|
|
54
|
+
}) {
|
|
55
|
+
return (
|
|
56
|
+
<Suspense fallback={<CollectionSkeleton />}>
|
|
57
|
+
<CollectionContent params={params} />
|
|
58
|
+
</Suspense>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function CollectionContent({
|
|
63
|
+
params,
|
|
64
|
+
}: {
|
|
65
|
+
params: Promise<{ slug: string }>;
|
|
66
|
+
}) {
|
|
67
|
+
const { slug } = await params;
|
|
68
|
+
const data = await getCollection(slug);
|
|
69
|
+
if (!data) notFound();
|
|
70
|
+
|
|
71
|
+
const { collection, products } = data;
|
|
72
|
+
return (
|
|
73
|
+
<section className="max-w-7xl mx-auto px-8 pt-12">
|
|
74
|
+
<header className="mb-8 text-center">
|
|
75
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
76
|
+
Collection
|
|
77
|
+
</p>
|
|
78
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-2">
|
|
79
|
+
{collection.name}
|
|
80
|
+
</h1>
|
|
81
|
+
{collection.description && (
|
|
82
|
+
<p className="mx-auto mb-2 max-w-xl text-muted-foreground">
|
|
83
|
+
{collection.description}
|
|
84
|
+
</p>
|
|
85
|
+
)}
|
|
86
|
+
<p className="text-sm text-muted-foreground">
|
|
87
|
+
{products.length} item{products.length === 1 ? "" : "s"}
|
|
88
|
+
</p>
|
|
89
|
+
</header>
|
|
90
|
+
<ListingClient products={products} />
|
|
91
|
+
{products.length === 0 && (
|
|
92
|
+
<p className="text-center mt-8">
|
|
93
|
+
<Link href="/" className="text-primary font-semibold">
|
|
94
|
+
← Back home
|
|
95
|
+
</Link>
|
|
96
|
+
</p>
|
|
97
|
+
)}
|
|
98
|
+
</section>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function CollectionSkeleton() {
|
|
103
|
+
return (
|
|
104
|
+
<section className="max-w-7xl mx-auto px-8 pt-12">
|
|
105
|
+
<header className="mb-8 text-center">
|
|
106
|
+
<div className="mx-auto h-3 w-20 bg-muted rounded mb-2 animate-pulse" />
|
|
107
|
+
<div className="mx-auto h-10 w-64 bg-muted rounded mb-2 animate-pulse" />
|
|
108
|
+
<div className="mx-auto h-4 w-80 bg-muted rounded animate-pulse" />
|
|
109
|
+
</header>
|
|
110
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
111
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
112
|
+
<div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</section>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Support — Currents Electronics",
|
|
5
|
+
description:
|
|
6
|
+
"Shipping, warranty, repairs, returns, payment — answers to the questions we hear most often.",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
interface QA {
|
|
10
|
+
q: string;
|
|
11
|
+
a: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SECTIONS: { title: string; items: QA[] }[] = [
|
|
15
|
+
{
|
|
16
|
+
title: "Ordering & delivery",
|
|
17
|
+
items: [
|
|
18
|
+
{
|
|
19
|
+
q: "How fast is shipping?",
|
|
20
|
+
a: "Free same-day delivery within Greater Accra for orders placed before 4pm. Outside Accra, we use a partner courier — usually 1–3 business days, with tracking sent by SMS.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
q: "Can I pick up in store?",
|
|
24
|
+
a: "Yes. Choose 'Pickup' at checkout and we'll have your order boxed at our Atomic Junction store within 30 minutes. Bring a photo ID matching the order name.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
q: "Do you ship internationally?",
|
|
28
|
+
a: "We ship across West Africa via DHL — rates and ETAs are calculated at checkout once you enter your destination.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
q: "How do I track my order?",
|
|
32
|
+
a: "You'll get an SMS and email with a tracking link within 30 minutes of dispatch. Same-day Accra orders include the rider's phone number.",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: "Warranty & repairs",
|
|
38
|
+
items: [
|
|
39
|
+
{
|
|
40
|
+
q: "What does the two-year warranty cover?",
|
|
41
|
+
a: "Manufacturing defects, electrical failures, and battery degradation below 80% within the first 24 months. Accidental damage, water damage, and software issues caused by user installs are excluded.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
q: "Where do I send a faulty device?",
|
|
45
|
+
a: "Drop it at our Atomic Junction store, or request a free pickup from your address by emailing support@currentselectronics.test. Loaner devices are available for warranty claims while we diagnose.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
q: "Can you fix devices bought elsewhere?",
|
|
49
|
+
a: "We offer paid repairs on Apple and Samsung devices regardless of where they were purchased. Expect a quote within 48 hours of inspection.",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
title: "Trade-in & financing",
|
|
55
|
+
items: [
|
|
56
|
+
{
|
|
57
|
+
q: "How does trade-in work?",
|
|
58
|
+
a: "Tell us the model and condition of your device, get an instant estimate, and ship it free or drop it at our store. Once we verify, we credit your account or pay out via Mobile Money — your choice.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
q: "Do you offer instalment payments?",
|
|
62
|
+
a: "Yes. Pay in 3 (interest-free) or 6/12 monthly instalments via our partner Fidelity Bank. Available at checkout for orders over GH₵2,000.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
q: "Are there bulk discounts for businesses?",
|
|
66
|
+
a: "Yes. Email business@currentselectronics.test with your shopping list and team size — most orders over 5 units qualify for a discount, plus free on-site setup.",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: "Payment & returns",
|
|
72
|
+
items: [
|
|
73
|
+
{
|
|
74
|
+
q: "What payment methods do you accept?",
|
|
75
|
+
a: "Mobile Money (MTN, Telecel, AirtelTigo), Visa/Mastercard, instalments via Fidelity Bank, and cash on delivery for Accra orders.",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
q: "What's your return policy?",
|
|
79
|
+
a: "14 days, no questions asked, on any sealed product. For opened items, returns are accepted within 7 days subject to a 10% restocking fee. We can't accept returns on activated SIM-bundled phones once the SIM is registered.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
q: "Are products genuine?",
|
|
83
|
+
a: "Yes. We're an authorised dealer for every brand we stock. Every product ships with the manufacturer's serial number registered to your name — verify it on the brand's official portal anytime.",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
export default function FaqPage() {
|
|
90
|
+
return (
|
|
91
|
+
<article className="max-w-3xl mx-auto px-8 py-16">
|
|
92
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
|
|
93
|
+
Support
|
|
94
|
+
</p>
|
|
95
|
+
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-10 -tracking-[0.025em]">
|
|
96
|
+
Questions, answered.
|
|
97
|
+
</h1>
|
|
98
|
+
<div className="space-y-12">
|
|
99
|
+
{SECTIONS.map((section) => (
|
|
100
|
+
<section key={section.title}>
|
|
101
|
+
<h2 className="text-2xl font-semibold mb-5 -tracking-[0.02em]">
|
|
102
|
+
{section.title}
|
|
103
|
+
</h2>
|
|
104
|
+
<dl className="space-y-6">
|
|
105
|
+
{section.items.map((item) => (
|
|
106
|
+
<div key={item.q}>
|
|
107
|
+
<dt className="font-semibold text-foreground mb-1.5">{item.q}</dt>
|
|
108
|
+
<dd className="text-muted-foreground leading-relaxed">{item.a}</dd>
|
|
109
|
+
</div>
|
|
110
|
+
))}
|
|
111
|
+
</dl>
|
|
112
|
+
</section>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
<p className="mt-12 pt-8 border-t border-border text-sm text-muted-foreground">
|
|
116
|
+
Still stuck? Email{" "}
|
|
117
|
+
<a
|
|
118
|
+
href="mailto:support@currentselectronics.test"
|
|
119
|
+
className="text-primary font-semibold hover:underline"
|
|
120
|
+
>
|
|
121
|
+
support@currentselectronics.test
|
|
122
|
+
</a>{" "}
|
|
123
|
+
and a real human will reply within 24 hours.
|
|
124
|
+
</p>
|
|
125
|
+
</article>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Scan the SDK's bundled JS so utility classes used inside its components
|
|
4
|
+
land in the compiled stylesheet alongside our own. */
|
|
5
|
+
@source "../node_modules/@cimplify/sdk/dist";
|
|
6
|
+
|
|
7
|
+
/* Currents Electronics palette — cool navy chrome, electric blue accent.
|
|
8
|
+
Modern retail aesthetic; tweak these tokens to retheme everything. */
|
|
9
|
+
@theme {
|
|
10
|
+
--color-background: oklch(0.99 0.005 240);
|
|
11
|
+
--color-foreground: oklch(0.2 0.03 250);
|
|
12
|
+
--color-card: oklch(1 0 0);
|
|
13
|
+
--color-card-foreground: oklch(0.2 0.03 250);
|
|
14
|
+
--color-popover: oklch(1 0 0);
|
|
15
|
+
--color-popover-foreground: oklch(0.2 0.03 250);
|
|
16
|
+
--color-primary: oklch(0.55 0.22 255);
|
|
17
|
+
--color-primary-foreground: oklch(0.99 0 0);
|
|
18
|
+
--color-secondary: oklch(0.95 0.02 250);
|
|
19
|
+
--color-secondary-foreground: oklch(0.25 0.05 250);
|
|
20
|
+
--color-muted: oklch(0.96 0.01 250);
|
|
21
|
+
--color-muted-foreground: oklch(0.5 0.03 250);
|
|
22
|
+
--color-accent: oklch(0.92 0.07 240);
|
|
23
|
+
--color-accent-foreground: oklch(0.25 0.1 250);
|
|
24
|
+
--color-destructive: oklch(0.58 0.24 27);
|
|
25
|
+
--color-destructive-foreground: oklch(0.99 0 0);
|
|
26
|
+
--color-border: oklch(0.92 0.01 250);
|
|
27
|
+
--color-input: oklch(0.94 0.01 250);
|
|
28
|
+
--color-ring: oklch(0.55 0.22 255);
|
|
29
|
+
--radius: 0.5rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@layer base {
|
|
33
|
+
html, body {
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
background: var(--color-background);
|
|
37
|
+
color: var(--color-foreground);
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
-moz-osx-font-smoothing: grayscale;
|
|
40
|
+
}
|
|
41
|
+
a { color: inherit; text-decoration: none; }
|
|
42
|
+
h1, h2, h3, h4 {
|
|
43
|
+
font-family: var(--font-sans);
|
|
44
|
+
letter-spacing: -0.02em;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Inter, JetBrains_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
import { Providers } from "@/components/providers";
|
|
5
|
+
import { Header } from "@/components/header";
|
|
6
|
+
import { Footer } from "@/components/footer";
|
|
7
|
+
import { ProductModal } from "@/components/product-modal";
|
|
8
|
+
import { Suspense } from "react";
|
|
9
|
+
|
|
10
|
+
const inter = Inter({
|
|
11
|
+
subsets: ["latin"],
|
|
12
|
+
variable: "--font-sans",
|
|
13
|
+
display: "swap",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const mono = JetBrains_Mono({
|
|
17
|
+
subsets: ["latin"],
|
|
18
|
+
variable: "--font-mono",
|
|
19
|
+
display: "swap",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const metadata: Metadata = {
|
|
23
|
+
title: "Currents Electronics",
|
|
24
|
+
description:
|
|
25
|
+
"The latest laptops, phones, audio, and accessories — curated, in-stock, shipped from Accra to Africa.",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
29
|
+
return (
|
|
30
|
+
<html lang="en" className={`${inter.variable} ${mono.variable}`}>
|
|
31
|
+
<body
|
|
32
|
+
suppressHydrationWarning
|
|
33
|
+
className="min-h-screen flex flex-col bg-background text-foreground font-sans"
|
|
34
|
+
>
|
|
35
|
+
<Providers>
|
|
36
|
+
<Header />
|
|
37
|
+
<main className="flex-1 pb-12 w-full">{children}</main>
|
|
38
|
+
<Footer />
|
|
39
|
+
<Suspense fallback={null}>
|
|
40
|
+
<ProductModal />
|
|
41
|
+
</Suspense>
|
|
42
|
+
</Providers>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
export default async function OrderPage({ params }: { params: Promise<{ id: string }> }) {
|
|
4
|
+
const { id } = await params;
|
|
5
|
+
return (
|
|
6
|
+
<section className="max-w-2xl mx-auto px-8 py-20 text-center">
|
|
7
|
+
<h1 className="text-3xl mt-0 mb-3 font-bold -tracking-[0.025em]">
|
|
8
|
+
Order confirmed.
|
|
9
|
+
</h1>
|
|
10
|
+
<p className="text-muted-foreground">
|
|
11
|
+
Order <code className="font-mono text-foreground">{id}</code> — you'll
|
|
12
|
+
get an SMS with tracking within 30 minutes.
|
|
13
|
+
</p>
|
|
14
|
+
<p className="mt-6">
|
|
15
|
+
<Link
|
|
16
|
+
href="/"
|
|
17
|
+
className="inline-block px-6 py-3 rounded-md bg-primary text-primary-foreground font-semibold text-sm transition-colors hover:bg-primary/90"
|
|
18
|
+
>
|
|
19
|
+
Continue shopping
|
|
20
|
+
</Link>
|
|
21
|
+
</p>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
4
|
+
import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
5
|
+
import { Hero } from "@/components/hero";
|
|
6
|
+
import { CollectionStrip } from "@/components/collection-strip";
|
|
7
|
+
import { CategoryGrid } from "@/components/category-grid";
|
|
8
|
+
|
|
9
|
+
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.",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
async function getHomeData() {
|
|
16
|
+
"use cache";
|
|
17
|
+
cacheTag(tags.collections(), tags.categories(), tags.products());
|
|
18
|
+
cacheLife("hours");
|
|
19
|
+
|
|
20
|
+
const client = getServerClient();
|
|
21
|
+
const [colRes, catRes] = await Promise.all([
|
|
22
|
+
client.catalogue.getCollections(),
|
|
23
|
+
client.catalogue.getCategories(),
|
|
24
|
+
]);
|
|
25
|
+
const collections = colRes.ok ? colRes.value : [];
|
|
26
|
+
const categories = catRes.ok ? catRes.value : [];
|
|
27
|
+
|
|
28
|
+
const collectionsWithProducts = await Promise.all(
|
|
29
|
+
collections.map(async (col) => {
|
|
30
|
+
const r = await client.catalogue.getCollectionProducts(col.id);
|
|
31
|
+
const items = r.ok
|
|
32
|
+
? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
|
|
33
|
+
: [];
|
|
34
|
+
return { collection: col, products: items };
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
collections: collectionsWithProducts.filter((x) => x.products.length > 0),
|
|
40
|
+
categories,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default async function HomePage() {
|
|
45
|
+
const { collections, categories } = await getHomeData();
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
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."
|
|
53
|
+
/>
|
|
54
|
+
{collections.map(({ collection, products }) => (
|
|
55
|
+
<Suspense
|
|
56
|
+
key={collection.id}
|
|
57
|
+
fallback={<StripSkeleton title={collection.name} />}
|
|
58
|
+
>
|
|
59
|
+
<CollectionStrip
|
|
60
|
+
collection={collection}
|
|
61
|
+
products={products}
|
|
62
|
+
collectionHref={`/collections/${collection.slug}`}
|
|
63
|
+
/>
|
|
64
|
+
</Suspense>
|
|
65
|
+
))}
|
|
66
|
+
<Suspense fallback={<CategoryGridSkeleton />}>
|
|
67
|
+
<CategoryGrid categories={categories} />
|
|
68
|
+
</Suspense>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
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="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">
|
|
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,101 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Privacy Policy — Currents Electronics",
|
|
5
|
+
description:
|
|
6
|
+
"How Currents Electronics 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-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
|
|
13
|
+
Privacy
|
|
14
|
+
</p>
|
|
15
|
+
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
|
|
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="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'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'll email
|
|
95
|
+
customers on our marketing list and update the "last updated"
|
|
96
|
+
date above.
|
|
97
|
+
</p>
|
|
98
|
+
</section>
|
|
99
|
+
</article>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
3
|
+
import { getServerClient, tags } from "@cimplify/sdk/server";
|
|
4
|
+
import { ShopClient } from "./shop-client";
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: "Shop — Akua's Bakery",
|
|
8
|
+
description: "Browse the full catalogue: breads, pastries, cakes, drinks, merch, and more.",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
async function getShopData() {
|
|
12
|
+
"use cache";
|
|
13
|
+
cacheTag(tags.products(), tags.categories());
|
|
14
|
+
cacheLife("hours");
|
|
15
|
+
|
|
16
|
+
const client = getServerClient();
|
|
17
|
+
const [p, c] = await Promise.all([
|
|
18
|
+
client.catalogue.getProducts({ limit: 50 }),
|
|
19
|
+
client.catalogue.getCategories(),
|
|
20
|
+
]);
|
|
21
|
+
return {
|
|
22
|
+
products: p.ok ? p.value.items : [],
|
|
23
|
+
categories: c.ok ? c.value : [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default async function ShopPage() {
|
|
28
|
+
const { products, categories } = await getShopData();
|
|
29
|
+
return <ShopClient products={products} categories={categories} />;
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CataloguePage } from "@cimplify/sdk/react";
|
|
4
|
+
import type { Category, Product } from "@cimplify/sdk";
|
|
5
|
+
import { StoreProductCard } from "@/components/store-product-card";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client island for the shop page. Server-side fetches all products and
|
|
9
|
+
* categories (cached via `'use cache'` in `app/shop/page.tsx`), then hands
|
|
10
|
+
* them to `<CataloguePage>` which owns the interactive filter / sort state.
|
|
11
|
+
*/
|
|
12
|
+
export function ShopClient({
|
|
13
|
+
products,
|
|
14
|
+
categories,
|
|
15
|
+
}: {
|
|
16
|
+
products: Product[];
|
|
17
|
+
categories: Category[];
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<CataloguePage
|
|
21
|
+
title="The Menu"
|
|
22
|
+
products={products}
|
|
23
|
+
categories={categories}
|
|
24
|
+
renderCard={(p) => <StoreProductCard product={p} />}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|