create-brainerce-store 1.51.0 → 1.52.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.
Files changed (170) hide show
  1. package/README.md +72 -0
  2. package/dist/index.js +9 -68
  3. package/messages/en.json +32 -0
  4. package/messages/he.json +32 -0
  5. package/package.json +1 -1
  6. package/templates/nextjs/base/.claude/commands/design.md +9 -0
  7. package/templates/nextjs/base/AI-GUIDE.md +52 -3
  8. package/templates/nextjs/base/CLAUDE.md +10 -0
  9. package/templates/nextjs/base/package.json.ejs +52 -39
  10. package/templates/nextjs/base/src/app/globals.css +36 -30
  11. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +14 -10
  12. package/templates/nextjs/base/src/components/ui/accordion.tsx +53 -0
  13. package/templates/nextjs/base/src/components/ui/badge.tsx +32 -0
  14. package/templates/nextjs/base/src/components/ui/button.tsx +49 -0
  15. package/templates/nextjs/base/src/components/ui/card.tsx +55 -0
  16. package/templates/nextjs/base/src/components/ui/checkbox.tsx +28 -0
  17. package/templates/nextjs/base/src/components/ui/dialog.tsx +107 -0
  18. package/templates/nextjs/base/src/components/ui/input.tsx +22 -0
  19. package/templates/nextjs/base/src/components/ui/label.tsx +21 -0
  20. package/templates/nextjs/base/src/components/ui/radio-group.tsx +38 -0
  21. package/templates/nextjs/base/src/components/ui/select.tsx +153 -0
  22. package/templates/nextjs/base/src/components/ui/separator.tsx +26 -0
  23. package/templates/nextjs/base/src/components/ui/sheet.tsx +125 -0
  24. package/templates/nextjs/base/src/components/ui/skeleton.tsx +7 -0
  25. package/templates/nextjs/base/src/components/ui/tabs.tsx +55 -0
  26. package/templates/nextjs/base/src/components/ui/textarea.tsx +21 -0
  27. package/templates/nextjs/base/src/components/ui/tooltip.tsx +32 -0
  28. package/templates/nextjs/base/src/ui/cart/cart-bundle-offer.tsx +17 -11
  29. package/templates/nextjs/base/src/ui/cart/cart-item.tsx +2 -8
  30. package/templates/nextjs/base/src/ui/cart/cart-nudges.tsx +28 -39
  31. package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +3 -1
  32. package/templates/nextjs/base/src/ui/cart/cart-upgrade-banner.tsx +8 -23
  33. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +18 -27
  34. package/templates/nextjs/base/src/ui/cart/coupon-input.tsx +10 -21
  35. package/templates/nextjs/base/src/ui/cart/free-shipping-bar.tsx +2 -3
  36. package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +95 -103
  37. package/templates/nextjs/base/src/ui/home/hero-section.tsx +4 -6
  38. package/templates/nextjs/base/src/ui/layout/announcement-bar.tsx.ejs +116 -125
  39. package/templates/nextjs/base/src/ui/layout/faq-section.tsx.ejs +90 -103
  40. package/templates/nextjs/base/src/ui/layout/header-account.tsx +2 -17
  41. package/templates/nextjs/base/src/ui/layout/language-switcher.tsx.ejs +80 -70
  42. package/templates/nextjs/base/src/ui/layout/site-footer.tsx.ejs +144 -164
  43. package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +152 -182
  44. package/templates/nextjs/base/src/ui/product/customization-fields.tsx +456 -444
  45. package/templates/nextjs/base/src/ui/product/discount-badge.tsx +18 -22
  46. package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +15 -27
  47. package/templates/nextjs/base/src/ui/product/modifier-group-selector.tsx +206 -175
  48. package/templates/nextjs/base/src/ui/product/price-display.tsx +3 -2
  49. package/templates/nextjs/base/src/ui/product/product-card.tsx +238 -271
  50. package/templates/nextjs/base/src/ui/product/product-client-section.tsx +455 -470
  51. package/templates/nextjs/base/src/ui/product/product-listing.tsx +96 -79
  52. package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +102 -107
  53. package/templates/nextjs/base/src/ui/product/review-form.tsx +44 -43
  54. package/templates/nextjs/{designs/passepartout → base/src}/ui/product/reviews-section.tsx.ejs +108 -96
  55. package/templates/nextjs/base/src/ui/product/stock-badge.tsx +61 -63
  56. package/templates/nextjs/base/tailwind.config.ts +69 -42
  57. package/templates/nextjs/designs/{passepartout → atelier}/app-overlay/layout.tsx.ejs +23 -25
  58. package/templates/nextjs/designs/atelier/globals.css +396 -0
  59. package/templates/nextjs/designs/atelier/messages-patch/en.json +99 -0
  60. package/templates/nextjs/designs/atelier/messages-patch/he.json +99 -0
  61. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-bundle-offer.tsx +110 -111
  62. package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +163 -0
  63. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +140 -0
  64. package/templates/nextjs/designs/atelier/ui/cart/cart-nudges.tsx +34 -0
  65. package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +107 -0
  66. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-upgrade-banner.tsx +123 -141
  67. package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +147 -0
  68. package/templates/nextjs/designs/atelier/ui/cart/coupon-input.tsx +142 -0
  69. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/free-shipping-bar.tsx +77 -62
  70. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/reservation-countdown.tsx +89 -103
  71. package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +34 -0
  72. package/templates/nextjs/designs/atelier/ui/home/category-tiles.tsx +148 -0
  73. package/templates/nextjs/designs/atelier/ui/home/discount-banner-strip.tsx +27 -0
  74. package/templates/nextjs/designs/atelier/ui/home/editorial-band.tsx +73 -0
  75. package/templates/nextjs/designs/atelier/ui/home/featured-products-section.tsx +36 -0
  76. package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +148 -0
  77. package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +54 -0
  78. package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +78 -0
  79. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +46 -0
  80. package/templates/nextjs/designs/atelier/ui/layout/announcement-bar.tsx +118 -0
  81. package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +96 -0
  82. package/templates/nextjs/designs/atelier/ui/layout/header-account.tsx +36 -0
  83. package/templates/nextjs/designs/atelier/ui/layout/header-cart.tsx +36 -0
  84. package/templates/nextjs/designs/atelier/ui/layout/language-switcher.tsx.ejs +137 -0
  85. package/templates/nextjs/designs/atelier/ui/layout/nav-url.ts +14 -0
  86. package/templates/nextjs/designs/atelier/ui/layout/rich-text-block.tsx +34 -0
  87. package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +141 -0
  88. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +138 -0
  89. package/templates/nextjs/designs/atelier/ui/product/customization-fields.tsx +466 -0
  90. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/discount-badge.tsx +17 -22
  91. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/frequently-bought-together.tsx +196 -205
  92. package/templates/nextjs/designs/atelier/ui/product/modifier-group-selector.tsx +193 -0
  93. package/templates/nextjs/designs/atelier/ui/product/price-display.tsx +69 -0
  94. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +260 -0
  95. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +471 -0
  96. package/templates/nextjs/designs/atelier/ui/product/product-grid.tsx +47 -0
  97. package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +260 -0
  98. package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +95 -0
  99. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/review-form.tsx +267 -294
  100. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -0
  101. package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +66 -0
  102. package/templates/nextjs/designs/atelier/ui/product/variant-selector.tsx +206 -0
  103. package/templates/nextjs/designs/atelier/ui/shared/cdn-image.tsx +29 -0
  104. package/templates/nextjs/designs/{passepartout → atelier}/ui/shared/fly-to-cart.ts +86 -85
  105. package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -0
  106. package/templates/nextjs/designs/atelier/ui/shared/loading-spinner.tsx +64 -0
  107. package/templates/nextjs/designs/atelier/ui/shared/reveal.tsx +71 -0
  108. package/templates/nextjs/designs/atelier/ui/shared/select-menu.tsx +125 -0
  109. package/templates/nextjs/themes/luxury/globals.css +405 -399
  110. package/templates/nextjs/themes/minimal/globals.css +36 -30
  111. package/templates/nextjs/themes/playful/globals.css +406 -400
  112. package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +2 -0
  113. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +2 -0
  114. package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +2 -0
  115. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +2 -0
  116. package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +2 -0
  117. package/templates/nextjs/ui-canvas/cart/cart-view.tsx +2 -2
  118. package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +2 -0
  119. package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +2 -0
  120. package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +2 -0
  121. package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +1 -1
  122. package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +1 -1
  123. package/templates/nextjs/ui-canvas/home/hero-section.tsx +1 -1
  124. package/templates/nextjs/ui-canvas/home/home-client.tsx +1 -1
  125. package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +1 -1
  126. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +2 -2
  127. package/templates/nextjs/ui-canvas/layout/header-account.tsx +1 -1
  128. package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +2 -0
  129. package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +2 -0
  130. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +2 -2
  131. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +2 -2
  132. package/templates/nextjs/ui-canvas/product/customization-fields.tsx +2 -0
  133. package/templates/nextjs/ui-canvas/product/discount-badge.tsx +2 -0
  134. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +2 -0
  135. package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +2 -0
  136. package/templates/nextjs/ui-canvas/product/price-display.tsx +2 -0
  137. package/templates/nextjs/ui-canvas/product/product-card.tsx +1 -1
  138. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +1 -1
  139. package/templates/nextjs/ui-canvas/product/product-grid.tsx +1 -1
  140. package/templates/nextjs/ui-canvas/product/product-listing.tsx +1 -1
  141. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +4 -0
  142. package/templates/nextjs/ui-canvas/product/review-form.tsx +29 -32
  143. package/templates/nextjs/ui-canvas/product/{reviews-section.tsx → reviews-section.tsx.ejs} +26 -8
  144. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +2 -0
  145. package/templates/nextjs/ui-canvas/product/variant-selector.tsx +2 -0
  146. package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +2 -0
  147. package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +2 -0
  148. package/templates/nextjs/base/src/ui/product/reviews-section.tsx +0 -85
  149. package/templates/nextjs/designs/passepartout/globals.css +0 -412
  150. package/templates/nextjs/designs/passepartout/messages-patch/en.json +0 -87
  151. package/templates/nextjs/designs/passepartout/messages-patch/he.json +0 -87
  152. package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +0 -39
  153. package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +0 -149
  154. package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +0 -401
  155. package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +0 -139
  156. package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +0 -45
  157. package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +0 -103
  158. package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +0 -159
  159. package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +0 -155
  160. package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +0 -60
  161. package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +0 -34
  162. package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +0 -111
  163. package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +0 -265
  164. package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +0 -667
  165. package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +0 -35
  166. package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +0 -427
  167. package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +0 -113
  168. package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +0 -52
  169. package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +0 -307
  170. package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +0 -33
@@ -1,182 +1,152 @@
1
- /**
2
- * Merchant-controlled site header.
3
- *
4
- * The merchant configures the header in the Brainerce dashboard under
5
- * Sell → Content → Header
6
- *
7
- * Renders brand (logo or store-name fallback) + nav items + CTA + cart link,
8
- * with a `<details>` mobile menu that works without client-side JS. Everything
9
- * is generic — do NOT hardcode nav labels or logo paths here; the merchant
10
- * edits them in the dashboard and the change propagates within ~5 minutes.
11
- *
12
- * If `header` is null (404 from the API, or the merchant deleted the row),
13
- * we render a minimal static fallback with just the brand label and cart link
14
- * so the layout never collapses. New stores ship with a seeded HEADER row
15
- * from the backend (StoresService.seedDefaultContent), so the populated
16
- * branch is the common case.
17
- */
18
- import * as React from 'react';
19
- import type { Content } from 'brainerce';
20
- import { HeaderAccount } from './header-account';
21
- <% if (i18nEnabled) { %>
22
- import { LanguageSwitcher } from '@/ui/layout/language-switcher';
23
- <% } %>
24
-
25
- interface SiteHeaderProps {
26
- /** Pre-fetched header payload (server-side). `null` triggers static fallback. */
27
- header: Content<'HEADER'> | null;
28
- /** Fallback brand label when the merchant hasn't uploaded a logo yet. */
29
- storeName?: string;
30
- }
31
-
32
- const MenuIcon = (props: React.SVGProps<SVGSVGElement>) => (
33
- <svg
34
- viewBox="0 0 24 24"
35
- fill="none"
36
- stroke="currentColor"
37
- strokeWidth={2}
38
- strokeLinecap="round"
39
- strokeLinejoin="round"
40
- aria-hidden="true"
41
- {...props}
42
- >
43
- <line x1="4" y1="6" x2="20" y2="6" />
44
- <line x1="4" y1="12" x2="20" y2="12" />
45
- <line x1="4" y1="18" x2="20" y2="18" />
46
- </svg>
47
- );
48
-
49
- const CartIcon = (props: React.SVGProps<SVGSVGElement>) => (
50
- <svg
51
- viewBox="0 0 24 24"
52
- fill="none"
53
- stroke="currentColor"
54
- strokeWidth={2}
55
- strokeLinecap="round"
56
- strokeLinejoin="round"
57
- aria-hidden="true"
58
- {...props}
59
- >
60
- <circle cx="9" cy="21" r="1" />
61
- <circle cx="20" cy="21" r="1" />
62
- <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
63
- </svg>
64
- );
65
-
66
- export function SiteHeader({ header, storeName }: SiteHeaderProps) {
67
- const data = header?.data;
68
- const logo = data?.logo;
69
- const navItems = data?.navItems ?? [];
70
- const cta = data?.cta;
71
- const brandLabel = logo?.alt || storeName || 'Store';
72
-
73
- // Static fallback — runs when the Content API returned null (no HEADER row
74
- // for this store yet, or fetch failed). Keep the brand + cart so the page
75
- // always has a clickable home link and entry to checkout, but no nav since
76
- // the merchant hasn't defined any items.
77
- if (!header) {
78
- return (
79
- <header className="border-border bg-background/95 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-40 border-b backdrop-blur">
80
- <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
81
- <a href="/" className="text-lg font-semibold tracking-tight" aria-label={brandLabel}>
82
- {brandLabel}
83
- </a>
84
- <div className="flex items-center gap-2">
85
- <% if (i18nEnabled) { %>
86
- <LanguageSwitcher />
87
- <% } %>
88
- <HeaderAccount />
89
- <a
90
- href="/cart"
91
- aria-label="Cart"
92
- className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 items-center justify-center rounded-md transition-colors"
93
- >
94
- <CartIcon className="h-5 w-5" />
95
- </a>
96
- </div>
97
- </div>
98
- </header>
99
- );
100
- }
101
-
102
- return (
103
- <header className="border-border bg-background/95 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-40 border-b backdrop-blur">
104
- <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
105
- <a
106
- href="/"
107
- className="flex items-center gap-2 font-semibold tracking-tight"
108
- aria-label={brandLabel}
109
- >
110
- {logo ? (
111
- // eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
112
- <img src={logo.src} alt={logo.alt} className="h-8 w-auto" />
113
- ) : (
114
- <span className="text-lg">{brandLabel}</span>
115
- )}
116
- </a>
117
-
118
- {navItems.length > 0 ? (
119
- <nav className="hidden items-center gap-8 md:flex">
120
- {navItems.map((item, idx) => (
121
- <a
122
- key={`${item.url}-${idx}`}
123
- href={item.url}
124
- className="text-foreground/80 hover:text-foreground text-sm font-medium transition-colors"
125
- >
126
- {item.label}
127
- </a>
128
- ))}
129
- </nav>
130
- ) : null}
131
-
132
- <div className="flex items-center gap-2">
133
- {cta ? (
134
- <a
135
- href={cta.url}
136
- className="bg-primary text-primary-foreground hidden items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:opacity-90 sm:inline-flex"
137
- >
138
- {cta.label}
139
- </a>
140
- ) : null}
141
-
142
- <% if (i18nEnabled) { %>
143
- <LanguageSwitcher />
144
- <% } %>
145
-
146
- <HeaderAccount />
147
-
148
- <a
149
- href="/cart"
150
- aria-label="Cart"
151
- className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 items-center justify-center rounded-md transition-colors"
152
- >
153
- <CartIcon className="h-5 w-5" />
154
- </a>
155
-
156
- {navItems.length > 0 ? (
157
- <details className="group relative md:hidden">
158
- <summary className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 cursor-pointer list-none items-center justify-center rounded-md transition-colors [&::-webkit-details-marker]:hidden">
159
- <MenuIcon className="h-5 w-5" />
160
- <span className="sr-only">Menu</span>
161
- </summary>
162
- <nav className="border-border bg-background absolute end-0 top-full z-50 mt-2 w-56 overflow-hidden rounded-lg border shadow-lg">
163
- <ul className="py-1">
164
- {navItems.map((item, idx) => (
165
- <li key={`m-${item.url}-${idx}`}>
166
- <a
167
- href={item.url}
168
- className="text-foreground/80 hover:text-foreground hover:bg-muted block px-4 py-2 text-sm transition-colors"
169
- >
170
- {item.label}
171
- </a>
172
- </li>
173
- ))}
174
- </ul>
175
- </nav>
176
- </details>
177
- ) : null}
178
- </div>
179
- </div>
180
- </header>
181
- );
182
- }
1
+ /**
2
+ * Merchant-controlled site header.
3
+ *
4
+ * The merchant configures the header in the Brainerce dashboard under
5
+ * Sell → Content → Header
6
+ *
7
+ * Renders brand (logo or store-name fallback) + nav items + CTA + cart link,
8
+ * with a `<details>` mobile menu that works without client-side JS. Everything
9
+ * is generic — do NOT hardcode nav labels or logo paths here; the merchant
10
+ * edits them in the dashboard and the change propagates within ~5 minutes.
11
+ *
12
+ * If `header` is null (404 from the API, or the merchant deleted the row),
13
+ * we render a minimal static fallback with just the brand label and cart link
14
+ * so the layout never collapses. New stores ship with a seeded HEADER row
15
+ * from the backend (StoresService.seedDefaultContent), so the populated
16
+ * branch is the common case.
17
+ */
18
+ import * as React from 'react';
19
+ import { Menu, ShoppingCart } from 'lucide-react';
20
+ import type { Content } from 'brainerce';
21
+ import { Button } from '@/components/ui/button';
22
+ // Locale-aware Link (client component — fine to render from this server
23
+ // component). Used for the static internal links (brand → /, cart) so the
24
+ // current locale prefix is preserved; merchant-configured nav items keep
25
+ // plain <a> since their URLs are arbitrary (may be external).
26
+ import { Link } from '@/core/lib/navigation';
27
+ import { HeaderAccount } from './header-account';
28
+ <% if (i18nEnabled) { %>
29
+ import { LanguageSwitcher } from '@/ui/layout/language-switcher';
30
+ <% } %>
31
+
32
+ interface SiteHeaderProps {
33
+ /** Pre-fetched header payload (server-side). `null` triggers static fallback. */
34
+ header: Content<'HEADER'> | null;
35
+ /** Fallback brand label when the merchant hasn't uploaded a logo yet. */
36
+ storeName?: string;
37
+ }
38
+
39
+ export function SiteHeader({ header, storeName }: SiteHeaderProps) {
40
+ const data = header?.data;
41
+ const logo = data?.logo;
42
+ const navItems = data?.navItems ?? [];
43
+ const cta = data?.cta;
44
+ const brandLabel = logo?.alt || storeName || 'Store';
45
+
46
+ // Static fallback — runs when the Content API returned null (no HEADER row
47
+ // for this store yet, or fetch failed). Keep the brand + cart so the page
48
+ // always has a clickable home link and entry to checkout, but no nav since
49
+ // the merchant hasn't defined any items.
50
+ if (!header) {
51
+ return (
52
+ <header className="border-border bg-background/95 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-40 border-b backdrop-blur">
53
+ <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
54
+ <Link href="/" className="text-lg font-semibold tracking-tight" aria-label={brandLabel}>
55
+ {brandLabel}
56
+ </Link>
57
+ <div className="flex items-center gap-2">
58
+ <% if (i18nEnabled) { %>
59
+ <LanguageSwitcher />
60
+ <% } %>
61
+ <HeaderAccount />
62
+ <Link
63
+ href="/cart"
64
+ aria-label="Cart"
65
+ className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 items-center justify-center rounded-md transition-colors"
66
+ >
67
+ <ShoppingCart className="h-5 w-5" aria-hidden="true" />
68
+ </Link>
69
+ </div>
70
+ </div>
71
+ </header>
72
+ );
73
+ }
74
+
75
+ return (
76
+ <header className="border-border bg-background/95 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-40 border-b backdrop-blur">
77
+ <div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
78
+ <Link
79
+ href="/"
80
+ className="flex items-center gap-2 font-semibold tracking-tight"
81
+ aria-label={brandLabel}
82
+ >
83
+ {logo ? (
84
+ // eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
85
+ <img src={logo.src} alt={logo.alt} className="h-8 w-auto" />
86
+ ) : (
87
+ <span className="text-lg">{brandLabel}</span>
88
+ )}
89
+ </Link>
90
+
91
+ {navItems.length > 0 ? (
92
+ <nav className="hidden items-center gap-8 md:flex">
93
+ {navItems.map((item, idx) => (
94
+ <a
95
+ key={`${item.url}-${idx}`}
96
+ href={item.url}
97
+ className="text-foreground/80 hover:text-foreground text-sm font-medium transition-colors"
98
+ >
99
+ {item.label}
100
+ </a>
101
+ ))}
102
+ </nav>
103
+ ) : null}
104
+
105
+ <div className="flex items-center gap-2">
106
+ {cta ? (
107
+ <Button asChild className="hidden sm:inline-flex">
108
+ <a href={cta.url}>{cta.label}</a>
109
+ </Button>
110
+ ) : null}
111
+
112
+ <% if (i18nEnabled) { %>
113
+ <LanguageSwitcher />
114
+ <% } %>
115
+
116
+ <HeaderAccount />
117
+
118
+ <Link
119
+ href="/cart"
120
+ aria-label="Cart"
121
+ className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 items-center justify-center rounded-md transition-colors"
122
+ >
123
+ <ShoppingCart className="h-5 w-5" aria-hidden="true" />
124
+ </Link>
125
+
126
+ {navItems.length > 0 ? (
127
+ <details className="group relative md:hidden">
128
+ <summary className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 w-9 cursor-pointer list-none items-center justify-center rounded-md transition-colors [&::-webkit-details-marker]:hidden">
129
+ <Menu className="h-5 w-5" aria-hidden="true" />
130
+ <span className="sr-only">Menu</span>
131
+ </summary>
132
+ <nav className="border-border bg-background absolute end-0 top-full z-50 mt-2 w-56 overflow-hidden rounded-lg border shadow-lg">
133
+ <ul className="py-1">
134
+ {navItems.map((item, idx) => (
135
+ <li key={`m-${item.url}-${idx}`}>
136
+ <a
137
+ href={item.url}
138
+ className="text-foreground/80 hover:text-foreground hover:bg-muted block px-4 py-2 text-sm transition-colors"
139
+ >
140
+ {item.label}
141
+ </a>
142
+ </li>
143
+ ))}
144
+ </ul>
145
+ </nav>
146
+ </details>
147
+ ) : null}
148
+ </div>
149
+ </div>
150
+ </header>
151
+ );
152
+ }