create-brainerce-store 1.50.5 → 1.51.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 (186) hide show
  1. package/dist/index.js +171 -66
  2. package/messages/en.json +4 -1
  3. package/messages/he.json +4 -1
  4. package/package.json +1 -1
  5. package/templates/nextjs/base/.claude/commands/design.md +51 -0
  6. package/templates/nextjs/base/.env.local.ejs +5 -0
  7. package/templates/nextjs/base/.eslintrc.json +57 -3
  8. package/templates/nextjs/base/AGENTS.md +41 -0
  9. package/templates/nextjs/base/AI-GUIDE.md +94 -0
  10. package/templates/nextjs/base/CLAUDE.md +42 -0
  11. package/templates/nextjs/base/TRANSLATIONS.md +1 -1
  12. package/templates/nextjs/base/next.config.ts +2 -2
  13. package/templates/nextjs/base/src/app/account/page.tsx +5 -5
  14. package/templates/nextjs/base/src/app/api/auth/logout/route.ts +1 -1
  15. package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +2 -2
  16. package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +1 -1
  17. package/templates/nextjs/base/src/app/auth/callback/page.tsx +4 -4
  18. package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +4 -4
  19. package/templates/nextjs/base/src/app/blog/page.tsx.ejs +2 -2
  20. package/templates/nextjs/base/src/app/cart/page.tsx +2 -173
  21. package/templates/nextjs/base/src/app/checkout/page.tsx +12 -11
  22. package/templates/nextjs/base/src/app/contact/page.tsx +3 -3
  23. package/templates/nextjs/base/src/app/faq/page.tsx.ejs +2 -2
  24. package/templates/nextjs/base/src/app/forgot-password/page.tsx +4 -4
  25. package/templates/nextjs/base/src/app/layout.tsx.ejs +12 -12
  26. package/templates/nextjs/base/src/app/login/page.tsx +4 -4
  27. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +19 -12
  28. package/templates/nextjs/base/src/app/page.tsx +4 -4
  29. package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +3 -3
  30. package/templates/nextjs/base/src/app/payment-complete/page.tsx +1 -1
  31. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -6
  32. package/templates/nextjs/base/src/app/products/page.tsx +4 -623
  33. package/templates/nextjs/base/src/app/register/page.tsx +4 -4
  34. package/templates/nextjs/base/src/app/reset-password/page.tsx +5 -5
  35. package/templates/nextjs/base/src/app/sitemap.ts +1 -1
  36. package/templates/nextjs/base/src/app/verify-email/page.tsx +5 -5
  37. package/templates/nextjs/base/src/components/account/address-book.tsx +3 -3
  38. package/templates/nextjs/base/src/components/account/order-customizations.tsx +3 -3
  39. package/templates/nextjs/base/src/components/account/order-history.tsx +13 -5
  40. package/templates/nextjs/base/src/components/account/order-payment-block.tsx +2 -2
  41. package/templates/nextjs/base/src/components/account/order-shipping-block.tsx +2 -2
  42. package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +2 -2
  43. package/templates/nextjs/base/src/components/account/profile-section.tsx +3 -3
  44. package/templates/nextjs/base/src/components/auth/login-form.tsx +4 -4
  45. package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +4 -4
  46. package/templates/nextjs/base/src/components/auth/register-form.tsx +4 -4
  47. package/templates/nextjs/base/src/components/brainerce-bot.tsx +2 -2
  48. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +24 -3
  49. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +2 -2
  50. package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +2 -2
  51. package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +4 -4
  52. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +6 -6
  53. package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +3 -3
  54. package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +3 -3
  55. package/templates/nextjs/base/src/components/checkout/tax-display.tsx +3 -3
  56. package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +2 -2
  57. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +3 -3
  58. package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +58 -0
  59. package/templates/nextjs/base/src/core/hooks/use-home-data.ts +51 -0
  60. package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +295 -0
  61. package/templates/nextjs/base/src/core/hooks/use-product-page.ts +278 -0
  62. package/templates/nextjs/base/src/{hooks → core/hooks}/use-search.ts +1 -1
  63. package/templates/nextjs/base/src/{lib → core/lib}/brainerce.ts.ejs +1 -1
  64. package/templates/nextjs/base/src/core/lib/product-options.ts +121 -0
  65. package/templates/nextjs/base/src/{lib → core/lib}/resolve-currency.ts +1 -1
  66. package/templates/nextjs/base/src/{lib → core/lib}/safe-redirect.ts +1 -1
  67. package/templates/nextjs/base/src/{lib → core/lib}/sanitize.ts.ejs +1 -1
  68. package/templates/nextjs/base/src/{lib → core/lib}/use-currency.ts +1 -1
  69. package/templates/nextjs/base/src/{providers → core/providers}/store-provider.tsx.ejs +4 -4
  70. package/templates/nextjs/base/src/{components → ui}/cart/cart-bundle-offer.tsx +5 -5
  71. package/templates/nextjs/base/src/{components → ui}/cart/cart-item.tsx +6 -6
  72. package/templates/nextjs/base/src/{components → ui}/cart/cart-nudges.tsx +1 -1
  73. package/templates/nextjs/base/src/{components → ui}/cart/cart-summary.tsx +4 -4
  74. package/templates/nextjs/base/src/{components → ui}/cart/cart-upgrade-banner.tsx +5 -5
  75. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +149 -0
  76. package/templates/nextjs/base/src/{components → ui}/cart/coupon-input.tsx +4 -4
  77. package/templates/nextjs/base/src/{components → ui}/cart/free-shipping-bar.tsx +4 -4
  78. package/templates/nextjs/base/src/{components → ui}/cart/reservation-countdown.tsx +2 -2
  79. package/templates/nextjs/base/src/ui/home/discount-banner-strip.tsx +20 -0
  80. package/templates/nextjs/base/src/ui/home/featured-products-section.tsx +24 -0
  81. package/templates/nextjs/base/src/ui/home/hero-section.tsx +29 -0
  82. package/templates/nextjs/base/src/ui/home/home-client.tsx +29 -0
  83. package/templates/nextjs/base/src/{components/content → ui/layout}/faq-section.tsx.ejs +2 -2
  84. package/templates/nextjs/base/src/{components/content → ui/layout}/header-account.tsx +3 -3
  85. package/templates/nextjs/base/src/{components/content → ui/layout}/rich-text-block.tsx.ejs +1 -1
  86. package/templates/nextjs/base/src/{components/content → ui/layout}/site-header.tsx.ejs +1 -1
  87. package/templates/nextjs/base/src/{components/products → ui/product}/customization-fields.tsx +18 -51
  88. package/templates/nextjs/base/src/{components/products → ui/product}/discount-badge.tsx +1 -1
  89. package/templates/nextjs/base/src/{components/products → ui/product}/frequently-bought-together.tsx +6 -6
  90. package/templates/nextjs/base/src/{components/products → ui/product}/modifier-group-selector.tsx +10 -73
  91. package/templates/nextjs/base/src/{components/shared → ui/product}/price-display.tsx +2 -2
  92. package/templates/nextjs/base/src/{components/products → ui/product}/product-card.tsx +10 -10
  93. package/templates/nextjs/base/src/{app/products/[slug] → ui/product}/product-client-section.tsx +45 -206
  94. package/templates/nextjs/base/src/{components/products → ui/product}/product-grid.tsx +3 -3
  95. package/templates/nextjs/base/src/ui/product/product-listing.tsx +429 -0
  96. package/templates/nextjs/base/src/{components/products → ui/product}/recommendation-section.tsx +4 -4
  97. package/templates/nextjs/base/src/{components/reviews → ui/product}/review-form.tsx +2 -2
  98. package/templates/nextjs/base/src/{components/reviews → ui/product}/reviews-section.tsx +1 -1
  99. package/templates/nextjs/base/src/{components/products → ui/product}/stock-badge.tsx +2 -2
  100. package/templates/nextjs/base/src/{components/products → ui/product}/variant-selector.tsx +3 -3
  101. package/templates/nextjs/base/src/{components → ui}/shared/cdn-image.tsx +1 -1
  102. package/templates/nextjs/base/src/{components → ui}/shared/loading-spinner.tsx +2 -2
  103. package/templates/nextjs/base/tailwind.config.ts +12 -0
  104. package/templates/nextjs/designs/passepartout/app-overlay/layout.tsx.ejs +253 -0
  105. package/templates/nextjs/designs/passepartout/globals.css +412 -0
  106. package/templates/nextjs/designs/passepartout/messages-patch/en.json +87 -0
  107. package/templates/nextjs/designs/passepartout/messages-patch/he.json +87 -0
  108. package/templates/nextjs/designs/passepartout/ui/cart/cart-bundle-offer.tsx +111 -0
  109. package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +39 -0
  110. package/templates/nextjs/designs/passepartout/ui/cart/cart-upgrade-banner.tsx +141 -0
  111. package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +149 -0
  112. package/templates/nextjs/designs/passepartout/ui/cart/free-shipping-bar.tsx +62 -0
  113. package/templates/nextjs/designs/passepartout/ui/cart/reservation-countdown.tsx +103 -0
  114. package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +401 -0
  115. package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +139 -0
  116. package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +45 -0
  117. package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +103 -0
  118. package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +159 -0
  119. package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +155 -0
  120. package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +60 -0
  121. package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +34 -0
  122. package/templates/nextjs/designs/passepartout/ui/product/discount-badge.tsx +22 -0
  123. package/templates/nextjs/designs/passepartout/ui/product/frequently-bought-together.tsx +205 -0
  124. package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +111 -0
  125. package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +265 -0
  126. package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +667 -0
  127. package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +35 -0
  128. package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +427 -0
  129. package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +113 -0
  130. package/templates/nextjs/designs/passepartout/ui/product/review-form.tsx +294 -0
  131. package/templates/nextjs/designs/passepartout/ui/product/reviews-section.tsx.ejs +96 -0
  132. package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +52 -0
  133. package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +307 -0
  134. package/templates/nextjs/designs/passepartout/ui/shared/fly-to-cart.ts +85 -0
  135. package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +33 -0
  136. package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +87 -0
  137. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +121 -0
  138. package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +25 -0
  139. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +97 -0
  140. package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +103 -0
  141. package/templates/nextjs/ui-canvas/cart/cart-view.tsx +106 -0
  142. package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +117 -0
  143. package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +52 -0
  144. package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +79 -0
  145. package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +19 -0
  146. package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +23 -0
  147. package/templates/nextjs/ui-canvas/home/hero-section.tsx +21 -0
  148. package/templates/nextjs/ui-canvas/home/home-client.tsx +26 -0
  149. package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +101 -0
  150. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +71 -0
  151. package/templates/nextjs/ui-canvas/layout/header-account.tsx +31 -0
  152. package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +74 -0
  153. package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +34 -0
  154. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +82 -0
  155. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +119 -0
  156. package/templates/nextjs/ui-canvas/product/customization-fields.tsx +443 -0
  157. package/templates/nextjs/ui-canvas/product/discount-badge.tsx +17 -0
  158. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +166 -0
  159. package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +163 -0
  160. package/templates/nextjs/ui-canvas/product/price-display.tsx +50 -0
  161. package/templates/nextjs/ui-canvas/product/product-card.tsx +197 -0
  162. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +352 -0
  163. package/templates/nextjs/ui-canvas/product/product-grid.tsx +27 -0
  164. package/templates/nextjs/ui-canvas/product/product-listing.tsx +224 -0
  165. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +95 -0
  166. package/templates/nextjs/ui-canvas/product/review-form.tsx +269 -0
  167. package/templates/nextjs/ui-canvas/product/reviews-section.tsx +78 -0
  168. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +43 -0
  169. package/templates/nextjs/ui-canvas/product/variant-selector.tsx +210 -0
  170. package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +29 -0
  171. package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +25 -0
  172. package/templates/nextjs/base/src/app/home-client.tsx +0 -98
  173. /package/templates/nextjs/base/src/{lib → core/lib}/auth.ts +0 -0
  174. /package/templates/nextjs/base/src/{lib → core/lib}/csrf.ts +0 -0
  175. /package/templates/nextjs/base/src/{lib → core/lib}/image-hosts.ts +0 -0
  176. /package/templates/nextjs/base/src/{lib → core/lib}/navigation.tsx.ejs +0 -0
  177. /package/templates/nextjs/base/src/{lib → core/lib}/nonce.ts +0 -0
  178. /package/templates/nextjs/base/src/{lib → core/lib}/sanitize-html.ts +0 -0
  179. /package/templates/nextjs/base/src/{lib → core/lib}/seo.ts +0 -0
  180. /package/templates/nextjs/base/src/{lib → core/lib}/store-info.ts +0 -0
  181. /package/templates/nextjs/base/src/{lib → core/lib}/translations.ts.ejs +0 -0
  182. /package/templates/nextjs/base/src/{lib → core/lib}/utils.ts +0 -0
  183. /package/templates/nextjs/base/src/{lib → core/lib}/validation.ts +0 -0
  184. /package/templates/nextjs/base/src/{components/content → ui/layout}/announcement-bar.tsx.ejs +0 -0
  185. /package/templates/nextjs/base/src/{components → ui}/layout/language-switcher.tsx.ejs +0 -0
  186. /package/templates/nextjs/base/src/{components/content → ui/layout}/site-footer.tsx.ejs +0 -0
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { useRouter, Link } from '@/lib/navigation';
5
- import { useAuth } from '@/providers/store-provider';
6
- import { proxyRegister } from '@/lib/auth';
4
+ import { useRouter, Link } from '@/core/lib/navigation';
5
+ import { useAuth } from '@/core/providers/store-provider';
6
+ import { proxyRegister } from '@/core/lib/auth';
7
7
  import { RegisterForm } from '@/components/auth/register-form';
8
8
  import { OAuthButtons } from '@/components/auth/oauth-buttons';
9
- import { useTranslations } from '@/lib/translations';
9
+ import { useTranslations } from '@/core/lib/translations';
10
10
 
11
11
  export default function RegisterPage() {
12
12
  const router = useRouter();
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { useRouter, Link } from '@/lib/navigation';
5
- import { proxyResetPassword } from '@/lib/auth';
6
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
- import { useTranslations } from '@/lib/translations';
8
- import { getPasswordError } from '@/lib/validation';
4
+ import { useRouter, Link } from '@/core/lib/navigation';
5
+ import { proxyResetPassword } from '@/core/lib/auth';
6
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
7
+ import { useTranslations } from '@/core/lib/translations';
8
+ import { getPasswordError } from '@/core/lib/validation';
9
9
 
10
10
  export default function ResetPasswordPage() {
11
11
  const router = useRouter();
@@ -1,5 +1,5 @@
1
1
  import type { MetadataRoute } from 'next';
2
- import { getServerClient } from '@/lib/brainerce';
2
+ import { getServerClient } from '@/core/lib/brainerce';
3
3
 
4
4
  export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
5
5
  const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { Suspense, useState, useRef, useEffect, useCallback } from 'react';
4
- import { useRouter, Link } from '@/lib/navigation';
5
- import { useAuth } from '@/providers/store-provider';
6
- import { proxyVerifyEmail, proxyResendVerification } from '@/lib/auth';
7
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
8
- import { useTranslations } from '@/lib/translations';
4
+ import { useRouter, Link } from '@/core/lib/navigation';
5
+ import { useAuth } from '@/core/providers/store-provider';
6
+ import { proxyVerifyEmail, proxyResendVerification } from '@/core/lib/auth';
7
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
8
+ import { useTranslations } from '@/core/lib/translations';
9
9
 
10
10
  const CODE_LENGTH = 6;
11
11
  const RESEND_COOLDOWN_SECONDS = 60;
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import type { CustomerAddress, CreateAddressDto } from 'brainerce';
5
- import { getClient } from '@/lib/brainerce';
6
- import { useTranslations } from '@/lib/translations';
7
- import { cn } from '@/lib/utils';
5
+ import { getClient } from '@/core/lib/brainerce';
6
+ import { useTranslations } from '@/core/lib/translations';
7
+ import { cn } from '@/core/lib/utils';
8
8
 
9
9
  interface AddressBookProps {
10
10
  addresses: CustomerAddress[];
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
- import { CdnImage as Image } from '@/components/shared/cdn-image';
3
+ import { CdnImage as Image } from '@/ui/shared/cdn-image';
4
4
  import type { OrderItem } from 'brainerce';
5
- import { useTranslations } from '@/lib/translations';
6
- import { cn } from '@/lib/utils';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { cn } from '@/core/lib/utils';
7
7
 
8
8
  type Customizations = NonNullable<OrderItem['customizations']>;
9
9
  type CustomizationEntry = Customizations[string];
@@ -1,13 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
- import { CdnImage as Image } from '@/components/shared/cdn-image';
4
+ import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
5
  import type { Order, OrderStatus, OrderDownloadLink } from 'brainerce';
6
6
  import { formatPrice } from 'brainerce';
7
- import { getClient } from '@/lib/brainerce';
8
- import { useCurrency } from '@/lib/use-currency';
9
- import { useTranslations } from '@/lib/translations';
10
- import { cn } from '@/lib/utils';
7
+ import { getClient } from '@/core/lib/brainerce';
8
+ import { useCurrency } from '@/core/lib/use-currency';
9
+ import { useTranslations } from '@/core/lib/translations';
10
+ import { cn } from '@/core/lib/utils';
11
11
  import { OrderCustomizations } from './order-customizations';
12
12
  import { OrderStatusTimeline } from './order-status-timeline';
13
13
  import { OrderShippingBlock } from './order-shipping-block';
@@ -207,6 +207,14 @@ function OrderCard({ order }: { order: Order }) {
207
207
  <OrderShippingBlock order={order} />
208
208
  <OrderPaymentBlock order={order} />
209
209
 
210
+ {/* The shopper's own checkout note, read-only */}
211
+ {order.notes && (
212
+ <div className="border-border border-t pt-2">
213
+ <p className="text-foreground text-sm font-medium">{t('orderNotes')}</p>
214
+ <p className="text-muted-foreground text-sm whitespace-pre-wrap">{order.notes}</p>
215
+ </div>
216
+ )}
217
+
210
218
  {/* Downloads section */}
211
219
  {order.hasDownloads && <OrderDownloads orderId={order.id} />}
212
220
 
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import type { Order } from 'brainerce';
4
- import { useTranslations } from '@/lib/translations';
5
- import { cn } from '@/lib/utils';
4
+ import { useTranslations } from '@/core/lib/translations';
5
+ import { cn } from '@/core/lib/utils';
6
6
 
7
7
  interface OrderPaymentBlockProps {
8
8
  order: Order;
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import type { Order } from 'brainerce';
4
- import { useTranslations } from '@/lib/translations';
5
- import { cn } from '@/lib/utils';
4
+ import { useTranslations } from '@/core/lib/translations';
5
+ import { cn } from '@/core/lib/utils';
6
6
 
7
7
  interface OrderShippingBlockProps {
8
8
  order: Order;
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import type { Order, OrderStatus } from 'brainerce';
4
- import { useTranslations } from '@/lib/translations';
5
- import { cn } from '@/lib/utils';
4
+ import { useTranslations } from '@/core/lib/translations';
5
+ import { cn } from '@/core/lib/utils';
6
6
 
7
7
  const STATUS_KEYS: Record<
8
8
  OrderStatus,
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import type { CustomerProfile } from 'brainerce';
5
- import { getClient } from '@/lib/brainerce';
6
- import { useTranslations } from '@/lib/translations';
7
- import { cn } from '@/lib/utils';
5
+ import { getClient } from '@/core/lib/brainerce';
6
+ import { useTranslations } from '@/core/lib/translations';
7
+ import { cn } from '@/core/lib/utils';
8
8
 
9
9
  interface ProfileSectionProps {
10
10
  profile: CustomerProfile;
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { Link } from '@/lib/navigation';
5
- import { useTranslations } from '@/lib/translations';
6
- import { cn } from '@/lib/utils';
7
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
4
+ import { Link } from '@/core/lib/navigation';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { cn } from '@/core/lib/utils';
7
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
8
8
 
9
9
  interface LoginFormProps {
10
10
  onSubmit: (email: string, password: string) => Promise<void>;
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
4
  import type { CustomerOAuthProvider } from 'brainerce';
5
- import { getClient } from '@/lib/brainerce';
6
- import { useTranslations } from '@/lib/translations';
7
- import { cn } from '@/lib/utils';
8
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
5
+ import { getClient } from '@/core/lib/brainerce';
6
+ import { useTranslations } from '@/core/lib/translations';
7
+ import { cn } from '@/core/lib/utils';
8
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
9
9
 
10
10
  const PROVIDER_CONFIG: Record<CustomerOAuthProvider, { labelKey: string; icon: React.ReactNode }> =
11
11
  {
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useMemo } from 'react';
4
- import { useTranslations } from '@/lib/translations';
5
- import { cn } from '@/lib/utils';
6
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
- import { getPasswordError } from '@/lib/validation';
4
+ import { useTranslations } from '@/core/lib/translations';
5
+ import { cn } from '@/core/lib/utils';
6
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
7
+ import { getPasswordError } from '@/core/lib/validation';
8
8
 
9
9
  interface RegisterData {
10
10
  firstName: string;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useRef } from 'react';
4
4
 
5
- import { useCart } from '@/providers/store-provider';
5
+ import { useCart } from '@/core/providers/store-provider';
6
6
 
7
7
  /**
8
8
  * Mounts the Brainerce AI chat widget (the store's shopping assistant).
@@ -36,7 +36,7 @@ export function BrainerceBotWidget() {
36
36
  baseUrl: process.env.NEXT_PUBLIC_BRAINERCE_API_URL || undefined,
37
37
  onAddToCart: async ({ productId, variantId, quantity }) => {
38
38
  try {
39
- const { getClient } = await import('@/lib/brainerce');
39
+ const { getClient } = await import('@/core/lib/brainerce');
40
40
  await getClient().smartAddToCart({
41
41
  productId,
42
42
  variantId: variantId ?? undefined,
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
4
  import type { SetShippingAddressDto, ShippingDestinations } from 'brainerce';
5
- import { useTranslations } from '@/lib/translations';
6
- import { cn } from '@/lib/utils';
7
- import { isValidEmail } from '@/lib/validation';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { cn } from '@/core/lib/utils';
7
+ import { isValidEmail } from '@/core/lib/validation';
8
8
 
9
9
  interface CheckoutConsent {
10
10
  acceptsMarketing: boolean;
@@ -41,6 +41,7 @@ export function CheckoutForm({
41
41
  postalCode: initialValues?.postalCode || '',
42
42
  country: initialValues?.country || '',
43
43
  phone: initialValues?.phone || '',
44
+ notes: initialValues?.notes || '',
44
45
  });
45
46
  const [errors, setErrors] = useState<Record<string, string>>({});
46
47
  const [privacyAccepted, setPrivacyAccepted] = useState(false);
@@ -65,6 +66,7 @@ export function CheckoutForm({
65
66
  postalCode: initialValues.postalCode || prev.postalCode,
66
67
  country: initialValues.country || prev.country,
67
68
  phone: initialValues.phone || prev.phone || '',
69
+ notes: prev.notes || '',
68
70
  }));
69
71
  }, [initialValues]);
70
72
 
@@ -346,6 +348,25 @@ export function CheckoutForm({
346
348
  </>
347
349
  )}
348
350
 
351
+ {/* Order notes (optional) */}
352
+ <div>
353
+ <label htmlFor="orderNotes" className="text-foreground mb-1 block text-sm font-medium">
354
+ {t('orderNotes')}
355
+ </label>
356
+ <textarea
357
+ id="orderNotes"
358
+ value={formData.notes || ''}
359
+ onChange={(e) => updateField('notes', e.target.value)}
360
+ maxLength={2000}
361
+ rows={3}
362
+ className={cn(
363
+ inputClass,
364
+ 'border-border h-auto min-h-[80px] resize-y py-2 leading-relaxed'
365
+ )}
366
+ placeholder={t('orderNotesPlaceholder')}
367
+ />
368
+ </div>
369
+
349
370
  {/* Privacy Policy (required) */}
350
371
  <div>
351
372
  <label className="flex cursor-pointer items-start gap-2">
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import type { CheckoutCustomFieldDefinition } from 'brainerce';
5
- import { useTranslations } from '@/lib/translations';
6
- import { cn } from '@/lib/utils';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { cn } from '@/core/lib/utils';
7
7
 
8
8
  interface CustomFieldsStepProps {
9
9
  fields: CheckoutCustomFieldDefinition[];
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useTranslations } from '@/lib/translations';
4
- import { cn } from '@/lib/utils';
3
+ import { useTranslations } from '@/core/lib/translations';
4
+ import { cn } from '@/core/lib/utils';
5
5
 
6
6
  interface DeliveryMethodStepProps {
7
7
  onSelect: (method: 'shipping' | 'pickup') => void;
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useMemo } from 'react';
4
- import { CdnImage as Image } from '@/components/shared/cdn-image';
4
+ import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
5
  import type { OrderBump, RecommendationVariant } from 'brainerce';
6
6
  import { formatPrice, getVariantOptions } from 'brainerce';
7
- import { useCurrency } from '@/lib/use-currency';
8
- import { useTranslations } from '@/lib/translations';
9
- import { cn } from '@/lib/utils';
7
+ import { useCurrency } from '@/core/lib/use-currency';
8
+ import { useTranslations } from '@/core/lib/translations';
9
+ import { cn } from '@/core/lib/utils';
10
10
 
11
11
  interface OrderBumpCardProps {
12
12
  bump: OrderBump;
@@ -3,12 +3,12 @@
3
3
  import { useEffect, useState, useRef, useCallback, type CSSProperties } from 'react';
4
4
  import type { PaymentIntent, PaymentClientSdk } from 'brainerce';
5
5
  import { formatPrice } from 'brainerce';
6
- import { getClient } from '@/lib/brainerce';
7
- import { useTranslations } from '@/lib/translations';
8
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
9
- import { useStoreInfo } from '@/providers/store-provider';
10
- import { cn } from '@/lib/utils';
11
- import { isAllowedPaymentUrl, isValidCheckoutId, safePaymentRedirect } from '@/lib/safe-redirect';
6
+ import { getClient } from '@/core/lib/brainerce';
7
+ import { useTranslations } from '@/core/lib/translations';
8
+ import { LoadingSpinner } from '@/ui/shared/loading-spinner';
9
+ import { useStoreInfo } from '@/core/providers/store-provider';
10
+ import { cn } from '@/core/lib/utils';
11
+ import { isAllowedPaymentUrl, isValidCheckoutId, safePaymentRedirect } from '@/core/lib/safe-redirect';
12
12
 
13
13
  /**
14
14
  * Backward-compat defaults when backend doesn't return clientSdk.
@@ -3,9 +3,9 @@
3
3
  import { useState } from 'react';
4
4
  import type { PickupLocation } from 'brainerce';
5
5
  import { formatPrice } from 'brainerce';
6
- import { useTranslations } from '@/lib/translations';
7
- import { useCurrency } from '@/lib/use-currency';
8
- import { cn } from '@/lib/utils';
6
+ import { useTranslations } from '@/core/lib/translations';
7
+ import { useCurrency } from '@/core/lib/use-currency';
8
+ import { cn } from '@/core/lib/utils';
9
9
 
10
10
  interface PickupStepProps {
11
11
  locations: PickupLocation[];
@@ -2,9 +2,9 @@
2
2
 
3
3
  import type { ShippingRate } from 'brainerce';
4
4
  import { formatPrice } from 'brainerce';
5
- import { useTranslations } from '@/lib/translations';
6
- import { useCurrency } from '@/lib/use-currency';
7
- import { cn } from '@/lib/utils';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { useCurrency } from '@/core/lib/use-currency';
7
+ import { cn } from '@/core/lib/utils';
8
8
 
9
9
  interface ShippingStepProps {
10
10
  rates: ShippingRate[];
@@ -2,9 +2,9 @@
2
2
 
3
3
  import type { TaxBreakdown } from 'brainerce';
4
4
  import { formatPrice } from 'brainerce';
5
- import { useTranslations } from '@/lib/translations';
6
- import { useCurrency } from '@/lib/use-currency';
7
- import { cn } from '@/lib/utils';
5
+ import { useTranslations } from '@/core/lib/translations';
6
+ import { useCurrency } from '@/core/lib/use-currency';
7
+ import { cn } from '@/core/lib/utils';
8
8
 
9
9
  interface TaxDisplayProps {
10
10
  /** Whether shipping address has been set */
@@ -1,6 +1,6 @@
1
1
  import type { StoreInfo } from 'brainerce';
2
- import { getNonce } from '@/lib/nonce';
3
- import { stripHtmlForSeo } from '@/lib/seo';
2
+ import { getNonce } from '@/core/lib/nonce';
3
+ import { stripHtmlForSeo } from '@/core/lib/seo';
4
4
 
5
5
  interface OrganizationJsonLdProps {
6
6
  storeInfo: StoreInfo;
@@ -1,8 +1,8 @@
1
1
  import type { Product } from 'brainerce';
2
2
  import { getProductPriceInfo } from 'brainerce';
3
- import { getNonce } from '@/lib/nonce';
4
- import { resolveCurrency } from '@/lib/resolve-currency';
5
- import { stripHtmlForSeo } from '@/lib/seo';
3
+ import { getNonce } from '@/core/lib/nonce';
4
+ import { resolveCurrency } from '@/core/lib/resolve-currency';
5
+ import { stripHtmlForSeo } from '@/core/lib/seo';
6
6
 
7
7
  interface ProductJsonLdProps {
8
8
  product: Product;
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import type {
5
+ Cart,
6
+ CartRecommendationsResponse,
7
+ CartUpgradesResponse,
8
+ CartBundlesResponse,
9
+ } from 'brainerce';
10
+ import { getClient } from '@/core/lib/brainerce';
11
+ import { useCart } from '@/core/providers/store-provider';
12
+
13
+ export interface UseCartPageResult {
14
+ cart: Cart | null;
15
+ cartLoading: boolean;
16
+ refreshCart: () => Promise<void>;
17
+ itemCount: number;
18
+ /** Cross-sell recommendations for the current cart contents. */
19
+ cartRecs: CartRecommendationsResponse | null;
20
+ /** Per-item upgrade suggestions keyed by productId. */
21
+ upgrades: CartUpgradesResponse | null;
22
+ /** Bundle offers matching the current cart. */
23
+ bundles: CartBundlesResponse | null;
24
+ }
25
+
26
+ /**
27
+ * Cart-page behavior: the shared cart state (from StoreProvider) plus the
28
+ * enrichment fetch (recommendations / upgrades / bundles) that runs whenever
29
+ * the cart contents change. Pure data/behavior — rendering lives in
30
+ * `ui/cart/cart-view.tsx`.
31
+ */
32
+ export function useCartPage(): UseCartPageResult {
33
+ const { cart, cartLoading, refreshCart, itemCount } = useCart();
34
+ const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
35
+ const [upgrades, setUpgrades] = useState<CartUpgradesResponse | null>(null);
36
+ const [bundles, setBundles] = useState<CartBundlesResponse | null>(null);
37
+
38
+ // Load recommendations, upgrades, and bundles in a single request
39
+ useEffect(() => {
40
+ if (!cart?.id || cart.items.length === 0) {
41
+ setCartRecs(null);
42
+ setUpgrades(null);
43
+ setBundles(null);
44
+ return;
45
+ }
46
+ const client = getClient();
47
+ client
48
+ .getCart(cart.id, { include: ['recommendations', 'upgrades', 'bundles'] })
49
+ .then((enriched) => {
50
+ setCartRecs(enriched.recommendations ?? null);
51
+ setUpgrades(enriched.upgrades ?? null);
52
+ setBundles(enriched.bundles ?? null);
53
+ })
54
+ .catch(() => {});
55
+ }, [cart?.id, cart?.items.length]);
56
+
57
+ return { cart, cartLoading, refreshCart, itemCount, cartRecs, upgrades, bundles };
58
+ }
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import type { Product, DiscountBanner } from 'brainerce';
5
+ import { getClient } from '@/core/lib/brainerce';
6
+
7
+ export interface UseHomeDataResult {
8
+ /** Newest products for the featured grid (max 8). */
9
+ products: Product[];
10
+ /** Active store-wide discount banners. */
11
+ banners: DiscountBanner[];
12
+ /** True while the initial fetch is in flight. */
13
+ loading: boolean;
14
+ }
15
+
16
+ /**
17
+ * Homepage data: featured products + discount banners.
18
+ * Pure data/behavior — rendering lives in `ui/home/`.
19
+ */
20
+ export function useHomeData(): UseHomeDataResult {
21
+ const [products, setProducts] = useState<Product[]>([]);
22
+ const [banners, setBanners] = useState<DiscountBanner[]>([]);
23
+ const [loading, setLoading] = useState(true);
24
+
25
+ useEffect(() => {
26
+ async function load() {
27
+ try {
28
+ const client = getClient();
29
+ const [productsRes, bannersRes] = await Promise.allSettled([
30
+ client.getProducts({ limit: 8, sortBy: 'createdAt', sortOrder: 'desc' }),
31
+ client.getDiscountBanners(),
32
+ ]);
33
+
34
+ if (productsRes.status === 'fulfilled') {
35
+ setProducts(productsRes.value.data);
36
+ }
37
+ if (bannersRes.status === 'fulfilled') {
38
+ setBanners(bannersRes.value);
39
+ }
40
+ } catch (err) {
41
+ console.error('Failed to load home page data:', err);
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ }
46
+
47
+ load();
48
+ }, []);
49
+
50
+ return { products, banners, loading };
51
+ }