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
@@ -19,6 +19,8 @@ interface CartBundleOfferCardProps {
19
19
  * DESIGN ME — bundle offer card on the cart page: offered products with
20
20
  * original vs. discounted prices + add-bundle button; offers come from
21
21
  * useCartPage().bundles.
22
+ *
23
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
22
24
  */
23
25
  export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBundleOfferCardProps) {
24
26
  const t = useTranslations('cart');
@@ -19,6 +19,8 @@ interface CartItemProps {
19
19
  /**
20
20
  * DESIGN ME — single cart line: image, name, variant, unit price, quantity
21
21
  * controls, remove, line total; the `item` prop comes from useCartPage().cart.
22
+ *
23
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
22
24
  */
23
25
  export function CartItem({ item, onUpdate, className }: CartItemProps) {
24
26
  const t = useTranslations('common');
@@ -11,6 +11,8 @@ interface CartNudgesProps {
11
11
  /**
12
12
  * DESIGN ME — discount-rule nudges on the cart page ("add X more for Y");
13
13
  * texts come from useCartPage().cart.nudges.
14
+ *
15
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
14
16
  */
15
17
  export function CartNudges({ nudges, className }: CartNudgesProps) {
16
18
  if (nudges.length === 0) return null;
@@ -14,6 +14,8 @@ interface CartSummaryProps {
14
14
  * DESIGN ME — order summary (subtotal, rule/coupon discounts, shipping, tax
15
15
  * note, total); all figures come from useCart().totals and useCart().cart.
16
16
  * Keep every discount branch — merchants rely on the per-rule breakdown.
17
+ *
18
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
17
19
  */
18
20
  export function CartSummary({ className }: CartSummaryProps) {
19
21
  const t = useTranslations('cart');
@@ -20,6 +20,8 @@ interface CartUpgradeBannerProps {
20
20
  * DESIGN ME — per-line upgrade suggestion under a cart item ("upgrade to X
21
21
  * for +Y"); the suggestion comes from useCartPage().upgrades keyed by
22
22
  * productId. Keep the session-scoped dismiss and the swap logic.
23
+ *
24
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
23
25
  */
24
26
  export function CartUpgradeBanner({
25
27
  suggestion,
@@ -27,7 +27,7 @@ export function CartView() {
27
27
  if (!cart || cart.items.length === 0) {
28
28
  return (
29
29
  <section>
30
- {/* DESIGN ME — empty-cart state: message + continue-shopping CTA; state comes from useCartPage() */}
30
+ {/* DESIGN ME — empty-cart state: message + continue-shopping CTA; state comes from useCartPage(). Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
31
31
  <h1>{t('emptyTitle')}</h1>
32
32
  <p>{t('emptySubtitle')}</p>
33
33
  <Link href="/products">{tc('continueShopping')}</Link>
@@ -37,7 +37,7 @@ export function CartView() {
37
37
 
38
38
  return (
39
39
  <section>
40
- {/* DESIGN ME — cart page: items list, upgrade/bundle offers, coupon, summary sidebar, checkout CTA, cross-sells; everything comes from useCartPage() */}
40
+ {/* DESIGN ME — cart page: items list, upgrade/bundle offers, coupon, summary sidebar, checkout CTA, cross-sells; everything comes from useCartPage(). Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
41
41
  <h1>
42
42
  {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
43
43
  </h1>
@@ -20,6 +20,8 @@ interface CouponInputProps {
20
20
  * DESIGN ME — coupon apply/remove control used on the cart page AND the
21
21
  * checkout page (via `checkoutId`); the `cart` prop comes from useCartPage().
22
22
  * Keep the applied-coupon state and the error message.
23
+ *
24
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
23
25
  */
24
26
  export function CouponInput({ cart, checkoutId, onUpdate, className }: CouponInputProps) {
25
27
  const t = useTranslations('coupon');
@@ -15,6 +15,8 @@ interface FreeShippingBarProps {
15
15
  * shipping"); threshold from useStoreInfo().upsell, subtotal from
16
16
  * useCart().totals. The semantic <progress> is a placeholder — build your own
17
17
  * meter.
18
+ *
19
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
18
20
  */
19
21
  export function FreeShippingBar({ className }: FreeShippingBarProps) {
20
22
  // Hooks must be called unconditionally and in the same order on every
@@ -14,6 +14,8 @@ interface ReservationCountdownProps {
14
14
  * DESIGN ME — stock-reservation countdown on the cart/checkout pages; the
15
15
  * `reservation` prop comes from useCartPage().cart.reservation. Keep the
16
16
  * ticking logic and the expired/urgent states (exposed via data-state).
17
+ *
18
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
17
19
  */
18
20
  export function ReservationCountdown({ reservation, className }: ReservationCountdownProps) {
19
21
  const t = useTranslations('reservation');
@@ -8,7 +8,7 @@ export function DiscountBannerStrip({ banners }: { banners: DiscountBanner[] })
8
8
 
9
9
  return (
10
10
  <div role="region" aria-label="Promotions">
11
- {/* DESIGN ME — discount banner strip: active discount-rule texts; banners come from useHomeData() in the parent */}
11
+ {/* DESIGN ME — discount banner strip: active discount-rule texts; banners come from useHomeData() in the parent. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
12
12
  <ul className="flex items-center gap-4">
13
13
  {banners.map((banner) => (
14
14
  <li key={banner.ruleId}>{banner.text}</li>
@@ -12,7 +12,7 @@ export function FeaturedProductsSection({ products }: { products: Product[] }) {
12
12
 
13
13
  return (
14
14
  <section>
15
- {/* DESIGN ME — homepage featured products: heading + view-all link + product grid; products come from useHomeData() in the parent */}
15
+ {/* DESIGN ME — homepage featured products: heading + view-all link + product grid; products come from useHomeData() in the parent. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
16
16
  <div className="flex items-center justify-between">
17
17
  <h2>{t('featuredProducts')}</h2>
18
18
  <Link href="/products">{tc('viewAll')}</Link>
@@ -10,7 +10,7 @@ export function HeroSection({ storeName }: { storeName?: string | null }) {
10
10
 
11
11
  return (
12
12
  <section>
13
- {/* DESIGN ME — homepage hero: headline + subtitle + shop-now CTA; store name via useStoreInfo() in the parent, copy via the `home`/`common` translation namespaces */}
13
+ {/* DESIGN ME — homepage hero: headline + subtitle + shop-now CTA; store name via useStoreInfo() in the parent, copy via the `home`/`common` translation namespaces. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
14
14
  <h1>
15
15
  {t('welcomeTo')} {storeName || tc('store')}
16
16
  </h1>
@@ -17,7 +17,7 @@ export function HomeClient() {
17
17
 
18
18
  return (
19
19
  <div>
20
- {/* DESIGN ME — homepage composition: discount banners + hero + featured products; data via useHomeData() and useStoreInfo(). Add/remove/reorder sections freely. */}
20
+ {/* DESIGN ME — homepage composition: discount banners + hero + featured products; data via useHomeData() and useStoreInfo(). Add/remove/reorder sections freely. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
21
21
  <DiscountBannerStrip banners={banners} />
22
22
  <HeroSection storeName={storeInfo?.name} />
23
23
  <FeaturedProductsSection products={products} />
@@ -73,7 +73,7 @@ export function AnnouncementBar({ announcements }: AnnouncementBarProps) {
73
73
 
74
74
  return (
75
75
  <div role="region" aria-label="Site announcements">
76
- {/* DESIGN ME — announcement bar(s) at the very top of every page; data.severity + message + optional CTA come from Content<'ANNOUNCEMENT'>[] pre-fetched in the layout */}
76
+ {/* DESIGN ME — announcement bar(s) at the very top of every page; data.severity + message + optional CTA come from Content<'ANNOUNCEMENT'>[] pre-fetched in the layout. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
77
77
  {visible.map((announcement) => (
78
78
  <div key={announcement.id} data-severity={announcement.data.severity} className="flex items-center gap-3">
79
79
  <span>
@@ -40,7 +40,7 @@ export function FaqSection({ faq }: FaqSectionProps) {
40
40
  if (!faq || items.length === 0) {
41
41
  return (
42
42
  <section>
43
- {/* DESIGN ME — FAQ empty state: heading + contact CTA; fed by Content<'FAQ'> pre-fetched in the /faq route */}
43
+ {/* DESIGN ME — FAQ empty state: heading + contact CTA; fed by Content<'FAQ'> pre-fetched in the /faq route. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
44
44
  <h1>{heading}</h1>
45
45
  <p>
46
46
  <strong>{t('faqEmptyTitle')}</strong>
@@ -54,7 +54,7 @@ export function FaqSection({ faq }: FaqSectionProps) {
54
54
 
55
55
  return (
56
56
  <section>
57
- {/* DESIGN ME — FAQ accordion: question/answer pairs from Content<'FAQ'> pre-fetched in the /faq route; answers are sanitized merchant HTML */}
57
+ {/* DESIGN ME — FAQ accordion: question/answer pairs from Content<'FAQ'> pre-fetched in the /faq route; answers are sanitized merchant HTML. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
58
58
  <h1>{heading}</h1>
59
59
  <ul>
60
60
  {items.map((item, idx) => (
@@ -24,7 +24,7 @@ export function HeaderAccount() {
24
24
 
25
25
  return (
26
26
  <Link href={href} aria-label={label}>
27
- {/* DESIGN ME — header account link; auth state via useAuth(), label via the `nav` translation namespace */}
27
+ {/* DESIGN ME — header account link; auth state via useAuth(), label via the `nav` translation namespace. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
28
28
  {label}
29
29
  </Link>
30
30
  );
@@ -35,6 +35,8 @@ const defaultLocale = '<%= defaultLocale %>';
35
35
  * DESIGN ME — locale switcher in the header; reads supported locales from
36
36
  * `@/i18n` and swaps the URL prefix. Keep the aria-label and the redirect
37
37
  * logic exactly as-is.
38
+ *
39
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
38
40
  */
39
41
  export function LanguageSwitcher() {
40
42
  const pathname = usePathname();
@@ -14,6 +14,8 @@
14
14
  * DESIGN ME — merchant-editable HTML block; fed by
15
15
  * `client.content.richText.get(key, locale)` pre-fetched in the route.
16
16
  * Style the injected HTML via the wrapper `className`.
17
+ *
18
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
17
19
  */
18
20
  import * as React from 'react';
19
21
  import type { Content } from 'brainerce';
@@ -33,7 +33,7 @@ export function SiteFooter({ footer, storeName }: SiteFooterProps) {
33
33
  if (!data || (columns.length === 0 && !data.copyright && social.length === 0)) {
34
34
  return (
35
35
  <footer>
36
- {/* DESIGN ME — footer (fallback branch): copyright only; Content<'FOOTER'> pre-fetched in the layout */}
36
+ {/* DESIGN ME — footer (fallback branch): copyright only; Content<'FOOTER'> pre-fetched in the layout. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
37
37
  <p>
38
38
  © {year} {brandLabel}. All rights reserved.
39
39
  </p>
@@ -43,7 +43,7 @@ export function SiteFooter({ footer, storeName }: SiteFooterProps) {
43
43
 
44
44
  return (
45
45
  <footer>
46
- {/* DESIGN ME — footer: brand block, merchant link columns and social links (Content<'FOOTER'> pre-fetched in the layout), copyright bar */}
46
+ {/* DESIGN ME — footer: brand block, merchant link columns and social links (Content<'FOOTER'> pre-fetched in the layout), copyright bar. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
47
47
  <div>
48
48
  <a href="/">{brandLabel}</a>
49
49
  {social.length > 0 ? (
@@ -41,7 +41,7 @@ export function SiteHeader({ header, storeName }: SiteHeaderProps) {
41
41
  if (!header) {
42
42
  return (
43
43
  <header>
44
- {/* DESIGN ME — site header (fallback branch, no merchant nav yet): brand link + account + cart; data comes pre-fetched from the layout via the Content API */}
44
+ {/* DESIGN ME — site header (fallback branch, no merchant nav yet): brand link + account + cart; data comes pre-fetched from the layout via the Content API. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
45
45
  <div className="flex items-center justify-between gap-4">
46
46
  <a href="/" aria-label={brandLabel}>
47
47
  {brandLabel}
@@ -62,7 +62,7 @@ export function SiteHeader({ header, storeName }: SiteHeaderProps) {
62
62
 
63
63
  return (
64
64
  <header>
65
- {/* DESIGN ME — site header: merchant-configured logo/nav/CTA (Content<'HEADER'> pre-fetched in the layout) + account + cart links. Keep every link working. */}
65
+ {/* DESIGN ME — site header: merchant-configured logo/nav/CTA (Content<'HEADER'> pre-fetched in the layout) + account + cart links. Keep every link working. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
66
66
  <div className="flex items-center justify-between gap-4">
67
67
  <a href="/" className="flex items-center gap-2" aria-label={brandLabel}>
68
68
  {logo ? (
@@ -24,6 +24,8 @@ interface CustomizationFieldsProps {
24
24
  * (engraving text, gift date, uploaded artwork, …). Field definitions,
25
25
  * values and validation errors come from useProductPage(); every input type
26
26
  * below must stay functional (including the image/gallery uploads).
27
+ *
28
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
27
29
  */
28
30
  export function CustomizationFields({
29
31
  fields,
@@ -9,6 +9,8 @@ interface DiscountBadgeProps {
9
9
  /**
10
10
  * DESIGN ME — discount-rule badge shown on product cards; text comes from
11
11
  * the product's `discount.badgeText` (merchant-configured rule).
12
+ *
13
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
12
14
  */
13
15
  export function DiscountBadge({ discount, className }: DiscountBadgeProps) {
14
16
  if (!discount) return null;
@@ -56,6 +56,8 @@ function ProductThumb({
56
56
  * current product + selectable cross-sells + combined total + add-all button;
57
57
  * items come from useProductPage().recommendations.crossSells, the feature
58
58
  * gate from useStoreInfo().upsell.
59
+ *
60
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
59
61
  */
60
62
  export function FrequentlyBoughtTogether({
61
63
  items,
@@ -19,6 +19,8 @@
19
19
  * The price-delta side: this component does NOT compute totals. The server
20
20
  * runs the free-allocation policy and returns the line's `unitPrice` /
21
21
  * `modifiers[]` / `modifiersTotal` snapshot — render those after add-to-cart.
22
+ *
23
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
22
24
  */
23
25
 
24
26
  import type { Modifier, ModifierGroup } from 'brainerce';
@@ -16,6 +16,8 @@ interface PriceDisplayProps {
16
16
  * DESIGN ME — price renderer used by cards, product page and recommendations;
17
17
  * computes sale state + discount % from props (currency via useCurrency()).
18
18
  * Never hardcode currency symbols — formatPrice handles them.
19
+ *
20
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
19
21
  */
20
22
  export function PriceDisplay({
21
23
  price,
@@ -130,7 +130,7 @@ export function ProductCard({ product, className }: ProductCardProps) {
130
130
 
131
131
  return (
132
132
  <article className={cn(className)}>
133
- {/* DESIGN ME — product card (grid/listing unit): image, badges, name, price, stock, add-to-cart; product data arrives as a prop from useHomeData()/useProductListing() */}
133
+ {/* DESIGN ME — product card (grid/listing unit): image, badges, name, price, stock, add-to-cart; product data arrives as a prop from useHomeData()/useProductListing(). Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
134
134
  <figure>
135
135
  <Link href={`/products/${slug}`}>
136
136
  {/* `relative` + `aspect-square` are layout-critical for next/image fill */}
@@ -108,7 +108,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
108
108
 
109
109
  return (
110
110
  <article>
111
- {/* DESIGN ME — product page (PDP): gallery, buy box (price/stock/variants/customization/modifiers/quantity/add-to-cart), description, specs, cross-sells; everything comes from useProductPage(initialProduct) */}
111
+ {/* DESIGN ME — product page (PDP): gallery, buy box (price/stock/variants/customization/modifiers/quantity/add-to-cart), description, specs, cross-sells; everything comes from useProductPage(initialProduct). Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
112
112
  <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
113
113
  {/* Image Gallery */}
114
114
  <figure>
@@ -18,7 +18,7 @@ export function ProductGrid({ products, className }: ProductGridProps) {
18
18
 
19
19
  return (
20
20
  <div className={cn('grid grid-cols-2 gap-4 lg:grid-cols-4', className)}>
21
- {/* DESIGN ME — product grid: lays out ProductCard units; products arrive as a prop from the listing/home hooks */}
21
+ {/* DESIGN ME — product grid: lays out ProductCard units; products arrive as a prop from the listing/home hooks. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
22
22
  {products.map((product) => (
23
23
  <ProductCard key={product.id} product={product} />
24
24
  ))}
@@ -65,7 +65,7 @@ export function ProductListing() {
65
65
 
66
66
  return (
67
67
  <section>
68
- {/* DESIGN ME — product listing page: header, category/brand/tag/custom-field filters, sort, product grid, load-more; everything comes from useProductListing() */}
68
+ {/* DESIGN ME — product listing page: header, category/brand/tag/custom-field filters, sort, product grid, load-more; everything comes from useProductListing(). Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
69
69
  <header>
70
70
  <h1>{searchQuery ? `${t('searchPrefix')} "${searchQuery}"` : t('allProducts')}</h1>
71
71
  {!loading && (
@@ -49,6 +49,8 @@ interface RecommendationSectionProps {
49
49
  /**
50
50
  * DESIGN ME — recommendation strip on the product page (upsells / related);
51
51
  * items come from useProductPage().recommendations, title is passed in.
52
+ *
53
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
52
54
  */
53
55
  export function RecommendationSection({ title, items, className }: RecommendationSectionProps) {
54
56
  if (items.length === 0) return null;
@@ -74,6 +76,8 @@ interface CartRecommendationSectionProps {
74
76
  /**
75
77
  * DESIGN ME — cross-sell strip on the cart page ("you might also need");
76
78
  * items come from useCartPage().cartRecs.
79
+ *
80
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
77
81
  */
78
82
  export function CartRecommendationSection({
79
83
  title,
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState } from 'react';
4
4
  import { getClient } from '@/core/lib/brainerce';
5
5
  import { checkAuthStatus } from '@/core/lib/auth';
6
+ import { useTranslations } from '@/core/lib/translations';
6
7
  import type { MyProductReview, ProductReview } from 'brainerce';
7
8
 
8
9
  interface ReviewFormProps {
@@ -26,9 +27,12 @@ type Stage =
26
27
  *
27
28
  * DESIGN ME — review submit/edit form; auth via checkAuthStatus(), eligibility
28
29
  * + existing review via getClient().getMyProductReview(productId).
30
+ *
31
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
29
32
  */
30
33
  export function ReviewForm({ productId }: ReviewFormProps) {
31
34
  const [stage, setStage] = useState<Stage>({ kind: 'loading' });
35
+ const t = useTranslations('reviews');
32
36
 
33
37
  useEffect(() => {
34
38
  let cancelled = false;
@@ -62,13 +66,13 @@ export function ReviewForm({ productId }: ReviewFormProps) {
62
66
  }, [productId]);
63
67
 
64
68
  if (stage.kind === 'loading') {
65
- return <p role="status">Loading…</p>;
69
+ return <p role="status">{t('loading')}</p>;
66
70
  }
67
71
 
68
72
  if (stage.kind === 'signed_out') {
69
73
  return (
70
74
  <p>
71
- <a href="/account/login">Sign in</a> to leave a review for this product.
75
+ <a href="/account/login">{t('signIn')}</a> {t('signInSuffix')}
72
76
  </p>
73
77
  );
74
78
  }
@@ -91,16 +95,17 @@ export function ReviewForm({ productId }: ReviewFormProps) {
91
95
  }
92
96
 
93
97
  function NotEligibleMessage({ reason }: { reason: MyProductReview['reason'] }) {
98
+ const t = useTranslations('reviews');
94
99
  let message: string;
95
100
  switch (reason) {
96
101
  case 'no_eligible_order':
97
- message = 'Only customers who purchased this product can leave a review.';
102
+ message = t('onlyPurchasers');
98
103
  break;
99
104
  case 'reviews_disabled':
100
- message = 'Reviews are not enabled for this store.';
105
+ message = t('reviewsDisabled');
101
106
  break;
102
107
  default:
103
- message = 'You cannot review this product right now.';
108
+ message = t('cannotReview');
104
109
  }
105
110
  return <p>{message}</p>;
106
111
  }
@@ -119,10 +124,11 @@ function EditReviewBlock({
119
124
  onDeleted: () => void;
120
125
  }) {
121
126
  const [deleting, setDeleting] = useState(false);
127
+ const t = useTranslations('reviews');
122
128
  const [deleteError, setDeleteError] = useState<string | null>(null);
123
129
 
124
130
  async function handleDelete() {
125
- if (!window.confirm('Delete your review for this product?')) return;
131
+ if (!window.confirm(t('confirmDelete'))) return;
126
132
  setDeleting(true);
127
133
  setDeleteError(null);
128
134
  try {
@@ -130,7 +136,7 @@ function EditReviewBlock({
130
136
  onDeleted();
131
137
  window.location.reload();
132
138
  } catch {
133
- setDeleteError('Could not delete your review. Please try again.');
139
+ setDeleteError(t('deleteFailed'));
134
140
  } finally {
135
141
  setDeleting(false);
136
142
  }
@@ -145,9 +151,9 @@ function EditReviewBlock({
145
151
  initialBody={initial.body ?? ''}
146
152
  />
147
153
  <div className="flex items-center gap-2">
148
- <p>Want to start over? You can delete your review.</p>
154
+ <p>{t('startOver')}</p>
149
155
  <button type="button" onClick={handleDelete} disabled={deleting}>
150
- {deleting ? 'Deleting…' : 'Delete review'}
156
+ {deleting ? t('deleting') : t('deleteReview')}
151
157
  </button>
152
158
  </div>
153
159
  {deleteError && <p role="alert">{deleteError}</p>}
@@ -172,6 +178,7 @@ function ReviewEditor({
172
178
  initialBody: string;
173
179
  }) {
174
180
  const [rating, setRating] = useState(initialRating);
181
+ const t = useTranslations('reviews');
175
182
  const [body, setBody] = useState(initialBody);
176
183
  const [submitting, setSubmitting] = useState(false);
177
184
  const [error, setError] = useState<string | null>(null);
@@ -194,17 +201,13 @@ function ReviewEditor({
194
201
  } catch (err) {
195
202
  const status = (err as { status?: number })?.status;
196
203
  if (status === 409) {
197
- setError("You've already submitted a review for this product.");
204
+ setError(t('alreadySubmitted'));
198
205
  } else if (status === 403) {
199
- setError('Only customers who purchased this product can leave a review.');
206
+ setError(t('onlyPurchasers'));
200
207
  } else if (status === 429) {
201
- setError('Too many submissions. Please try again in a few minutes.');
208
+ setError(t('tooMany'));
202
209
  } else {
203
- setError(
204
- mode === 'edit'
205
- ? 'Could not update your review. Please try again.'
206
- : 'Could not submit your review. Please try again.'
207
- );
210
+ setError(mode === 'edit' ? t('updateFailed') : t('submitFailed'));
208
211
  }
209
212
  } finally {
210
213
  setSubmitting(false);
@@ -212,22 +215,16 @@ function ReviewEditor({
212
215
  }
213
216
 
214
217
  if (success) {
215
- return (
216
- <p role="status">
217
- {mode === 'edit'
218
- ? 'Thanks! Your review has been updated.'
219
- : 'Thanks! Your review has been submitted.'}
220
- </p>
221
- );
218
+ return <p role="status">{mode === 'edit' ? t('thanksUpdated') : t('thanksSubmitted')}</p>;
222
219
  }
223
220
 
224
221
  return (
225
222
  <form onSubmit={handleSubmit}>
226
- <h3>{mode === 'edit' ? 'Edit your review' : 'Write a review'}</h3>
223
+ <h3>{mode === 'edit' ? t('editYourReview') : t('writeReview')}</h3>
227
224
 
228
225
  <fieldset>
229
- <legend>Your rating</legend>
230
- <div className="inline-flex gap-1" role="radiogroup" aria-label="Star rating">
226
+ <legend>{t('yourRating')}</legend>
227
+ <div className="inline-flex gap-1" role="radiogroup" aria-label={t('starRating')}>
231
228
  {[1, 2, 3, 4, 5].map((n) => (
232
229
  <button
233
230
  key={n}
@@ -243,7 +240,7 @@ function ReviewEditor({
243
240
  </fieldset>
244
241
 
245
242
  <label className="block">
246
- Your review <span>(optional)</span>
243
+ {t('yourReview')} <span>({t('optionalField')})</span>
247
244
  <textarea
248
245
  value={body}
249
246
  onChange={(event) => setBody(event.target.value)}
@@ -258,11 +255,11 @@ function ReviewEditor({
258
255
  <button type="submit" disabled={submitting}>
259
256
  {submitting
260
257
  ? mode === 'edit'
261
- ? 'Saving…'
262
- : 'Submitting…'
258
+ ? t('saving')
259
+ : t('submittingBtn')
263
260
  : mode === 'edit'
264
- ? 'Save changes'
265
- : 'Submit review'}
261
+ ? t('saveChanges')
262
+ : t('submitReview')}
266
263
  </button>
267
264
  </form>
268
265
  );
@@ -1,10 +1,23 @@
1
1
  import type { ProductReview } from 'brainerce';
2
2
  import { getServerClient } from '@/core/lib/brainerce';
3
+ <% if (i18nEnabled) { %>import { getMessages, defaultLocale } from '@/i18n';<% } else { %>import { defaultLocale, messages } from '@/i18n';<% } %>
3
4
  import { ReviewForm } from './review-form';
4
5
 
6
+ // Server component — it renders before any client i18n context exists, so the
7
+ // wrapper strings (title / empty state / verified badge) are read straight
8
+ // from the `reviews` namespace in messages/, keyed by the store locale.
9
+ type ReviewStrings = Record<string, string>;
10
+
11
+ <% if (i18nEnabled) { %>async function getReviewStrings(locale: string): Promise<ReviewStrings> {
12
+ return ((await getMessages(locale)).reviews ?? {}) as ReviewStrings;
13
+ }<% } else { %>async function getReviewStrings(_locale: string): Promise<ReviewStrings> {
14
+ return messages.reviews as ReviewStrings;
15
+ }<% } %>
16
+
5
17
  interface ReviewsSectionProps {
6
18
  productId: string;
7
19
  initialReviews?: ProductReview[];
20
+ locale?: string;
8
21
  /** Where to send users back to after submit (defaults to the current product URL). */
9
22
  returnUrl?: string;
10
23
  }
@@ -16,7 +29,12 @@ interface ReviewsSectionProps {
16
29
  * the initial HTML payload (good for SEO; JSON-LD on the same page already
17
30
  * carries `aggregateRating` when reviewCount > 0).
18
31
  */
19
- export async function ReviewsSection({ productId, initialReviews }: ReviewsSectionProps) {
32
+ export async function ReviewsSection({
33
+ productId,
34
+ initialReviews,
35
+ locale = defaultLocale,
36
+ }: ReviewsSectionProps) {
37
+ const t = await getReviewStrings(locale);
20
38
  let reviews: ProductReview[] = initialReviews ?? [];
21
39
 
22
40
  if (!initialReviews) {
@@ -24,22 +42,22 @@ export async function ReviewsSection({ productId, initialReviews }: ReviewsSecti
24
42
  const result = await getServerClient().listProductReviews(productId, { page: 1, limit: 20 });
25
43
  reviews = result.data;
26
44
  } catch {
27
- // Reviews disabled at store level or network error — render the form anyway.
45
+ // Review listing disabled at store level or network error — render the form anyway.
28
46
  reviews = [];
29
47
  }
30
48
  }
31
49
 
32
50
  return (
33
51
  <section aria-labelledby="reviews-heading">
34
- {/* DESIGN ME — product reviews: server-fetched review list (SDK listProductReviews) + eligibility-aware submit form */}
35
- <h2 id="reviews-heading">Reviews</h2>
52
+ {/* DESIGN ME — product reviews: server-fetched review list (SDK listProductReviews) + eligibility-aware submit form. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
53
+ <h2 id="reviews-heading">{t.title}</h2>
36
54
 
37
55
  {reviews.length === 0 ? (
38
- <p>No reviews yet — be the first to share your experience.</p>
56
+ <p>{t.noReviews}</p>
39
57
  ) : (
40
58
  <ul>
41
59
  {reviews.map((review) => (
42
- <ReviewCard key={review.id} review={review} />
60
+ <ReviewCard key={review.id} review={review} verifiedLabel={t.verifiedPurchase} />
43
61
  ))}
44
62
  </ul>
45
63
  )}
@@ -49,14 +67,14 @@ export async function ReviewsSection({ productId, initialReviews }: ReviewsSecti
49
67
  );
50
68
  }
51
69
 
52
- function ReviewCard({ review }: { review: ProductReview }) {
70
+ function ReviewCard({ review, verifiedLabel }: { review: ProductReview; verifiedLabel: string }) {
53
71
  return (
54
72
  <li>
55
73
  <article>
56
74
  <header className="flex flex-wrap items-center gap-2">
57
75
  <Stars rating={review.rating} />
58
76
  <span>{review.authorName}</span>
59
- {review.verifiedPurchase && <span>Verified purchase</span>}
77
+ {review.verifiedPurchase && <span>{verifiedLabel}</span>}
60
78
  <time dateTime={review.createdAt}>{new Date(review.createdAt).toLocaleDateString()}</time>
61
79
  </header>
62
80
  {review.body && <p className="whitespace-pre-line break-words">{review.body}</p>}
@@ -14,6 +14,8 @@ interface StockBadgeProps {
14
14
  * DESIGN ME — stock status badge (in stock / low stock / out of stock);
15
15
  * derives the label from the product/variant `inventory` prop. Keep the
16
16
  * label logic; add your own tones per state.
17
+ *
18
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
17
19
  */
18
20
  export function StockBadge({ inventory, lowStockThreshold = 5, className }: StockBadgeProps) {
19
21
  const t = useTranslations('productDetail');
@@ -34,6 +34,8 @@ interface AttributeGroup {
34
34
  * (`displayType`, `swatchColor`, `swatchColor2`, `swatchImageUrl`) is
35
35
  * computed per value — the skeleton renders plain buttons, use the swatch
36
36
  * data to build color/image swatches in your design.
37
+ *
38
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
37
39
  */
38
40
  export function VariantSelector({
39
41
  product,