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
@@ -0,0 +1,125 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as SheetPrimitive from '@radix-ui/react-dialog';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+ import { X } from 'lucide-react';
7
+
8
+ import { cn } from '@/core/lib/utils';
9
+
10
+ const Sheet = SheetPrimitive.Root;
11
+
12
+ const SheetTrigger = SheetPrimitive.Trigger;
13
+
14
+ const SheetClose = SheetPrimitive.Close;
15
+
16
+ const SheetPortal = SheetPrimitive.Portal;
17
+
18
+ const SheetOverlay = React.forwardRef<
19
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
20
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
21
+ >(({ className, ...props }, ref) => (
22
+ <SheetPrimitive.Overlay
23
+ className={cn(
24
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
25
+ className
26
+ )}
27
+ {...props}
28
+ ref={ref}
29
+ />
30
+ ));
31
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
32
+
33
+ const sheetVariants = cva(
34
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
35
+ {
36
+ variants: {
37
+ side: {
38
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
39
+ bottom:
40
+ 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
41
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
42
+ right:
43
+ 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ side: 'right',
48
+ },
49
+ }
50
+ );
51
+
52
+ interface SheetContentProps
53
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
54
+ VariantProps<typeof sheetVariants> {}
55
+
56
+ const SheetContent = React.forwardRef<
57
+ React.ElementRef<typeof SheetPrimitive.Content>,
58
+ SheetContentProps
59
+ >(({ side = 'right', className, children, ...props }, ref) => (
60
+ <SheetPortal>
61
+ <SheetOverlay />
62
+ <SheetPrimitive.Content
63
+ ref={ref}
64
+ className={cn(sheetVariants({ side }), className)}
65
+ {...props}
66
+ >
67
+ {children}
68
+ <SheetPrimitive.Close className="absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
69
+ <X className="h-4 w-4" />
70
+ <span className="sr-only">Close</span>
71
+ </SheetPrimitive.Close>
72
+ </SheetPrimitive.Content>
73
+ </SheetPortal>
74
+ ));
75
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
76
+
77
+ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
78
+ <div className={cn('flex flex-col space-y-2 text-center sm:text-start', className)} {...props} />
79
+ );
80
+ SheetHeader.displayName = 'SheetHeader';
81
+
82
+ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
83
+ <div
84
+ className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
85
+ {...props}
86
+ />
87
+ );
88
+ SheetFooter.displayName = 'SheetFooter';
89
+
90
+ const SheetTitle = React.forwardRef<
91
+ React.ElementRef<typeof SheetPrimitive.Title>,
92
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
93
+ >(({ className, ...props }, ref) => (
94
+ <SheetPrimitive.Title
95
+ ref={ref}
96
+ className={cn('text-lg font-semibold text-foreground', className)}
97
+ {...props}
98
+ />
99
+ ));
100
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
101
+
102
+ const SheetDescription = React.forwardRef<
103
+ React.ElementRef<typeof SheetPrimitive.Description>,
104
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
105
+ >(({ className, ...props }, ref) => (
106
+ <SheetPrimitive.Description
107
+ ref={ref}
108
+ className={cn('text-sm text-muted-foreground', className)}
109
+ {...props}
110
+ />
111
+ ));
112
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
113
+
114
+ export {
115
+ Sheet,
116
+ SheetPortal,
117
+ SheetOverlay,
118
+ SheetTrigger,
119
+ SheetClose,
120
+ SheetContent,
121
+ SheetHeader,
122
+ SheetFooter,
123
+ SheetTitle,
124
+ SheetDescription,
125
+ };
@@ -0,0 +1,7 @@
1
+ import { cn } from '@/core/lib/utils';
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />;
5
+ }
6
+
7
+ export { Skeleton };
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
5
+
6
+ import { cn } from '@/core/lib/utils';
7
+
8
+ const Tabs = TabsPrimitive.Root;
9
+
10
+ const TabsList = React.forwardRef<
11
+ React.ElementRef<typeof TabsPrimitive.List>,
12
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13
+ >(({ className, ...props }, ref) => (
14
+ <TabsPrimitive.List
15
+ ref={ref}
16
+ className={cn(
17
+ 'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ));
23
+ TabsList.displayName = TabsPrimitive.List.displayName;
24
+
25
+ const TabsTrigger = React.forwardRef<
26
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
27
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
28
+ >(({ className, ...props }, ref) => (
29
+ <TabsPrimitive.Trigger
30
+ ref={ref}
31
+ className={cn(
32
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ ));
38
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
39
+
40
+ const TabsContent = React.forwardRef<
41
+ React.ElementRef<typeof TabsPrimitive.Content>,
42
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
43
+ >(({ className, ...props }, ref) => (
44
+ <TabsPrimitive.Content
45
+ ref={ref}
46
+ className={cn(
47
+ 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ));
53
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
54
+
55
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/core/lib/utils';
4
+
5
+ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>(
6
+ ({ className, ...props }, ref) => {
7
+ return (
8
+ <textarea
9
+ className={cn(
10
+ 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
11
+ className
12
+ )}
13
+ ref={ref}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+ );
19
+ Textarea.displayName = 'Textarea';
20
+
21
+ export { Textarea };
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
5
+
6
+ import { cn } from '@/core/lib/utils';
7
+
8
+ const TooltipProvider = TooltipPrimitive.Provider;
9
+
10
+ const Tooltip = TooltipPrimitive.Root;
11
+
12
+ const TooltipTrigger = TooltipPrimitive.Trigger;
13
+
14
+ const TooltipContent = React.forwardRef<
15
+ React.ElementRef<typeof TooltipPrimitive.Content>,
16
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17
+ >(({ className, sideOffset = 4, ...props }, ref) => (
18
+ <TooltipPrimitive.Portal>
19
+ <TooltipPrimitive.Content
20
+ ref={ref}
21
+ sideOffset={sideOffset}
22
+ className={cn(
23
+ 'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ </TooltipPrimitive.Portal>
29
+ ));
30
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
31
+
32
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -6,6 +6,10 @@ import type { CartBundleOffer as CartBundleOfferType } from 'brainerce';
6
6
  import { formatPrice } from 'brainerce';
7
7
  import { useCurrency } from '@/core/lib/use-currency';
8
8
  import { useTranslations } from '@/core/lib/translations';
9
+ import { Badge } from '@/components/ui/badge';
10
+ import { Button } from '@/components/ui/button';
11
+ import { Card } from '@/components/ui/card';
12
+ import { Separator } from '@/components/ui/separator';
9
13
  import { cn } from '@/core/lib/utils';
10
14
 
11
15
  interface CartBundleOfferCardProps {
@@ -47,7 +51,7 @@ export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBun
47
51
  }
48
52
 
49
53
  return (
50
- <div className={cn('bg-background border-border rounded-lg border p-4', className)}>
54
+ <Card className={cn('p-4 shadow-none', className)}>
51
55
  <div className="mb-3">
52
56
  <p className="text-foreground text-sm font-medium">{offer.name}</p>
53
57
  {offer.description && (
@@ -82,7 +86,8 @@ export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBun
82
86
  })}
83
87
  </ul>
84
88
 
85
- <div className="border-border mt-3 flex items-center justify-between border-t pt-3">
89
+ <Separator className="mt-3" />
90
+ <div className="flex items-center justify-between pt-3">
86
91
  <div className="flex items-center gap-2">
87
92
  <span className="text-muted-foreground text-sm line-through">
88
93
  {formatPrice(totalOriginal, { currency }) as string}
@@ -90,22 +95,23 @@ export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBun
90
95
  <span className="text-foreground text-sm font-semibold">
91
96
  {formatPrice(totalDiscounted, { currency }) as string}
92
97
  </span>
93
- <span className="bg-destructive/10 text-destructive rounded px-1.5 py-0.5 text-xs font-medium">
98
+ <Badge
99
+ variant="outline"
100
+ className="bg-destructive/10 text-destructive rounded border-transparent px-1.5 py-0.5 font-medium"
101
+ >
94
102
  -{discountLabel}
95
- </span>
103
+ </Badge>
96
104
  </div>
97
- <button
105
+ <Button
98
106
  type="button"
107
+ size="sm"
99
108
  onClick={handleAdd}
100
109
  disabled={adding}
101
- className={cn(
102
- 'bg-primary text-primary-foreground flex-shrink-0 rounded px-4 py-2 text-xs font-medium transition-opacity hover:opacity-90',
103
- 'disabled:cursor-not-allowed disabled:opacity-50'
104
- )}
110
+ className="h-auto flex-shrink-0 rounded px-4 py-2 text-xs"
105
111
  >
106
112
  {adding ? t('addingBundle') : t('addBundleItem')}
107
- </button>
113
+ </Button>
108
114
  </div>
109
- </div>
115
+ </Card>
110
116
  );
111
117
  }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { Image as ImageIcon } from 'lucide-react';
4
5
  import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
6
  import type { CartItem as CartItemType } from 'brainerce';
6
7
  import { getCartItemImage, formatPrice } from 'brainerce';
@@ -73,14 +74,7 @@ export function CartItem({ item, onUpdate, className }: CartItemProps) {
73
74
  <Image src={imageUrl} alt={productName} fill sizes="80px" className="object-cover" />
74
75
  ) : (
75
76
  <div className="text-muted-foreground absolute inset-0 flex items-center justify-center">
76
- <svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
77
- <path
78
- strokeLinecap="round"
79
- strokeLinejoin="round"
80
- strokeWidth={1.5}
81
- d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
82
- />
83
- </svg>
77
+ <ImageIcon className="h-8 w-8" strokeWidth={1.5} aria-hidden="true" />
84
78
  </div>
85
79
  )}
86
80
  </div>
@@ -1,39 +1,28 @@
1
- 'use client';
2
-
3
- import type { CartNudge } from 'brainerce';
4
- import { cn } from '@/core/lib/utils';
5
-
6
- interface CartNudgesProps {
7
- nudges: CartNudge[];
8
- className?: string;
9
- }
10
-
11
- export function CartNudges({ nudges, className }: CartNudgesProps) {
12
- if (nudges.length === 0) return null;
13
-
14
- return (
15
- <div className={cn('space-y-2', className)}>
16
- {nudges.map((nudge) => (
17
- <div
18
- key={nudge.ruleId}
19
- className="bg-primary/5 border-primary/20 flex items-start gap-3 rounded-lg border px-4 py-3"
20
- >
21
- <svg
22
- className="text-primary mt-0.5 h-5 w-5 flex-shrink-0"
23
- fill="none"
24
- viewBox="0 0 24 24"
25
- stroke="currentColor"
26
- >
27
- <path
28
- strokeLinecap="round"
29
- strokeLinejoin="round"
30
- strokeWidth={2}
31
- d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
32
- />
33
- </svg>
34
- <p className="text-foreground flex-1 text-sm">{nudge.text}</p>
35
- </div>
36
- ))}
37
- </div>
38
- );
39
- }
1
+ 'use client';
2
+
3
+ import { Info } from 'lucide-react';
4
+ import type { CartNudge } from 'brainerce';
5
+ import { cn } from '@/core/lib/utils';
6
+
7
+ interface CartNudgesProps {
8
+ nudges: CartNudge[];
9
+ className?: string;
10
+ }
11
+
12
+ export function CartNudges({ nudges, className }: CartNudgesProps) {
13
+ if (nudges.length === 0) return null;
14
+
15
+ return (
16
+ <div className={cn('space-y-2', className)}>
17
+ {nudges.map((nudge) => (
18
+ <div
19
+ key={nudge.ruleId}
20
+ className="bg-primary/5 border-primary/20 flex items-start gap-3 rounded-lg border px-4 py-3"
21
+ >
22
+ <Info className="text-primary mt-0.5 h-5 w-5 flex-shrink-0" aria-hidden="true" />
23
+ <p className="text-foreground flex-1 text-sm">{nudge.text}</p>
24
+ </div>
25
+ ))}
26
+ </div>
27
+ );
28
+ }
@@ -4,6 +4,7 @@ import { formatPrice } from 'brainerce';
4
4
  import { useTranslations } from '@/core/lib/translations';
5
5
  import { useCart } from '@/core/providers/store-provider';
6
6
  import { useCurrency } from '@/core/lib/use-currency';
7
+ import { Separator } from '@/components/ui/separator';
7
8
  import { cn } from '@/core/lib/utils';
8
9
 
9
10
  interface CartSummaryProps {
@@ -94,7 +95,8 @@ export function CartSummary({ className }: CartSummaryProps) {
94
95
  </div>
95
96
 
96
97
  {/* Divider */}
97
- <div className="border-border mt-2 border-t pt-2">
98
+ <Separator className="mt-2" />
99
+ <div className="pt-1">
98
100
  <div className="flex items-center justify-between">
99
101
  <span className="text-foreground font-semibold">{tc('total')}</span>
100
102
  <span className="text-foreground text-base font-semibold">
@@ -1,12 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
+ import { Image as ImageIcon, X } from 'lucide-react';
4
5
  import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
6
  import type { CartUpgradeSuggestion, CartItem as CartItemType } from 'brainerce';
6
7
  import { formatPrice } from 'brainerce';
7
8
  import { getClient } from '@/core/lib/brainerce';
8
9
  import { useCurrency } from '@/core/lib/use-currency';
9
10
  import { useTranslations } from '@/core/lib/translations';
11
+ import { Button } from '@/components/ui/button';
10
12
  import { cn } from '@/core/lib/utils';
11
13
 
12
14
  interface CartUpgradeBannerProps {
@@ -88,15 +90,7 @@ export function CartUpgradeBanner({
88
90
  className="text-muted-foreground hover:text-foreground absolute end-2 top-2 text-xs"
89
91
  aria-label={t('dismissUpgrade')}
90
92
  >
91
- <svg
92
- className="h-4 w-4"
93
- fill="none"
94
- viewBox="0 0 24 24"
95
- stroke="currentColor"
96
- strokeWidth={2}
97
- >
98
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
99
- </svg>
93
+ <X className="h-4 w-4" aria-hidden="true" />
100
94
  </button>
101
95
 
102
96
  {/* Product image */}
@@ -105,14 +99,7 @@ export function CartUpgradeBanner({
105
99
  <Image src={imageUrl} alt={target.name} fill sizes="48px" className="object-cover" />
106
100
  ) : (
107
101
  <div className="text-muted-foreground flex h-full w-full items-center justify-center">
108
- <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
109
- <path
110
- strokeLinecap="round"
111
- strokeLinejoin="round"
112
- strokeWidth={1.5}
113
- d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
114
- />
115
- </svg>
102
+ <ImageIcon className="h-5 w-5" strokeWidth={1.5} aria-hidden="true" />
116
103
  </div>
117
104
  )}
118
105
  </div>
@@ -125,17 +112,15 @@ export function CartUpgradeBanner({
125
112
  </div>
126
113
 
127
114
  {/* Upgrade button */}
128
- <button
115
+ <Button
129
116
  type="button"
117
+ size="sm"
130
118
  onClick={handleUpgrade}
131
119
  disabled={upgrading}
132
- className={cn(
133
- 'bg-primary text-primary-foreground flex-shrink-0 rounded px-3 py-1.5 text-xs font-medium transition-opacity hover:opacity-90',
134
- 'disabled:cursor-not-allowed disabled:opacity-50'
135
- )}
120
+ className="h-auto flex-shrink-0 rounded px-3 py-1.5 text-xs"
136
121
  >
137
122
  {upgrading ? t('upgrading') : t('upgrade')}
138
- </button>
123
+ </Button>
139
124
  </div>
140
125
  );
141
126
  }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { ShoppingBag } from 'lucide-react';
3
4
  import { Link } from '@/core/lib/navigation';
4
5
  import { useCartPage } from '@/core/hooks/use-cart-page';
5
6
  import { CartItem } from '@/ui/cart/cart-item';
@@ -12,6 +13,9 @@ import { FreeShippingBar } from '@/ui/cart/free-shipping-bar';
12
13
  import { ReservationCountdown } from '@/ui/cart/reservation-countdown';
13
14
  import { CartRecommendationSection } from '@/ui/product/recommendation-section';
14
15
  import { LoadingSpinner } from '@/ui/shared/loading-spinner';
16
+ import { Button } from '@/components/ui/button';
17
+ import { Card } from '@/components/ui/card';
18
+ import { Separator } from '@/components/ui/separator';
15
19
  import { useTranslations } from '@/core/lib/translations';
16
20
 
17
21
  export function CartView() {
@@ -31,27 +35,16 @@ export function CartView() {
31
35
  if (!cart || cart.items.length === 0) {
32
36
  return (
33
37
  <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
34
- <svg
38
+ <ShoppingBag
35
39
  className="text-muted-foreground mx-auto mb-4 h-16 w-16"
36
- fill="none"
37
- viewBox="0 0 24 24"
38
- stroke="currentColor"
39
- >
40
- <path
41
- strokeLinecap="round"
42
- strokeLinejoin="round"
43
- strokeWidth={1.5}
44
- d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
45
- />
46
- </svg>
40
+ strokeWidth={1.5}
41
+ aria-hidden="true"
42
+ />
47
43
  <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
48
44
  <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
49
- <Link
50
- href="/products"
51
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
52
- >
53
- {tc('continueShopping')}
54
- </Link>
45
+ <Button asChild size="lg" className="mt-6 rounded px-6 text-base">
46
+ <Link href="/products">{tc('continueShopping')}</Link>
47
+ </Button>
55
48
  </div>
56
49
  );
57
50
  }
@@ -108,23 +101,21 @@ export function CartView() {
108
101
  )}
109
102
 
110
103
  {/* Coupon input */}
111
- <div className="border-border mt-6 border-t pt-4">
104
+ <Separator className="mt-6" />
105
+ <div className="pt-4">
112
106
  <CouponInput cart={cart} onUpdate={refreshCart} />
113
107
  </div>
114
108
  </div>
115
109
 
116
110
  {/* Summary sidebar */}
117
111
  <div className="lg:col-span-1">
118
- <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
112
+ <Card className="bg-muted/50 sticky top-24 p-6 shadow-none">
119
113
  <FreeShippingBar className="mb-4" />
120
114
  <CartSummary />
121
115
 
122
- <Link
123
- href="/checkout"
124
- className="bg-primary text-primary-foreground mt-6 inline-flex w-full items-center justify-center rounded px-6 py-3 text-sm font-medium transition-opacity hover:opacity-90"
125
- >
126
- {t('proceedToCheckout')}
127
- </Link>
116
+ <Button asChild size="lg" className="mt-6 w-full rounded px-6 text-sm">
117
+ <Link href="/checkout">{t('proceedToCheckout')}</Link>
118
+ </Button>
128
119
 
129
120
  <Link
130
121
  href="/products"
@@ -132,7 +123,7 @@ export function CartView() {
132
123
  >
133
124
  {tc('continueShopping')}
134
125
  </Link>
135
- </div>
126
+ </Card>
136
127
  </div>
137
128
  </div>
138
129
 
@@ -1,10 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { Tag } from 'lucide-react';
4
5
  import type { Cart } from 'brainerce';
5
6
  import { getClient } from '@/core/lib/brainerce';
6
7
  import { useTranslations } from '@/core/lib/translations';
7
8
  import { LoadingSpinner } from '@/ui/shared/loading-spinner';
9
+ import { Button } from '@/components/ui/button';
10
+ import { Input } from '@/components/ui/input';
8
11
  import { cn } from '@/core/lib/utils';
9
12
 
10
13
  interface CouponInputProps {
@@ -75,19 +78,7 @@ export function CouponInput({ cart, checkoutId, onUpdate, className }: CouponInp
75
78
  <div className={cn('space-y-2', className)}>
76
79
  <div className="bg-muted flex items-center justify-between rounded px-3 py-2">
77
80
  <div className="flex items-center gap-2">
78
- <svg
79
- className="text-primary h-4 w-4 flex-shrink-0"
80
- fill="none"
81
- viewBox="0 0 24 24"
82
- stroke="currentColor"
83
- >
84
- <path
85
- strokeLinecap="round"
86
- strokeLinejoin="round"
87
- strokeWidth={2}
88
- d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
89
- />
90
- </svg>
81
+ <Tag className="text-primary h-4 w-4 flex-shrink-0" aria-hidden="true" />
91
82
  <span className="text-foreground text-sm font-medium">{appliedCoupon}</span>
92
83
  </div>
93
84
  <button
@@ -106,7 +97,7 @@ export function CouponInput({ cart, checkoutId, onUpdate, className }: CouponInp
106
97
  return (
107
98
  <div className={cn('space-y-2', className)}>
108
99
  <div className="flex gap-2">
109
- <input
100
+ <Input
110
101
  type="text"
111
102
  value={code}
112
103
  onChange={(e) => {
@@ -120,23 +111,21 @@ export function CouponInput({ cart, checkoutId, onUpdate, className }: CouponInp
120
111
  }
121
112
  }}
122
113
  placeholder={t('placeholder')}
123
- className={cn(
124
- 'bg-background text-foreground placeholder:text-muted-foreground focus:ring-primary/20 focus:border-primary h-9 flex-1 rounded border px-3 text-sm focus:outline-none focus:ring-2',
125
- error ? 'border-destructive' : 'border-border'
126
- )}
114
+ className={cn('h-9 flex-1 rounded', error && 'border-destructive')}
127
115
  />
128
- <button
116
+ <Button
129
117
  type="button"
118
+ variant="outline"
130
119
  onClick={handleApply}
131
120
  disabled={applying || !code.trim()}
132
- className="border-border bg-background text-foreground hover:bg-muted h-9 rounded border px-4 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40"
121
+ className="h-9 rounded px-4 hover:bg-muted"
133
122
  >
134
123
  {applying ? (
135
124
  <LoadingSpinner size="sm" className="border-muted-foreground/30 border-t-foreground" />
136
125
  ) : (
137
126
  tc('apply')
138
127
  )}
139
- </button>
128
+ </Button>
140
129
  </div>
141
130
 
142
131
  {error && <p className="text-destructive text-xs">{error}</p>}