create-brainerce-store 1.50.5 → 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 (230) hide show
  1. package/README.md +72 -0
  2. package/dist/index.js +146 -100
  3. package/messages/en.json +36 -1
  4. package/messages/he.json +36 -1
  5. package/package.json +1 -1
  6. package/templates/nextjs/base/.claude/commands/design.md +60 -0
  7. package/templates/nextjs/base/.env.local.ejs +5 -0
  8. package/templates/nextjs/base/.eslintrc.json +57 -3
  9. package/templates/nextjs/base/AGENTS.md +41 -0
  10. package/templates/nextjs/base/AI-GUIDE.md +143 -0
  11. package/templates/nextjs/base/CLAUDE.md +52 -0
  12. package/templates/nextjs/base/TRANSLATIONS.md +1 -1
  13. package/templates/nextjs/base/next.config.ts +2 -2
  14. package/templates/nextjs/base/package.json.ejs +52 -39
  15. package/templates/nextjs/base/src/app/account/page.tsx +5 -5
  16. package/templates/nextjs/base/src/app/api/auth/logout/route.ts +1 -1
  17. package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +2 -2
  18. package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +1 -1
  19. package/templates/nextjs/base/src/app/auth/callback/page.tsx +4 -4
  20. package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +4 -4
  21. package/templates/nextjs/base/src/app/blog/page.tsx.ejs +2 -2
  22. package/templates/nextjs/base/src/app/cart/page.tsx +2 -173
  23. package/templates/nextjs/base/src/app/checkout/page.tsx +12 -11
  24. package/templates/nextjs/base/src/app/contact/page.tsx +3 -3
  25. package/templates/nextjs/base/src/app/faq/page.tsx.ejs +2 -2
  26. package/templates/nextjs/base/src/app/forgot-password/page.tsx +4 -4
  27. package/templates/nextjs/base/src/app/globals.css +36 -30
  28. package/templates/nextjs/base/src/app/layout.tsx.ejs +12 -12
  29. package/templates/nextjs/base/src/app/login/page.tsx +4 -4
  30. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +19 -12
  31. package/templates/nextjs/base/src/app/page.tsx +4 -4
  32. package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +3 -3
  33. package/templates/nextjs/base/src/app/payment-complete/page.tsx +1 -1
  34. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -6
  35. package/templates/nextjs/base/src/app/products/page.tsx +4 -623
  36. package/templates/nextjs/base/src/app/register/page.tsx +4 -4
  37. package/templates/nextjs/base/src/app/reset-password/page.tsx +5 -5
  38. package/templates/nextjs/base/src/app/sitemap.ts +1 -1
  39. package/templates/nextjs/base/src/app/verify-email/page.tsx +5 -5
  40. package/templates/nextjs/base/src/components/account/address-book.tsx +3 -3
  41. package/templates/nextjs/base/src/components/account/order-customizations.tsx +3 -3
  42. package/templates/nextjs/base/src/components/account/order-history.tsx +13 -5
  43. package/templates/nextjs/base/src/components/account/order-payment-block.tsx +2 -2
  44. package/templates/nextjs/base/src/components/account/order-shipping-block.tsx +2 -2
  45. package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +2 -2
  46. package/templates/nextjs/base/src/components/account/profile-section.tsx +3 -3
  47. package/templates/nextjs/base/src/components/auth/login-form.tsx +4 -4
  48. package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +4 -4
  49. package/templates/nextjs/base/src/components/auth/register-form.tsx +4 -4
  50. package/templates/nextjs/base/src/components/brainerce-bot.tsx +2 -2
  51. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +24 -3
  52. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +2 -2
  53. package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +2 -2
  54. package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +4 -4
  55. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +20 -16
  56. package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +3 -3
  57. package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +3 -3
  58. package/templates/nextjs/base/src/components/checkout/tax-display.tsx +3 -3
  59. package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +2 -2
  60. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +3 -3
  61. package/templates/nextjs/base/src/components/ui/accordion.tsx +53 -0
  62. package/templates/nextjs/base/src/components/ui/badge.tsx +32 -0
  63. package/templates/nextjs/base/src/components/ui/button.tsx +49 -0
  64. package/templates/nextjs/base/src/components/ui/card.tsx +55 -0
  65. package/templates/nextjs/base/src/components/ui/checkbox.tsx +28 -0
  66. package/templates/nextjs/base/src/components/ui/dialog.tsx +107 -0
  67. package/templates/nextjs/base/src/components/ui/input.tsx +22 -0
  68. package/templates/nextjs/base/src/components/ui/label.tsx +21 -0
  69. package/templates/nextjs/base/src/components/ui/radio-group.tsx +38 -0
  70. package/templates/nextjs/base/src/components/ui/select.tsx +153 -0
  71. package/templates/nextjs/base/src/components/ui/separator.tsx +26 -0
  72. package/templates/nextjs/base/src/components/ui/sheet.tsx +125 -0
  73. package/templates/nextjs/base/src/components/ui/skeleton.tsx +7 -0
  74. package/templates/nextjs/base/src/components/ui/tabs.tsx +55 -0
  75. package/templates/nextjs/base/src/components/ui/textarea.tsx +21 -0
  76. package/templates/nextjs/base/src/components/ui/tooltip.tsx +32 -0
  77. package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +58 -0
  78. package/templates/nextjs/base/src/core/hooks/use-home-data.ts +51 -0
  79. package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +295 -0
  80. package/templates/nextjs/base/src/core/hooks/use-product-page.ts +278 -0
  81. package/templates/nextjs/base/src/{hooks → core/hooks}/use-search.ts +1 -1
  82. package/templates/nextjs/base/src/{lib → core/lib}/brainerce.ts.ejs +1 -1
  83. package/templates/nextjs/base/src/core/lib/product-options.ts +121 -0
  84. package/templates/nextjs/base/src/{lib → core/lib}/resolve-currency.ts +1 -1
  85. package/templates/nextjs/base/src/{lib → core/lib}/safe-redirect.ts +1 -1
  86. package/templates/nextjs/base/src/{lib → core/lib}/sanitize.ts.ejs +1 -1
  87. package/templates/nextjs/base/src/{lib → core/lib}/use-currency.ts +1 -1
  88. package/templates/nextjs/base/src/{providers → core/providers}/store-provider.tsx.ejs +4 -4
  89. package/templates/nextjs/base/src/{components → ui}/cart/cart-bundle-offer.tsx +22 -16
  90. package/templates/nextjs/base/src/{components → ui}/cart/cart-item.tsx +8 -14
  91. package/templates/nextjs/base/src/{components → ui}/cart/cart-nudges.tsx +28 -39
  92. package/templates/nextjs/base/src/{components → ui}/cart/cart-summary.tsx +7 -5
  93. package/templates/nextjs/base/src/{components → ui}/cart/cart-upgrade-banner.tsx +13 -28
  94. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +140 -0
  95. package/templates/nextjs/base/src/{components → ui}/cart/coupon-input.tsx +14 -25
  96. package/templates/nextjs/base/src/{components → ui}/cart/free-shipping-bar.tsx +6 -7
  97. package/templates/nextjs/base/src/{components → ui}/cart/reservation-countdown.tsx +95 -103
  98. package/templates/nextjs/base/src/ui/home/discount-banner-strip.tsx +20 -0
  99. package/templates/nextjs/base/src/ui/home/featured-products-section.tsx +24 -0
  100. package/templates/nextjs/base/src/ui/home/hero-section.tsx +27 -0
  101. package/templates/nextjs/base/src/ui/home/home-client.tsx +29 -0
  102. package/templates/nextjs/base/src/{components/content → ui/layout}/announcement-bar.tsx.ejs +116 -125
  103. package/templates/nextjs/base/src/ui/layout/faq-section.tsx.ejs +90 -0
  104. package/templates/nextjs/base/src/{components/content → ui/layout}/header-account.tsx +5 -20
  105. package/templates/nextjs/base/src/ui/layout/language-switcher.tsx.ejs +80 -0
  106. package/templates/nextjs/base/src/{components/content → ui/layout}/rich-text-block.tsx.ejs +1 -1
  107. package/templates/nextjs/base/src/{components/content → ui/layout}/site-footer.tsx.ejs +144 -164
  108. package/templates/nextjs/base/src/{components/content → ui/layout}/site-header.tsx.ejs +152 -182
  109. package/templates/nextjs/base/src/{components/products → ui/product}/customization-fields.tsx +457 -478
  110. package/templates/nextjs/base/src/{components/products → ui/product}/discount-badge.tsx +18 -22
  111. package/templates/nextjs/base/src/{components/products → ui/product}/frequently-bought-together.tsx +21 -33
  112. package/templates/nextjs/base/src/ui/product/modifier-group-selector.tsx +206 -0
  113. package/templates/nextjs/base/src/{components/shared → ui/product}/price-display.tsx +5 -4
  114. package/templates/nextjs/base/src/{components/products → ui/product}/product-card.tsx +238 -271
  115. package/templates/nextjs/base/src/{app/products/[slug] → ui/product}/product-client-section.tsx +455 -631
  116. package/templates/nextjs/base/src/{components/products → ui/product}/product-grid.tsx +3 -3
  117. package/templates/nextjs/base/src/ui/product/product-listing.tsx +446 -0
  118. package/templates/nextjs/base/src/{components/products → ui/product}/recommendation-section.tsx +102 -107
  119. package/templates/nextjs/base/src/{components/reviews → ui/product}/review-form.tsx +46 -45
  120. package/templates/nextjs/base/src/{components/reviews/reviews-section.tsx → ui/product/reviews-section.tsx.ejs} +38 -15
  121. package/templates/nextjs/base/src/{components/products → ui/product}/stock-badge.tsx +61 -63
  122. package/templates/nextjs/base/src/{components/products → ui/product}/variant-selector.tsx +3 -3
  123. package/templates/nextjs/base/src/{components → ui}/shared/cdn-image.tsx +1 -1
  124. package/templates/nextjs/base/src/{components → ui}/shared/loading-spinner.tsx +2 -2
  125. package/templates/nextjs/base/tailwind.config.ts +69 -30
  126. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +251 -0
  127. package/templates/nextjs/designs/atelier/globals.css +396 -0
  128. package/templates/nextjs/designs/atelier/messages-patch/en.json +99 -0
  129. package/templates/nextjs/designs/atelier/messages-patch/he.json +99 -0
  130. package/templates/nextjs/designs/atelier/ui/cart/cart-bundle-offer.tsx +110 -0
  131. package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +163 -0
  132. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +140 -0
  133. package/templates/nextjs/designs/atelier/ui/cart/cart-nudges.tsx +34 -0
  134. package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +107 -0
  135. package/templates/nextjs/designs/atelier/ui/cart/cart-upgrade-banner.tsx +123 -0
  136. package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +147 -0
  137. package/templates/nextjs/designs/atelier/ui/cart/coupon-input.tsx +142 -0
  138. package/templates/nextjs/designs/atelier/ui/cart/free-shipping-bar.tsx +77 -0
  139. package/templates/nextjs/designs/atelier/ui/cart/reservation-countdown.tsx +89 -0
  140. package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +34 -0
  141. package/templates/nextjs/designs/atelier/ui/home/category-tiles.tsx +148 -0
  142. package/templates/nextjs/designs/atelier/ui/home/discount-banner-strip.tsx +27 -0
  143. package/templates/nextjs/designs/atelier/ui/home/editorial-band.tsx +73 -0
  144. package/templates/nextjs/designs/atelier/ui/home/featured-products-section.tsx +36 -0
  145. package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +148 -0
  146. package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +54 -0
  147. package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +78 -0
  148. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +46 -0
  149. package/templates/nextjs/designs/atelier/ui/layout/announcement-bar.tsx +118 -0
  150. package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +96 -0
  151. package/templates/nextjs/designs/atelier/ui/layout/header-account.tsx +36 -0
  152. package/templates/nextjs/designs/atelier/ui/layout/header-cart.tsx +36 -0
  153. package/templates/nextjs/designs/atelier/ui/layout/language-switcher.tsx.ejs +137 -0
  154. package/templates/nextjs/designs/atelier/ui/layout/nav-url.ts +14 -0
  155. package/templates/nextjs/designs/atelier/ui/layout/rich-text-block.tsx +34 -0
  156. package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +141 -0
  157. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +138 -0
  158. package/templates/nextjs/designs/atelier/ui/product/customization-fields.tsx +466 -0
  159. package/templates/nextjs/designs/atelier/ui/product/discount-badge.tsx +17 -0
  160. package/templates/nextjs/designs/atelier/ui/product/frequently-bought-together.tsx +196 -0
  161. package/templates/nextjs/designs/atelier/ui/product/modifier-group-selector.tsx +193 -0
  162. package/templates/nextjs/designs/atelier/ui/product/price-display.tsx +69 -0
  163. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +260 -0
  164. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +471 -0
  165. package/templates/nextjs/designs/atelier/ui/product/product-grid.tsx +47 -0
  166. package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +260 -0
  167. package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +95 -0
  168. package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +267 -0
  169. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -0
  170. package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +66 -0
  171. package/templates/nextjs/designs/atelier/ui/product/variant-selector.tsx +206 -0
  172. package/templates/nextjs/designs/atelier/ui/shared/cdn-image.tsx +29 -0
  173. package/templates/nextjs/designs/atelier/ui/shared/fly-to-cart.ts +86 -0
  174. package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -0
  175. package/templates/nextjs/designs/atelier/ui/shared/loading-spinner.tsx +64 -0
  176. package/templates/nextjs/designs/atelier/ui/shared/reveal.tsx +71 -0
  177. package/templates/nextjs/designs/atelier/ui/shared/select-menu.tsx +125 -0
  178. package/templates/nextjs/themes/luxury/globals.css +405 -399
  179. package/templates/nextjs/themes/minimal/globals.css +36 -30
  180. package/templates/nextjs/themes/playful/globals.css +406 -400
  181. package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +89 -0
  182. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +123 -0
  183. package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +27 -0
  184. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +99 -0
  185. package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +105 -0
  186. package/templates/nextjs/ui-canvas/cart/cart-view.tsx +106 -0
  187. package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +119 -0
  188. package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +54 -0
  189. package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +81 -0
  190. package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +19 -0
  191. package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +23 -0
  192. package/templates/nextjs/ui-canvas/home/hero-section.tsx +21 -0
  193. package/templates/nextjs/ui-canvas/home/home-client.tsx +26 -0
  194. package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +101 -0
  195. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +71 -0
  196. package/templates/nextjs/ui-canvas/layout/header-account.tsx +31 -0
  197. package/templates/nextjs/{base/src/components → ui-canvas}/layout/language-switcher.tsx.ejs +76 -70
  198. package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +36 -0
  199. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +82 -0
  200. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +119 -0
  201. package/templates/nextjs/ui-canvas/product/customization-fields.tsx +445 -0
  202. package/templates/nextjs/ui-canvas/product/discount-badge.tsx +19 -0
  203. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +168 -0
  204. package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +165 -0
  205. package/templates/nextjs/ui-canvas/product/price-display.tsx +52 -0
  206. package/templates/nextjs/ui-canvas/product/product-card.tsx +197 -0
  207. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +352 -0
  208. package/templates/nextjs/ui-canvas/product/product-grid.tsx +27 -0
  209. package/templates/nextjs/ui-canvas/product/product-listing.tsx +224 -0
  210. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +99 -0
  211. package/templates/nextjs/ui-canvas/product/review-form.tsx +266 -0
  212. package/templates/nextjs/ui-canvas/product/reviews-section.tsx.ejs +96 -0
  213. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +45 -0
  214. package/templates/nextjs/ui-canvas/product/variant-selector.tsx +212 -0
  215. package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +31 -0
  216. package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +27 -0
  217. package/templates/nextjs/base/src/app/home-client.tsx +0 -98
  218. package/templates/nextjs/base/src/components/content/faq-section.tsx.ejs +0 -103
  219. package/templates/nextjs/base/src/components/products/modifier-group-selector.tsx +0 -238
  220. /package/templates/nextjs/base/src/{lib → core/lib}/auth.ts +0 -0
  221. /package/templates/nextjs/base/src/{lib → core/lib}/csrf.ts +0 -0
  222. /package/templates/nextjs/base/src/{lib → core/lib}/image-hosts.ts +0 -0
  223. /package/templates/nextjs/base/src/{lib → core/lib}/navigation.tsx.ejs +0 -0
  224. /package/templates/nextjs/base/src/{lib → core/lib}/nonce.ts +0 -0
  225. /package/templates/nextjs/base/src/{lib → core/lib}/sanitize-html.ts +0 -0
  226. /package/templates/nextjs/base/src/{lib → core/lib}/seo.ts +0 -0
  227. /package/templates/nextjs/base/src/{lib → core/lib}/store-info.ts +0 -0
  228. /package/templates/nextjs/base/src/{lib → core/lib}/translations.ts.ejs +0 -0
  229. /package/templates/nextjs/base/src/{lib → core/lib}/utils.ts +0 -0
  230. /package/templates/nextjs/base/src/{lib → core/lib}/validation.ts +0 -0
@@ -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.
@@ -76,16 +76,6 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
76
76
  const t = useTranslations('checkout');
77
77
  const { storeInfo } = useStoreInfo();
78
78
 
79
- // Defense in depth: the parent already validates checkoutId from URL params,
80
- // but we re-check here so the component is safe to render in any context.
81
- if (!isValidCheckoutId(checkoutId)) {
82
- return (
83
- <div className={cn('border-destructive/50 rounded-md border p-4', className)}>
84
- <p className="text-destructive text-sm">{t('paymentError')}</p>
85
- </div>
86
- );
87
- }
88
-
89
79
  const [paymentIntent, setPaymentIntent] = useState<PaymentIntent | null>(null);
90
80
  const [preloadedSdk, setPreloadedSdk] = useState<PaymentClientSdk | null>(null);
91
81
  const [loading, setLoading] = useState(true);
@@ -174,6 +164,10 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
174
164
  // "call createPaymentProcess right before you need to render the wallet"
175
165
  // =========================================================================
176
166
  useEffect(() => {
167
+ // Defense in depth: the parent already validates checkoutId from URL
168
+ // params, but we re-check here so the component is safe to render in any
169
+ // context. Invalid id → no SDK loading, no API calls (error UI below).
170
+ if (!isValidCheckoutId(checkoutId)) return;
177
171
  if (initialized.current) return;
178
172
  initialized.current = true;
179
173
 
@@ -488,6 +482,16 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
488
482
 
489
483
  // --- UI ---
490
484
 
485
+ // Invalid checkout id — render the error box instead of ever spinning.
486
+ // (Checked after the hooks above so hook order is stable across renders.)
487
+ if (!isValidCheckoutId(checkoutId)) {
488
+ return (
489
+ <div className={cn('border-destructive/50 rounded-md border p-4', className)}>
490
+ <p className="text-destructive text-sm">{t('paymentError')}</p>
491
+ </div>
492
+ );
493
+ }
494
+
491
495
  if (loading) {
492
496
  return (
493
497
  <div className={cn('flex flex-col items-center justify-center py-12', className)}>
@@ -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,53 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as AccordionPrimitive from '@radix-ui/react-accordion';
5
+ import { ChevronDown } from 'lucide-react';
6
+
7
+ import { cn } from '@/core/lib/utils';
8
+
9
+ const Accordion = AccordionPrimitive.Root;
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item ref={ref} className={cn('border-b', className)} {...props} />
16
+ ));
17
+ AccordionItem.displayName = 'AccordionItem';
18
+
19
+ const AccordionTrigger = React.forwardRef<
20
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
21
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
22
+ >(({ className, children, ...props }, ref) => (
23
+ <AccordionPrimitive.Header className="flex">
24
+ <AccordionPrimitive.Trigger
25
+ ref={ref}
26
+ className={cn(
27
+ 'flex flex-1 items-center justify-between py-4 text-start font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
28
+ className
29
+ )}
30
+ {...props}
31
+ >
32
+ {children}
33
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
34
+ </AccordionPrimitive.Trigger>
35
+ </AccordionPrimitive.Header>
36
+ ));
37
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
38
+
39
+ const AccordionContent = React.forwardRef<
40
+ React.ElementRef<typeof AccordionPrimitive.Content>,
41
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
42
+ >(({ className, children, ...props }, ref) => (
43
+ <AccordionPrimitive.Content
44
+ ref={ref}
45
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
46
+ {...props}
47
+ >
48
+ <div className={cn('pb-4 pt-0', className)}>{children}</div>
49
+ </AccordionPrimitive.Content>
50
+ ));
51
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName;
52
+
53
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '@/core/lib/utils';
5
+
6
+ const badgeVariants = cva(
7
+ 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
12
+ secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
13
+ destructive:
14
+ 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
15
+ outline: 'text-foreground',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: 'default',
20
+ },
21
+ }
22
+ );
23
+
24
+ export interface BadgeProps
25
+ extends React.HTMLAttributes<HTMLDivElement>,
26
+ VariantProps<typeof badgeVariants> {}
27
+
28
+ function Badge({ className, variant, ...props }: BadgeProps) {
29
+ return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
30
+ }
31
+
32
+ export { Badge, badgeVariants };
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '@/core/lib/utils';
6
+
7
+ const buttonVariants = cva(
8
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
15
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
16
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
17
+ link: 'text-primary underline-offset-4 hover:underline',
18
+ },
19
+ size: {
20
+ default: 'h-10 px-4 py-2',
21
+ sm: 'h-9 rounded-md px-3',
22
+ lg: 'h-11 rounded-md px-8',
23
+ icon: 'h-10 w-10',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ size: 'default',
29
+ },
30
+ }
31
+ );
32
+
33
+ export interface ButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof buttonVariants> {
36
+ asChild?: boolean;
37
+ }
38
+
39
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
41
+ const Comp = asChild ? Slot : 'button';
42
+ return (
43
+ <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
44
+ );
45
+ }
46
+ );
47
+ Button.displayName = 'Button';
48
+
49
+ export { Button, buttonVariants };
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/core/lib/utils';
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)}
10
+ {...props}
11
+ />
12
+ )
13
+ );
14
+ Card.displayName = 'Card';
15
+
16
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
+ ({ className, ...props }, ref) => (
18
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
19
+ )
20
+ );
21
+ CardHeader.displayName = 'CardHeader';
22
+
23
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
24
+ ({ className, ...props }, ref) => (
25
+ <div
26
+ ref={ref}
27
+ className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
28
+ {...props}
29
+ />
30
+ )
31
+ );
32
+ CardTitle.displayName = 'CardTitle';
33
+
34
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
35
+ ({ className, ...props }, ref) => (
36
+ <div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
37
+ )
38
+ );
39
+ CardDescription.displayName = 'CardDescription';
40
+
41
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
42
+ ({ className, ...props }, ref) => (
43
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
44
+ )
45
+ );
46
+ CardContent.displayName = 'CardContent';
47
+
48
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
49
+ ({ className, ...props }, ref) => (
50
+ <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
51
+ )
52
+ );
53
+ CardFooter.displayName = 'CardFooter';
54
+
55
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5
+ import { Check } from 'lucide-react';
6
+
7
+ import { cn } from '@/core/lib/utils';
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ));
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
27
+
28
+ export { Checkbox };