@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.
Files changed (83) hide show
  1. package/dist/dispatcher.mjs +39 -5
  2. package/dist/mock/cli.mjs +30 -6
  3. package/dist/mock/library.js +30 -6
  4. package/dist/mock/library.mjs +30 -6
  5. package/dist/mock/msw.js +30 -6
  6. package/dist/mock/msw.mjs +30 -6
  7. package/dist/styles.css +1 -1
  8. package/package.json +1 -1
  9. package/templates/{storefront → storefront-bakery}/README.md +8 -1
  10. package/templates/{storefront → storefront-bakery}/package.json +1 -1
  11. package/templates/storefront-retail/.env.example +11 -0
  12. package/templates/storefront-retail/README.md +77 -0
  13. package/templates/storefront-retail/app/about/page.tsx +71 -0
  14. package/templates/storefront-retail/app/cart/page.tsx +9 -0
  15. package/templates/storefront-retail/app/categories/[slug]/listing-client.tsx +19 -0
  16. package/templates/storefront-retail/app/categories/[slug]/page.tsx +129 -0
  17. package/templates/storefront-retail/app/checkout/page.tsx +17 -0
  18. package/templates/storefront-retail/app/collections/[slug]/listing-client.tsx +20 -0
  19. package/templates/storefront-retail/app/collections/[slug]/page.tsx +129 -0
  20. package/templates/storefront-retail/app/faq/page.tsx +127 -0
  21. package/templates/storefront-retail/app/globals.css +47 -0
  22. package/templates/storefront-retail/app/layout.tsx +46 -0
  23. package/templates/storefront-retail/app/orders/[id]/page.tsx +24 -0
  24. package/templates/storefront-retail/app/page.tsx +239 -0
  25. package/templates/storefront-retail/app/privacy/page.tsx +101 -0
  26. package/templates/storefront-retail/app/shop/page.tsx +63 -0
  27. package/templates/storefront-retail/app/shop/shop-client.tsx +32 -0
  28. package/templates/storefront-retail/app/terms/page.tsx +117 -0
  29. package/templates/storefront-retail/components/brand-marquee.tsx +36 -0
  30. package/templates/storefront-retail/components/cart-pill.tsx +32 -0
  31. package/templates/storefront-retail/components/category-grid.tsx +28 -0
  32. package/templates/storefront-retail/components/category-tiles.tsx +104 -0
  33. package/templates/storefront-retail/components/collection-strip.tsx +45 -0
  34. package/templates/storefront-retail/components/feature-hero.tsx +83 -0
  35. package/templates/storefront-retail/components/footer.tsx +188 -0
  36. package/templates/storefront-retail/components/header.tsx +47 -0
  37. package/templates/storefront-retail/components/hero.tsx +28 -0
  38. package/templates/storefront-retail/components/nav-link.tsx +20 -0
  39. package/templates/storefront-retail/components/newsletter.tsx +52 -0
  40. package/templates/storefront-retail/components/product-modal.tsx +105 -0
  41. package/templates/storefront-retail/components/promo-banner.tsx +44 -0
  42. package/templates/storefront-retail/components/providers.tsx +31 -0
  43. package/templates/storefront-retail/components/section-heading.tsx +37 -0
  44. package/templates/storefront-retail/components/store-product-card.tsx +83 -0
  45. package/templates/storefront-retail/components/trade-in-cta.tsx +60 -0
  46. package/templates/storefront-retail/components/trust-bar.tsx +26 -0
  47. package/templates/storefront-retail/lib/cart.ts +27 -0
  48. package/templates/storefront-retail/next.config.ts +40 -0
  49. package/templates/storefront-retail/package.json +28 -0
  50. package/templates/storefront-retail/postcss.config.mjs +7 -0
  51. package/templates/storefront-retail/tsconfig.json +23 -0
  52. package/templates/storefront/lib/server-client.ts +0 -27
  53. /package/templates/{storefront → storefront-bakery}/.env.example +0 -0
  54. /package/templates/{storefront → storefront-bakery}/app/about/page.tsx +0 -0
  55. /package/templates/{storefront → storefront-bakery}/app/cart/page.tsx +0 -0
  56. /package/templates/{storefront → storefront-bakery}/app/categories/[slug]/listing-client.tsx +0 -0
  57. /package/templates/{storefront → storefront-bakery}/app/categories/[slug]/page.tsx +0 -0
  58. /package/templates/{storefront → storefront-bakery}/app/checkout/page.tsx +0 -0
  59. /package/templates/{storefront → storefront-bakery}/app/collections/[slug]/listing-client.tsx +0 -0
  60. /package/templates/{storefront → storefront-bakery}/app/collections/[slug]/page.tsx +0 -0
  61. /package/templates/{storefront → storefront-bakery}/app/faq/page.tsx +0 -0
  62. /package/templates/{storefront → storefront-bakery}/app/globals.css +0 -0
  63. /package/templates/{storefront → storefront-bakery}/app/layout.tsx +0 -0
  64. /package/templates/{storefront → storefront-bakery}/app/orders/[id]/page.tsx +0 -0
  65. /package/templates/{storefront → storefront-bakery}/app/page.tsx +0 -0
  66. /package/templates/{storefront → storefront-bakery}/app/privacy/page.tsx +0 -0
  67. /package/templates/{storefront → storefront-bakery}/app/shop/page.tsx +0 -0
  68. /package/templates/{storefront → storefront-bakery}/app/shop/shop-client.tsx +0 -0
  69. /package/templates/{storefront → storefront-bakery}/app/terms/page.tsx +0 -0
  70. /package/templates/{storefront → storefront-bakery}/components/cart-pill.tsx +0 -0
  71. /package/templates/{storefront → storefront-bakery}/components/category-grid.tsx +0 -0
  72. /package/templates/{storefront → storefront-bakery}/components/collection-strip.tsx +0 -0
  73. /package/templates/{storefront → storefront-bakery}/components/footer.tsx +0 -0
  74. /package/templates/{storefront → storefront-bakery}/components/header.tsx +0 -0
  75. /package/templates/{storefront → storefront-bakery}/components/hero.tsx +0 -0
  76. /package/templates/{storefront → storefront-bakery}/components/nav-link.tsx +0 -0
  77. /package/templates/{storefront → storefront-bakery}/components/product-modal.tsx +0 -0
  78. /package/templates/{storefront → storefront-bakery}/components/providers.tsx +0 -0
  79. /package/templates/{storefront → storefront-bakery}/components/store-product-card.tsx +0 -0
  80. /package/templates/{storefront → storefront-bakery}/lib/cart.ts +0 -0
  81. /package/templates/{storefront → storefront-bakery}/next.config.ts +0 -0
  82. /package/templates/{storefront → storefront-bakery}/postcss.config.mjs +0 -0
  83. /package/templates/{storefront → storefront-bakery}/tsconfig.json +0 -0
@@ -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,28 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { CategoryGrid as SdkCategoryGrid } from "@cimplify/sdk/react";
5
+ import type { Category } from "@cimplify/sdk";
6
+
7
+ /**
8
+ * Homepage category tiles — defers to the SDK's <CategoryGrid/> for layout
9
+ * and accessibility, and routes selections to /categories/:slug.
10
+ */
11
+ export function CategoryGrid({ categories }: { categories?: Category[] }) {
12
+ const router = useRouter();
13
+ return (
14
+ <section className="max-w-7xl mx-auto px-8 pt-14">
15
+ <h2 className="font-serif text-[28px] font-semibold m-0 mb-5">Browse the menu</h2>
16
+ <SdkCategoryGrid
17
+ categories={categories}
18
+ onSelect={(c) => router.push(`/categories/${c.slug}`)}
19
+ classNames={{
20
+ item: "flex flex-col gap-1 p-6 bg-card border border-border rounded-2xl cursor-pointer text-left transition-all hover:border-primary hover:-translate-y-0.5",
21
+ name: "font-serif text-lg font-semibold text-foreground",
22
+ description: "text-xs text-muted-foreground",
23
+ count: "text-xs text-muted-foreground mt-1",
24
+ }}
25
+ />
26
+ </section>
27
+ );
28
+ }
@@ -0,0 +1,104 @@
1
+ import Link from "next/link";
2
+ import Image from "next/image";
3
+ import type { Category } from "@cimplify/sdk";
4
+
5
+ interface CategoryTilesProps {
6
+ categories: Category[];
7
+ }
8
+
9
+ const ICONS: Record<string, React.ReactNode> = {
10
+ phones: (
11
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
12
+ <rect x="6" y="2" width="12" height="20" rx="2.5" />
13
+ <line x1="12" y1="18" x2="12" y2="18" strokeWidth="2.5" strokeLinecap="round" />
14
+ </svg>
15
+ ),
16
+ laptops: (
17
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
18
+ <rect x="3" y="4" width="18" height="12" rx="1.5" />
19
+ <line x1="2" y1="20" x2="22" y2="20" strokeLinecap="round" />
20
+ </svg>
21
+ ),
22
+ audio: (
23
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
24
+ <path d="M3 14v-2a9 9 0 0 1 18 0v2" />
25
+ <rect x="2" y="14" width="5" height="6" rx="1.5" />
26
+ <rect x="17" y="14" width="5" height="6" rx="1.5" />
27
+ </svg>
28
+ ),
29
+ accessories: (
30
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
31
+ <rect x="2" y="6" width="14" height="10" rx="2" />
32
+ <path d="M16 12h4l2 2v0l-2 2h-4" />
33
+ </svg>
34
+ ),
35
+ gaming: (
36
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
37
+ <path d="M5 7h14a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3h-1l-2-3H8l-2 3H5a3 3 0 0 1-3-3v-4a3 3 0 0 1 3-3z" />
38
+ <line x1="9" y1="11" x2="9" y2="13" strokeLinecap="round" />
39
+ <line x1="8" y1="12" x2="10" y2="12" strokeLinecap="round" />
40
+ <circle cx="15" cy="11" r="0.75" fill="currentColor" />
41
+ <circle cx="17" cy="13" r="0.75" fill="currentColor" />
42
+ </svg>
43
+ ),
44
+ };
45
+
46
+ export function CategoryTiles({ categories }: CategoryTilesProps) {
47
+ if (categories.length === 0) return null;
48
+ return (
49
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
50
+ <div className="flex items-end justify-between gap-6 mb-8">
51
+ <div>
52
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
53
+ Shop the catalogue
54
+ </p>
55
+ <h2 className="text-[clamp(1.75rem,3vw,2.25rem)] font-bold -tracking-[0.025em]">
56
+ Pick your category.
57
+ </h2>
58
+ </div>
59
+ <Link
60
+ href="/shop"
61
+ className="text-sm font-semibold text-primary hover:underline whitespace-nowrap hidden sm:inline-flex items-center gap-1"
62
+ >
63
+ See everything
64
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
65
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
66
+ </svg>
67
+ </Link>
68
+ </div>
69
+ <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
70
+ {categories.map((c, i) => (
71
+ <Link
72
+ key={c.id}
73
+ href={`/categories/${c.slug}`}
74
+ className="group relative overflow-hidden rounded-2xl bg-card border border-border p-5 hover:border-primary hover:-translate-y-0.5 transition-all"
75
+ >
76
+ <div className="flex items-start justify-between mb-12">
77
+ <div className="grid place-items-center w-10 h-10 rounded-lg bg-primary/10 text-primary">
78
+ {ICONS[c.slug] ?? (
79
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
80
+ <rect x="3" y="3" width="18" height="18" rx="2" />
81
+ </svg>
82
+ )}
83
+ </div>
84
+ <span className="text-[10px] font-mono text-muted-foreground tabular-nums">
85
+ {String(i + 1).padStart(2, "0")}
86
+ </span>
87
+ </div>
88
+ <p className="text-base font-semibold mb-1 -tracking-[0.015em]">{c.name}</p>
89
+ {c.product_count != null && (
90
+ <p className="text-xs text-muted-foreground">
91
+ {c.product_count} {c.product_count === 1 ? "product" : "products"}
92
+ </p>
93
+ )}
94
+ <span className="absolute right-4 bottom-4 grid place-items-center w-7 h-7 rounded-full bg-foreground text-background opacity-0 group-hover:opacity-100 transition-opacity">
95
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
96
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
97
+ </svg>
98
+ </span>
99
+ </Link>
100
+ ))}
101
+ </div>
102
+ </section>
103
+ );
104
+ }
@@ -0,0 +1,45 @@
1
+ import Link from "next/link";
2
+ import type { Collection, Product } from "@cimplify/sdk";
3
+ import { StoreProductCard } from "./store-product-card";
4
+
5
+ interface CollectionStripProps {
6
+ collection: Collection;
7
+ products: Product[];
8
+ collectionHref?: string;
9
+ }
10
+
11
+ /**
12
+ * Horizontal strip of products under a collection title. Cards come from the
13
+ * SDK so every variant (Food, Bundle, Composite, Service, …) renders
14
+ * correctly; clicks open the shared URL-driven product modal.
15
+ */
16
+ export function CollectionStrip({ collection, products, collectionHref }: CollectionStripProps) {
17
+ if (products.length === 0) return null;
18
+ return (
19
+ <section className="max-w-7xl mx-auto px-8 pt-12">
20
+ <header className="grid grid-cols-[1fr_auto] items-end gap-4 mb-5">
21
+ <h2 className="font-serif text-[28px] font-semibold m-0">{collection.name}</h2>
22
+ {collectionHref && (
23
+ <Link
24
+ href={collectionHref}
25
+ className="text-[13px] font-semibold text-primary hover:underline"
26
+ >
27
+ See all →
28
+ </Link>
29
+ )}
30
+ {collection.description && (
31
+ <p className="col-span-full m-0 mt-1 text-sm text-muted-foreground">
32
+ {collection.description}
33
+ </p>
34
+ )}
35
+ </header>
36
+ <div className="grid grid-flow-col auto-cols-[minmax(220px,1fr)] gap-4 overflow-x-auto snap-x snap-mandatory pb-2">
37
+ {products.slice(0, 8).map((p) => (
38
+ <div key={p.id} className="snap-start">
39
+ <StoreProductCard product={p} />
40
+ </div>
41
+ ))}
42
+ </div>
43
+ </section>
44
+ );
45
+ }
@@ -0,0 +1,83 @@
1
+ import Link from "next/link";
2
+ import Image from "next/image";
3
+
4
+ interface FeatureHeroProps {
5
+ eyebrow: string;
6
+ title: React.ReactNode;
7
+ description: string;
8
+ primaryCta: { label: string; href: string };
9
+ secondaryCta?: { label: string; href: string };
10
+ imageUrl: string;
11
+ imageAlt: string;
12
+ badge?: string;
13
+ }
14
+
15
+ /**
16
+ * Apple-style split hero. Left: copy + CTAs. Right: large product image.
17
+ * Stacks to image-on-top on mobile.
18
+ */
19
+ export function FeatureHero({
20
+ eyebrow,
21
+ title,
22
+ description,
23
+ primaryCta,
24
+ secondaryCta,
25
+ imageUrl,
26
+ imageAlt,
27
+ badge,
28
+ }: FeatureHeroProps) {
29
+ return (
30
+ <section className="relative overflow-hidden bg-foreground text-background">
31
+ <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:40px_40px]" />
32
+ <div className="relative max-w-7xl mx-auto px-6 sm:px-8 py-16 sm:py-20 lg:py-24 grid grid-cols-1 lg:grid-cols-[1.1fr_1fr] gap-10 lg:gap-16 items-center">
33
+ <div>
34
+ {badge && (
35
+ <span className="inline-flex items-center gap-2 mb-5 px-3 py-1.5 rounded-full bg-primary/15 border border-primary/40 text-primary-foreground/95 text-[11px] font-mono uppercase tracking-[0.16em]">
36
+ <span className="grid place-items-center w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
37
+ {badge}
38
+ </span>
39
+ )}
40
+ <p className="text-[12px] font-mono uppercase tracking-[0.2em] text-background/60 mb-3">
41
+ {eyebrow}
42
+ </p>
43
+ <h1 className="text-[clamp(2.5rem,6vw,4.75rem)] font-bold m-0 mb-5 -tracking-[0.035em] leading-[1.02]">
44
+ {title}
45
+ </h1>
46
+ <p className="text-base sm:text-lg text-background/75 leading-relaxed max-w-xl">
47
+ {description}
48
+ </p>
49
+ <div className="flex flex-wrap items-center gap-3 mt-7">
50
+ <Link
51
+ href={primaryCta.href}
52
+ className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
53
+ >
54
+ {primaryCta.label}
55
+ <svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
56
+ <path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
57
+ </svg>
58
+ </Link>
59
+ {secondaryCta && (
60
+ <Link
61
+ href={secondaryCta.href}
62
+ className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-background/25 text-background hover:bg-background/10 transition-colors text-sm font-medium"
63
+ >
64
+ {secondaryCta.label}
65
+ </Link>
66
+ )}
67
+ </div>
68
+ </div>
69
+ <div className="relative aspect-[4/3] lg:aspect-square w-full rounded-2xl overflow-hidden bg-background/5 ring-1 ring-background/10">
70
+ <Image
71
+ src={imageUrl}
72
+ alt={imageAlt}
73
+ fill
74
+ sizes="(min-width: 1024px) 50vw, 100vw"
75
+ className="object-cover"
76
+ priority
77
+ />
78
+ <div className="absolute inset-0 bg-gradient-to-tr from-foreground/40 via-transparent to-transparent pointer-events-none" />
79
+ </div>
80
+ </div>
81
+ </section>
82
+ );
83
+ }
@@ -0,0 +1,188 @@
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/currentselectronics",
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: "X",
17
+ href: "https://x.com/currentselectronics",
18
+ svg: (
19
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
20
+ <path d="M18 2h3l-7.5 8.6L22 22h-6.6l-5-6.5L4 22H1l8-9.2L1.4 2H8l4.6 6 5.4-6z" />
21
+ </svg>
22
+ ),
23
+ },
24
+ {
25
+ label: "YouTube",
26
+ href: "https://youtube.com/@currentselectronics",
27
+ svg: (
28
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
29
+ <path d="M22.5 6.5a2.6 2.6 0 0 0-1.8-1.8C19 4.2 12 4.2 12 4.2s-7 0-8.7.5A2.6 2.6 0 0 0 1.5 6.5C1 8.2 1 12 1 12s0 3.8.5 5.5a2.6 2.6 0 0 0 1.8 1.8C5 19.8 12 19.8 12 19.8s7 0 8.7-.5a2.6 2.6 0 0 0 1.8-1.8c.5-1.7.5-5.5.5-5.5s0-3.8-.5-5.5zM10 15.5v-7l6 3.5-6 3.5z" />
30
+ </svg>
31
+ ),
32
+ },
33
+ {
34
+ label: "WhatsApp",
35
+ href: "https://wa.me/233244000000",
36
+ svg: (
37
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
38
+ <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" />
39
+ </svg>
40
+ ),
41
+ },
42
+ ];
43
+
44
+ const SITEMAP: { title: string; links: { label: string; href: string }[] }[] = [
45
+ {
46
+ title: "Shop",
47
+ links: [
48
+ { label: "All products", href: "/shop" },
49
+ { label: "Laptops", href: "/categories/laptops" },
50
+ { label: "Phones", href: "/categories/phones" },
51
+ { label: "Audio", href: "/categories/audio" },
52
+ { label: "Accessories", href: "/categories/accessories" },
53
+ { label: "Deals", href: "/categories/deals" },
54
+ ],
55
+ },
56
+ {
57
+ title: "Support",
58
+ links: [
59
+ { label: "Contact", href: "mailto:hello@currentselectronics.test" },
60
+ { label: "Warranty", href: "/faq#warranty--repairs" },
61
+ { label: "Returns", href: "/faq#payment--returns" },
62
+ { label: "Shipping", href: "/faq#ordering--delivery" },
63
+ ],
64
+ },
65
+ {
66
+ title: "Company",
67
+ links: [
68
+ { label: "About", href: "/about" },
69
+ { label: "Trade-in", href: "/about" },
70
+ { label: "Business sales", href: "mailto:business@currentselectronics.test" },
71
+ { label: "Careers", href: "/about" },
72
+ ],
73
+ },
74
+ {
75
+ title: "Legal",
76
+ links: [
77
+ { label: "Terms of Service", href: "/terms" },
78
+ { label: "Privacy Policy", href: "/privacy" },
79
+ ],
80
+ },
81
+ ];
82
+
83
+ export async function Footer() {
84
+ "use cache";
85
+ const year = new Date().getFullYear();
86
+ return (
87
+ <footer className="mt-16 border-t border-border bg-foreground text-background/80 text-sm">
88
+ <div className="max-w-7xl mx-auto px-6 sm:px-8 pt-12 pb-8">
89
+ <div className="grid gap-10 md:grid-cols-[1.4fr_repeat(4,1fr)]">
90
+ <div>
91
+ <div className="flex items-center gap-2.5 mb-4">
92
+ <span className="grid place-items-center w-8 h-8 rounded-md bg-primary text-primary-foreground text-[13px] font-bold font-mono">
93
+ C
94
+ </span>
95
+ <span className="text-background text-lg font-bold -tracking-[0.025em]">
96
+ Currents Electronics
97
+ </span>
98
+ </div>
99
+ <p className="leading-relaxed mb-4 max-w-sm">
100
+ Authorised dealer for Apple, Samsung, Sony, Bose. Same-day
101
+ delivery in Accra; nationwide via partner courier. Two-year
102
+ warranty on every product.
103
+ </p>
104
+ <address className="not-italic space-y-1">
105
+ <p className="m-0">Atomic Junction, East Legon, Accra</p>
106
+ <p className="m-0">
107
+ <a href="tel:+233244000000" className="hover:text-background transition-colors">
108
+ +233 244 000 000
109
+ </a>
110
+ </p>
111
+ <p className="m-0">
112
+ <a
113
+ href="mailto:hello@currentselectronics.test"
114
+ className="hover:text-background transition-colors"
115
+ >
116
+ hello@currentselectronics.test
117
+ </a>
118
+ </p>
119
+ <p className="m-0 text-xs">Mon–Sat · 9am–8pm</p>
120
+ </address>
121
+ <div className="flex items-center gap-3 mt-5">
122
+ {SOCIALS.map((s) => (
123
+ <a
124
+ key={s.label}
125
+ href={s.href}
126
+ aria-label={s.label}
127
+ target="_blank"
128
+ rel="noopener noreferrer"
129
+ className="inline-flex items-center justify-center w-9 h-9 rounded-md border border-background/20 hover:bg-primary hover:border-primary transition-colors"
130
+ >
131
+ {s.svg}
132
+ </a>
133
+ ))}
134
+ </div>
135
+ </div>
136
+ {SITEMAP.map((section) => (
137
+ <nav key={section.title} aria-labelledby={`footer-${section.title}`}>
138
+ <p
139
+ id={`footer-${section.title}`}
140
+ className="text-background font-mono mb-3 text-[11px] uppercase tracking-[0.12em]"
141
+ >
142
+ {section.title}
143
+ </p>
144
+ <ul className="space-y-2 m-0 p-0 list-none">
145
+ {section.links.map((link) => (
146
+ <li key={link.label}>
147
+ <Link
148
+ href={link.href}
149
+ className="hover:text-background transition-colors"
150
+ >
151
+ {link.label}
152
+ </Link>
153
+ </li>
154
+ ))}
155
+ </ul>
156
+ </nav>
157
+ ))}
158
+ </div>
159
+
160
+ <div className="mt-12 pt-6 border-t border-background/15 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs">
161
+ <p className="m-0">© {year} Currents Electronics. All rights reserved.</p>
162
+ <p className="m-0 inline-flex items-center gap-1.5">
163
+ <span className="opacity-70">Powered by</span>
164
+ <a
165
+ href="https://app.cimplify.io"
166
+ target="_blank"
167
+ rel="noopener noreferrer"
168
+ aria-label="Cimplify"
169
+ className="inline-flex items-center gap-1 text-background hover:text-primary transition-colors"
170
+ >
171
+ <span className="font-semibold tracking-tight">Cimplify</span>
172
+ <svg
173
+ viewBox="0 0 12 12"
174
+ aria-hidden
175
+ className="w-3 h-3 opacity-70"
176
+ fill="none"
177
+ stroke="currentColor"
178
+ strokeWidth="1.5"
179
+ >
180
+ <path d="M3 9L9 3M9 3H4M9 3v5" strokeLinecap="round" strokeLinejoin="round" />
181
+ </svg>
182
+ </a>
183
+ </p>
184
+ </div>
185
+ </div>
186
+ </footer>
187
+ );
188
+ }
@@ -0,0 +1,47 @@
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
+ /**
7
+ * Server-rendered header chrome. Brand mark + nav layout streams from the
8
+ * cache; the active-link styling and live cart count are dynamic islands
9
+ * mounted in their own Suspense boundaries so the chrome never blocks.
10
+ */
11
+ export function Header() {
12
+ return (
13
+ <header className="sticky top-0 z-30 flex items-center justify-between px-6 sm:px-8 py-3.5 border-b border-border bg-background/90 backdrop-blur-md">
14
+ <Link href="/" className="flex items-center gap-2.5 group">
15
+ <span className="grid place-items-center w-8 h-8 rounded-md bg-foreground text-background text-[13px] font-bold font-mono group-hover:bg-primary transition-colors">
16
+ C
17
+ </span>
18
+ <span className="text-[18px] font-bold -tracking-[0.025em]">Currents</span>
19
+ <span className="hidden sm:inline text-[10px] font-mono uppercase tracking-[0.16em] text-muted-foreground border border-border rounded px-1.5 py-0.5">
20
+ ELECTRONICS
21
+ </span>
22
+ </Link>
23
+ <nav className="flex items-center gap-5 sm:gap-6">
24
+ <Suspense fallback={<NavLinkFallback>Shop</NavLinkFallback>}>
25
+ <NavLink href="/shop">Shop</NavLink>
26
+ </Suspense>
27
+ <Suspense fallback={<NavLinkFallback>Deals</NavLinkFallback>}>
28
+ <NavLink href="/categories/deals">Deals</NavLink>
29
+ </Suspense>
30
+ <Suspense fallback={<NavLinkFallback>Support</NavLinkFallback>}>
31
+ <NavLink href="/faq">Support</NavLink>
32
+ </Suspense>
33
+ <Suspense fallback={<CartPillSkeleton />}>
34
+ <CartPill />
35
+ </Suspense>
36
+ </nav>
37
+ </header>
38
+ );
39
+ }
40
+
41
+ function NavLinkFallback({ children }: { children: React.ReactNode }) {
42
+ return (
43
+ <span className="text-[13px] font-medium tracking-wide text-muted-foreground">
44
+ {children}
45
+ </span>
46
+ );
47
+ }
@@ -0,0 +1,28 @@
1
+ interface HeroProps {
2
+ badge?: string;
3
+ title: string;
4
+ subtitle?: string;
5
+ }
6
+
7
+ export function Hero({ badge, title, subtitle }: HeroProps) {
8
+ return (
9
+ <section className="relative px-8 py-20 text-center overflow-hidden bg-gradient-to-br from-foreground via-foreground to-primary text-background">
10
+ <div className="absolute inset-0 opacity-[0.06] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:32px_32px]" />
11
+ <div className="relative max-w-3xl mx-auto">
12
+ {badge && (
13
+ <span className="inline-block mb-5 px-3.5 py-1.5 rounded-full bg-primary/15 border border-primary/30 text-primary-foreground/90 text-[11px] font-semibold uppercase tracking-[0.16em] font-mono">
14
+ {badge}
15
+ </span>
16
+ )}
17
+ <h1 className="text-[clamp(2.5rem,6vw,4rem)] font-bold m-0 -tracking-[0.03em] leading-[1.05]">
18
+ {title}
19
+ </h1>
20
+ {subtitle && (
21
+ <p className="mx-auto mt-5 max-w-2xl text-base sm:text-lg text-background/80 leading-relaxed">
22
+ {subtitle}
23
+ </p>
24
+ )}
25
+ </div>
26
+ </section>
27
+ );
28
+ }
@@ -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
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ export function Newsletter() {
6
+ const [email, setEmail] = useState("");
7
+ const [submitted, setSubmitted] = useState(false);
8
+
9
+ return (
10
+ <section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
11
+ <div className="rounded-3xl border border-border bg-card p-8 sm:p-12 grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
12
+ <div>
13
+ <p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
14
+ The shortlist
15
+ </p>
16
+ <h2 className="text-[clamp(1.5rem,3vw,2rem)] font-bold m-0 mb-3 -tracking-[0.025em]">
17
+ New drops, real prices, in your inbox.
18
+ </h2>
19
+ <p className="text-muted-foreground leading-relaxed">
20
+ One email a week. Curated launches, our actual price (not the
21
+ launch price), and the deals worth knowing about. No newsletter
22
+ chaff.
23
+ </p>
24
+ </div>
25
+ <form
26
+ onSubmit={(e) => {
27
+ e.preventDefault();
28
+ setSubmitted(true);
29
+ }}
30
+ className="flex flex-col sm:flex-row gap-2"
31
+ >
32
+ <input
33
+ type="email"
34
+ required
35
+ value={email}
36
+ onChange={(e) => setEmail(e.target.value)}
37
+ placeholder="you@email.com"
38
+ disabled={submitted}
39
+ className="flex-1 px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-shadow text-sm disabled:opacity-50"
40
+ />
41
+ <button
42
+ type="submit"
43
+ disabled={submitted}
44
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-md bg-foreground text-background font-semibold text-sm hover:bg-primary transition-colors disabled:opacity-50"
45
+ >
46
+ {submitted ? "Subscribed ✓" : "Subscribe"}
47
+ </button>
48
+ </form>
49
+ </div>
50
+ </section>
51
+ );
52
+ }