@cimplify/sdk 0.39.1 → 0.40.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/dispatcher.mjs +39 -5
- package/dist/mock/cli.mjs +30 -6
- package/dist/mock/library.js +30 -6
- package/dist/mock/library.mjs +30 -6
- package/dist/mock/msw.js +30 -6
- package/dist/mock/msw.mjs +30 -6
- 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 +129 -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 +129 -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 +239 -0
- package/templates/storefront-retail/app/privacy/page.tsx +101 -0
- package/templates/storefront-retail/app/shop/page.tsx +63 -0
- package/templates/storefront-retail/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-retail/app/terms/page.tsx +117 -0
- package/templates/storefront-retail/components/brand-marquee.tsx +36 -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/category-tiles.tsx +104 -0
- package/templates/storefront-retail/components/collection-strip.tsx +45 -0
- package/templates/storefront-retail/components/feature-hero.tsx +83 -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/newsletter.tsx +52 -0
- package/templates/storefront-retail/components/product-modal.tsx +105 -0
- package/templates/storefront-retail/components/promo-banner.tsx +44 -0
- package/templates/storefront-retail/components/providers.tsx +31 -0
- package/templates/storefront-retail/components/section-heading.tsx +37 -0
- package/templates/storefront-retail/components/store-product-card.tsx +83 -0
- package/templates/storefront-retail/components/trade-in-cta.tsx +60 -0
- package/templates/storefront-retail/components/trust-bar.tsx +26 -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,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,239 @@
|
|
|
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 { FeatureHero } from "@/components/feature-hero";
|
|
6
|
+
import { CategoryTiles } from "@/components/category-tiles";
|
|
7
|
+
import { CollectionStrip } from "@/components/collection-strip";
|
|
8
|
+
import { PromoBanner } from "@/components/promo-banner";
|
|
9
|
+
import { TrustBar } from "@/components/trust-bar";
|
|
10
|
+
import { BrandMarquee } from "@/components/brand-marquee";
|
|
11
|
+
import { TradeInCta } from "@/components/trade-in-cta";
|
|
12
|
+
import { Newsletter } from "@/components/newsletter";
|
|
13
|
+
import { SectionHeading } from "@/components/section-heading";
|
|
14
|
+
import { StoreProductCard } from "@/components/store-product-card";
|
|
15
|
+
|
|
16
|
+
export const metadata: Metadata = {
|
|
17
|
+
title: "Currents Electronics — Tech, in stock, in Accra",
|
|
18
|
+
description:
|
|
19
|
+
"Authorised dealer for Apple, Samsung, Sony, Bose. Same-day delivery, two-year warranty, instalment payments — across Accra and West Africa.",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface CollectionWithProducts {
|
|
23
|
+
collection: { id: string; slug: string; name: string; description?: string };
|
|
24
|
+
products: Product[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getHomeData() {
|
|
28
|
+
"use cache";
|
|
29
|
+
cacheTag(tags.collections(), tags.categories(), tags.products());
|
|
30
|
+
cacheLife("hours");
|
|
31
|
+
|
|
32
|
+
const client = getServerClient();
|
|
33
|
+
const [colRes, catRes, productsRes] = await Promise.all([
|
|
34
|
+
client.catalogue.getCollections(),
|
|
35
|
+
client.catalogue.getCategories(),
|
|
36
|
+
client.catalogue.getProducts({ limit: 12 }),
|
|
37
|
+
]);
|
|
38
|
+
const collections = colRes.ok ? colRes.value : [];
|
|
39
|
+
const categories = catRes.ok ? catRes.value : [];
|
|
40
|
+
const allProducts = productsRes.ok ? productsRes.value.items : [];
|
|
41
|
+
|
|
42
|
+
const collectionsWithProducts: CollectionWithProducts[] = await Promise.all(
|
|
43
|
+
collections.map(async (col) => {
|
|
44
|
+
const r = await client.catalogue.getCollectionProducts(col.id);
|
|
45
|
+
const items = r.ok
|
|
46
|
+
? ((r.value as { items?: Product[] }).items ?? (r.value as Product[]))
|
|
47
|
+
: [];
|
|
48
|
+
return { collection: col, products: items };
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
collections: collectionsWithProducts.filter((x) => x.products.length > 0),
|
|
54
|
+
categories,
|
|
55
|
+
featured: allProducts.slice(0, 4),
|
|
56
|
+
newArrivals: allProducts.slice(4, 12),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const TRUST_ITEMS = [
|
|
61
|
+
{
|
|
62
|
+
label: "Delivery",
|
|
63
|
+
value: "Same-day in Accra",
|
|
64
|
+
description: "Orders before 4pm, free over GH₵500.",
|
|
65
|
+
icon: (
|
|
66
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
67
|
+
<path d="M3 7h12v8H3zM15 10h4l3 3v2h-7z" strokeLinejoin="round" />
|
|
68
|
+
<circle cx="7" cy="17" r="1.5" />
|
|
69
|
+
<circle cx="18" cy="17" r="1.5" />
|
|
70
|
+
</svg>
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: "Warranty",
|
|
75
|
+
value: "2 years on everything",
|
|
76
|
+
description: "1yr manufacturer + 1yr Currents.",
|
|
77
|
+
icon: (
|
|
78
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
79
|
+
<path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" strokeLinejoin="round" />
|
|
80
|
+
<path d="M9 12l2 2 4-4" strokeLinecap="round" strokeLinejoin="round" />
|
|
81
|
+
</svg>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: "Payment",
|
|
86
|
+
value: "Pay in 3, interest-free",
|
|
87
|
+
description: "Or 6/12 months via Fidelity Bank.",
|
|
88
|
+
icon: (
|
|
89
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
90
|
+
<rect x="3" y="6" width="18" height="12" rx="2" />
|
|
91
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
92
|
+
</svg>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: "Genuine",
|
|
97
|
+
value: "Authorised dealer",
|
|
98
|
+
description: "Direct from Apple, Samsung, Sony, Bose.",
|
|
99
|
+
icon: (
|
|
100
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
101
|
+
<circle cx="12" cy="12" r="9" />
|
|
102
|
+
<path d="M8 12l3 3 5-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
103
|
+
</svg>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const HERO_IMAGE =
|
|
109
|
+
"https://images.unsplash.com/photo-1592899677977-9c10ca588bbd?w=1200&h=1200&fit=crop&auto=format&q=85";
|
|
110
|
+
|
|
111
|
+
export default async function HomePage() {
|
|
112
|
+
const { collections, categories, featured, newArrivals } = await getHomeData();
|
|
113
|
+
const heroProduct = featured[0];
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<FeatureHero
|
|
118
|
+
eyebrow="Featured · in stock"
|
|
119
|
+
badge="Just landed"
|
|
120
|
+
title={
|
|
121
|
+
<>
|
|
122
|
+
Built for the way<br />
|
|
123
|
+
you actually work.
|
|
124
|
+
</>
|
|
125
|
+
}
|
|
126
|
+
description={
|
|
127
|
+
heroProduct?.description ??
|
|
128
|
+
"Apple, Samsung, Sony, Bose — direct from the source, with a two-year warranty and same-day Accra delivery on every order."
|
|
129
|
+
}
|
|
130
|
+
primaryCta={{
|
|
131
|
+
label: heroProduct ? `Shop ${heroProduct.name}` : "Shop the menu",
|
|
132
|
+
href: heroProduct ? `?product=${heroProduct.slug ?? heroProduct.id}` : "/shop",
|
|
133
|
+
}}
|
|
134
|
+
secondaryCta={{ label: "See deals", href: "/categories/deals" }}
|
|
135
|
+
imageUrl={heroProduct?.image_url ?? heroProduct?.images?.[0] ?? HERO_IMAGE}
|
|
136
|
+
imageAlt={heroProduct?.name ?? "Featured product"}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<TrustBar items={TRUST_ITEMS} />
|
|
140
|
+
|
|
141
|
+
<Suspense fallback={<CategoryTilesSkeleton />}>
|
|
142
|
+
<CategoryTiles categories={categories} />
|
|
143
|
+
</Suspense>
|
|
144
|
+
|
|
145
|
+
<PromoBanner
|
|
146
|
+
badge="Limited · ends Sunday"
|
|
147
|
+
title="Trade in your old phone, get up to GH₵3,500 off."
|
|
148
|
+
body="Send us your last-gen iPhone or Galaxy. We'll quote within 48 hours and credit the value toward your next upgrade."
|
|
149
|
+
cta={{ label: "Start trade-in", href: "/about" }}
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
153
|
+
<SectionHeading
|
|
154
|
+
eyebrow="Just dropped"
|
|
155
|
+
title="New this week."
|
|
156
|
+
description="Hand-picked launches and freshly restocked staples."
|
|
157
|
+
link={{ label: "Browse all", href: "/shop" }}
|
|
158
|
+
/>
|
|
159
|
+
<Suspense fallback={<GridSkeleton count={4} />}>
|
|
160
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
161
|
+
{newArrivals.slice(0, 4).map((p) => (
|
|
162
|
+
<StoreProductCard key={p.id} product={p} />
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
</Suspense>
|
|
166
|
+
</section>
|
|
167
|
+
|
|
168
|
+
<BrandMarquee />
|
|
169
|
+
|
|
170
|
+
{collections.map(({ collection, products }) => (
|
|
171
|
+
<Suspense
|
|
172
|
+
key={collection.id}
|
|
173
|
+
fallback={<StripSkeleton title={collection.name} />}
|
|
174
|
+
>
|
|
175
|
+
<CollectionStrip
|
|
176
|
+
collection={collection}
|
|
177
|
+
products={products}
|
|
178
|
+
collectionHref={`/collections/${collection.slug}`}
|
|
179
|
+
/>
|
|
180
|
+
</Suspense>
|
|
181
|
+
))}
|
|
182
|
+
|
|
183
|
+
<TradeInCta />
|
|
184
|
+
|
|
185
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
186
|
+
<SectionHeading
|
|
187
|
+
eyebrow="Best sellers"
|
|
188
|
+
title="What everyone's buying."
|
|
189
|
+
link={{ label: "See more", href: "/shop" }}
|
|
190
|
+
/>
|
|
191
|
+
<Suspense fallback={<GridSkeleton count={4} />}>
|
|
192
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
193
|
+
{newArrivals.slice(4, 8).map((p) => (
|
|
194
|
+
<StoreProductCard key={p.id} product={p} />
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
</Suspense>
|
|
198
|
+
</section>
|
|
199
|
+
|
|
200
|
+
<Newsletter />
|
|
201
|
+
</>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function CategoryTilesSkeleton() {
|
|
206
|
+
return (
|
|
207
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
208
|
+
<div className="h-8 w-64 bg-muted rounded mb-8 animate-pulse" />
|
|
209
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
|
|
210
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
211
|
+
<div key={i} className="h-40 bg-muted rounded-2xl animate-pulse" />
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
</section>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function GridSkeleton({ count }: { count: number }) {
|
|
219
|
+
return (
|
|
220
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
221
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
222
|
+
<div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function StripSkeleton({ title }: { title: string }) {
|
|
229
|
+
return (
|
|
230
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-12">
|
|
231
|
+
<h2 className="text-[26px] font-semibold m-0 mb-5 -tracking-[0.02em]">{title}</h2>
|
|
232
|
+
<div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto pb-2">
|
|
233
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
234
|
+
<div key={i} className="aspect-square bg-muted rounded-2xl animate-pulse" />
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
</section>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -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,63 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
4
|
+
import { getServerClient, tags } from "@cimplify/sdk/server";
|
|
5
|
+
import { ShopClient } from "./shop-client";
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: "Shop — Currents Electronics",
|
|
9
|
+
description:
|
|
10
|
+
"Browse every laptop, phone, audio device, accessory, and gaming product we stock. Authorised dealer pricing, 2-year warranty.",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function getShopData() {
|
|
14
|
+
"use cache";
|
|
15
|
+
cacheTag(tags.products(), tags.categories());
|
|
16
|
+
cacheLife("hours");
|
|
17
|
+
|
|
18
|
+
const client = getServerClient();
|
|
19
|
+
const [p, c] = await Promise.all([
|
|
20
|
+
client.catalogue.getProducts({ limit: 50 }),
|
|
21
|
+
client.catalogue.getCategories(),
|
|
22
|
+
]);
|
|
23
|
+
return {
|
|
24
|
+
products: p.ok ? p.value.items : [],
|
|
25
|
+
categories: c.ok ? c.value : [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function ShopPage() {
|
|
30
|
+
const { products, categories } = await getShopData();
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<section className="bg-foreground text-background relative overflow-hidden">
|
|
34
|
+
<div className="absolute inset-0 opacity-[0.04] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:32px_32px]" />
|
|
35
|
+
<div className="relative max-w-7xl mx-auto px-6 sm:px-8 py-12 sm:py-14">
|
|
36
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.2em] text-background/60 mb-2">
|
|
37
|
+
Catalogue · {products.length} products
|
|
38
|
+
</p>
|
|
39
|
+
<h1 className="text-[clamp(2rem,4vw,3rem)] font-bold m-0 -tracking-[0.025em] leading-[1.05]">
|
|
40
|
+
Every product we stock.
|
|
41
|
+
</h1>
|
|
42
|
+
<p className="mt-3 max-w-xl text-base text-background/75">
|
|
43
|
+
Filter, sort, search. Authorised dealer pricing, two-year warranty,
|
|
44
|
+
same-day Accra delivery on every order.
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
<Suspense
|
|
49
|
+
fallback={
|
|
50
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-8 py-10">
|
|
51
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
52
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
53
|
+
<div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
<ShopClient products={products} categories={categories} />
|
|
60
|
+
</Suspense>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
* The page hero (in `app/shop/page.tsx`) already provides the title; we
|
|
13
|
+
* pass an empty `title` and override the SDK heading via className so the
|
|
14
|
+
* page reads as one continuous design.
|
|
15
|
+
*/
|
|
16
|
+
export function ShopClient({
|
|
17
|
+
products,
|
|
18
|
+
categories,
|
|
19
|
+
}: {
|
|
20
|
+
products: Product[];
|
|
21
|
+
categories: Category[];
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<CataloguePage
|
|
25
|
+
title="All products"
|
|
26
|
+
products={products}
|
|
27
|
+
categories={categories}
|
|
28
|
+
renderCard={(p) => <StoreProductCard product={p} />}
|
|
29
|
+
className="max-w-7xl mx-auto px-6 sm:px-8 py-10 sm:py-12"
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Terms of Service — Currents Electronics",
|
|
5
|
+
description: "The rules of the road for ordering from Currents Electronics.",
|
|
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-mono uppercase tracking-[0.16em] text-primary mb-2 not-prose">
|
|
12
|
+
Terms of service
|
|
13
|
+
</p>
|
|
14
|
+
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-bold mb-2 -tracking-[0.025em]">
|
|
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="text-2xl font-semibold mt-0 -tracking-[0.02em]">
|
|
23
|
+
1. Who we are
|
|
24
|
+
</h2>
|
|
25
|
+
<p>
|
|
26
|
+
Currents Electronics ("we", "us") is a consumer
|
|
27
|
+
electronics retailer based at Atomic Junction, East Legon, Accra,
|
|
28
|
+
Ghana. By placing an order, you ("you", "customer")
|
|
29
|
+
agree to these terms.
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
33
|
+
2. Authorised dealer status
|
|
34
|
+
</h2>
|
|
35
|
+
<p>
|
|
36
|
+
We are authorised dealers for the brands listed on our About page.
|
|
37
|
+
Every product is sourced direct from the manufacturer or a certified
|
|
38
|
+
distributor. Serial numbers can be verified on the brand's
|
|
39
|
+
official portal at any time.
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
43
|
+
3. Pricing and payment
|
|
44
|
+
</h2>
|
|
45
|
+
<p>
|
|
46
|
+
Prices are in Ghana Cedis (GH₵) and include VAT where applicable.
|
|
47
|
+
Payment is due at checkout via Mobile Money, card, or instalments via
|
|
48
|
+
our partner bank. Cash on delivery is available for Accra orders only.
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
52
|
+
4. Warranty
|
|
53
|
+
</h2>
|
|
54
|
+
<p>
|
|
55
|
+
Every product carries a two-year warranty: the standard manufacturer
|
|
56
|
+
warranty in year one, plus a Currents-backed extension in year two.
|
|
57
|
+
Coverage includes manufacturing defects and battery degradation below
|
|
58
|
+
80%. Accidental damage, liquid damage, and unauthorised modifications
|
|
59
|
+
are excluded.
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
63
|
+
5. Returns
|
|
64
|
+
</h2>
|
|
65
|
+
<p>
|
|
66
|
+
Sealed products: 14 days, no questions asked. Opened products:
|
|
67
|
+
7 days, subject to a 10% restocking fee for non-defective items.
|
|
68
|
+
Activated SIM-bundled phones cannot be returned once the SIM is
|
|
69
|
+
registered. Custom orders and bulk corporate orders are
|
|
70
|
+
non-refundable except for defects.
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
74
|
+
6. Trade-in
|
|
75
|
+
</h2>
|
|
76
|
+
<p>
|
|
77
|
+
Trade-in quotes are estimates only. Final value is determined after
|
|
78
|
+
inspection by our refurbishment partner. We reserve the right to
|
|
79
|
+
revise the quote, and you may decline the revised offer and receive
|
|
80
|
+
your device back at our cost.
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
84
|
+
7. Liability
|
|
85
|
+
</h2>
|
|
86
|
+
<p>
|
|
87
|
+
To the maximum extent permitted by Ghanaian law, our liability for
|
|
88
|
+
any claim arising from this website or your order is limited to the
|
|
89
|
+
value of the order. We're not liable for indirect or
|
|
90
|
+
consequential losses, including data loss.
|
|
91
|
+
</p>
|
|
92
|
+
|
|
93
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
94
|
+
8. Governing law
|
|
95
|
+
</h2>
|
|
96
|
+
<p>
|
|
97
|
+
These terms are governed by the laws of Ghana. Disputes will be
|
|
98
|
+
resolved in the courts of Greater Accra.
|
|
99
|
+
</p>
|
|
100
|
+
|
|
101
|
+
<h2 className="text-2xl font-semibold -tracking-[0.02em]">
|
|
102
|
+
9. Contact
|
|
103
|
+
</h2>
|
|
104
|
+
<p>
|
|
105
|
+
Questions? Email{" "}
|
|
106
|
+
<a
|
|
107
|
+
href="mailto:hello@currentselectronics.test"
|
|
108
|
+
className="text-primary font-semibold hover:underline"
|
|
109
|
+
>
|
|
110
|
+
hello@currentselectronics.test
|
|
111
|
+
</a>
|
|
112
|
+
.
|
|
113
|
+
</p>
|
|
114
|
+
</section>
|
|
115
|
+
</article>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const BRANDS = [
|
|
2
|
+
"Apple",
|
|
3
|
+
"Samsung",
|
|
4
|
+
"Sony",
|
|
5
|
+
"Bose",
|
|
6
|
+
"Logitech",
|
|
7
|
+
"Anker",
|
|
8
|
+
"JBL",
|
|
9
|
+
"Microsoft",
|
|
10
|
+
"Lenovo",
|
|
11
|
+
"DJI",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Brand authority strip. Wordmark-only (avoids licensing issues with real
|
|
16
|
+
* logos), JetBrains Mono for the brand-as-typography aesthetic.
|
|
17
|
+
*/
|
|
18
|
+
export function BrandMarquee() {
|
|
19
|
+
return (
|
|
20
|
+
<section className="border-y border-border bg-background py-8 sm:py-10 overflow-hidden">
|
|
21
|
+
<p className="text-center text-[11px] font-mono uppercase tracking-[0.2em] text-muted-foreground mb-6">
|
|
22
|
+
Authorised dealer · genuine stock · 2-year warranty
|
|
23
|
+
</p>
|
|
24
|
+
<div className="flex items-center justify-around gap-10 sm:gap-14 flex-wrap px-6">
|
|
25
|
+
{BRANDS.map((b) => (
|
|
26
|
+
<span
|
|
27
|
+
key={b}
|
|
28
|
+
className="text-[clamp(1.25rem,2vw,1.75rem)] font-semibold text-muted-foreground hover:text-foreground transition-colors -tracking-[0.025em] opacity-70 hover:opacity-100"
|
|
29
|
+
>
|
|
30
|
+
{b}
|
|
31
|
+
</span>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
);
|
|
36
|
+
}
|