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.
- package/README.md +72 -0
- package/dist/index.js +9 -68
- package/messages/en.json +32 -0
- package/messages/he.json +32 -0
- package/package.json +1 -1
- package/templates/nextjs/base/.claude/commands/design.md +9 -0
- package/templates/nextjs/base/AI-GUIDE.md +52 -3
- package/templates/nextjs/base/CLAUDE.md +10 -0
- package/templates/nextjs/base/package.json.ejs +52 -39
- package/templates/nextjs/base/src/app/globals.css +36 -30
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +14 -10
- package/templates/nextjs/base/src/components/ui/accordion.tsx +53 -0
- package/templates/nextjs/base/src/components/ui/badge.tsx +32 -0
- package/templates/nextjs/base/src/components/ui/button.tsx +49 -0
- package/templates/nextjs/base/src/components/ui/card.tsx +55 -0
- package/templates/nextjs/base/src/components/ui/checkbox.tsx +28 -0
- package/templates/nextjs/base/src/components/ui/dialog.tsx +107 -0
- package/templates/nextjs/base/src/components/ui/input.tsx +22 -0
- package/templates/nextjs/base/src/components/ui/label.tsx +21 -0
- package/templates/nextjs/base/src/components/ui/radio-group.tsx +38 -0
- package/templates/nextjs/base/src/components/ui/select.tsx +153 -0
- package/templates/nextjs/base/src/components/ui/separator.tsx +26 -0
- package/templates/nextjs/base/src/components/ui/sheet.tsx +125 -0
- package/templates/nextjs/base/src/components/ui/skeleton.tsx +7 -0
- package/templates/nextjs/base/src/components/ui/tabs.tsx +55 -0
- package/templates/nextjs/base/src/components/ui/textarea.tsx +21 -0
- package/templates/nextjs/base/src/components/ui/tooltip.tsx +32 -0
- package/templates/nextjs/base/src/ui/cart/cart-bundle-offer.tsx +17 -11
- package/templates/nextjs/base/src/ui/cart/cart-item.tsx +2 -8
- package/templates/nextjs/base/src/ui/cart/cart-nudges.tsx +28 -39
- package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +3 -1
- package/templates/nextjs/base/src/ui/cart/cart-upgrade-banner.tsx +8 -23
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +18 -27
- package/templates/nextjs/base/src/ui/cart/coupon-input.tsx +10 -21
- package/templates/nextjs/base/src/ui/cart/free-shipping-bar.tsx +2 -3
- package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +95 -103
- package/templates/nextjs/base/src/ui/home/hero-section.tsx +4 -6
- package/templates/nextjs/base/src/ui/layout/announcement-bar.tsx.ejs +116 -125
- package/templates/nextjs/base/src/ui/layout/faq-section.tsx.ejs +90 -103
- package/templates/nextjs/base/src/ui/layout/header-account.tsx +2 -17
- package/templates/nextjs/base/src/ui/layout/language-switcher.tsx.ejs +80 -70
- package/templates/nextjs/base/src/ui/layout/site-footer.tsx.ejs +144 -164
- package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +152 -182
- package/templates/nextjs/base/src/ui/product/customization-fields.tsx +456 -444
- package/templates/nextjs/base/src/ui/product/discount-badge.tsx +18 -22
- package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +15 -27
- package/templates/nextjs/base/src/ui/product/modifier-group-selector.tsx +206 -175
- package/templates/nextjs/base/src/ui/product/price-display.tsx +3 -2
- package/templates/nextjs/base/src/ui/product/product-card.tsx +238 -271
- package/templates/nextjs/base/src/ui/product/product-client-section.tsx +455 -470
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +96 -79
- package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +102 -107
- package/templates/nextjs/base/src/ui/product/review-form.tsx +44 -43
- package/templates/nextjs/{designs/passepartout → base/src}/ui/product/reviews-section.tsx.ejs +108 -96
- package/templates/nextjs/base/src/ui/product/stock-badge.tsx +61 -63
- package/templates/nextjs/base/tailwind.config.ts +69 -42
- package/templates/nextjs/designs/{passepartout → atelier}/app-overlay/layout.tsx.ejs +23 -25
- package/templates/nextjs/designs/atelier/globals.css +396 -0
- package/templates/nextjs/designs/atelier/messages-patch/en.json +99 -0
- package/templates/nextjs/designs/atelier/messages-patch/he.json +99 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-bundle-offer.tsx +110 -111
- package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +163 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +140 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-nudges.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +107 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-upgrade-banner.tsx +123 -141
- package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +147 -0
- package/templates/nextjs/designs/atelier/ui/cart/coupon-input.tsx +142 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/free-shipping-bar.tsx +77 -62
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/reservation-countdown.tsx +89 -103
- package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/home/category-tiles.tsx +148 -0
- package/templates/nextjs/designs/atelier/ui/home/discount-banner-strip.tsx +27 -0
- package/templates/nextjs/designs/atelier/ui/home/editorial-band.tsx +73 -0
- package/templates/nextjs/designs/atelier/ui/home/featured-products-section.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +148 -0
- package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +54 -0
- package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +78 -0
- package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +46 -0
- package/templates/nextjs/designs/atelier/ui/layout/announcement-bar.tsx +118 -0
- package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +96 -0
- package/templates/nextjs/designs/atelier/ui/layout/header-account.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/layout/header-cart.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/layout/language-switcher.tsx.ejs +137 -0
- package/templates/nextjs/designs/atelier/ui/layout/nav-url.ts +14 -0
- package/templates/nextjs/designs/atelier/ui/layout/rich-text-block.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +141 -0
- package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +138 -0
- package/templates/nextjs/designs/atelier/ui/product/customization-fields.tsx +466 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/discount-badge.tsx +17 -22
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/frequently-bought-together.tsx +196 -205
- package/templates/nextjs/designs/atelier/ui/product/modifier-group-selector.tsx +193 -0
- package/templates/nextjs/designs/atelier/ui/product/price-display.tsx +69 -0
- package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +260 -0
- package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +471 -0
- package/templates/nextjs/designs/atelier/ui/product/product-grid.tsx +47 -0
- package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +260 -0
- package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +95 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/review-form.tsx +267 -294
- package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -0
- package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +66 -0
- package/templates/nextjs/designs/atelier/ui/product/variant-selector.tsx +206 -0
- package/templates/nextjs/designs/atelier/ui/shared/cdn-image.tsx +29 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/shared/fly-to-cart.ts +86 -85
- package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -0
- package/templates/nextjs/designs/atelier/ui/shared/loading-spinner.tsx +64 -0
- package/templates/nextjs/designs/atelier/ui/shared/reveal.tsx +71 -0
- package/templates/nextjs/designs/atelier/ui/shared/select-menu.tsx +125 -0
- package/templates/nextjs/themes/luxury/globals.css +405 -399
- package/templates/nextjs/themes/minimal/globals.css +36 -30
- package/templates/nextjs/themes/playful/globals.css +406 -400
- package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +2 -2
- package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +2 -0
- package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/hero-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/home-client.tsx +1 -1
- package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +1 -1
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/layout/header-account.tsx +1 -1
- package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +2 -0
- package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +2 -0
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/product/customization-fields.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/discount-badge.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/price-display.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-grid.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-listing.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +4 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +29 -32
- package/templates/nextjs/ui-canvas/product/{reviews-section.tsx → reviews-section.tsx.ejs} +26 -8
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/variant-selector.tsx +2 -0
- package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +2 -0
- package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +2 -0
- package/templates/nextjs/base/src/ui/product/reviews-section.tsx +0 -85
- package/templates/nextjs/designs/passepartout/globals.css +0 -412
- package/templates/nextjs/designs/passepartout/messages-patch/en.json +0 -87
- package/templates/nextjs/designs/passepartout/messages-patch/he.json +0 -87
- package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +0 -39
- package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +0 -149
- package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +0 -401
- package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +0 -139
- package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +0 -45
- package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +0 -103
- package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +0 -159
- package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +0 -155
- package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +0 -60
- package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +0 -34
- package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +0 -111
- package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +0 -265
- package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +0 -667
- package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +0 -35
- package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +0 -427
- package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +0 -113
- package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +0 -52
- package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +0 -307
- 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,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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
103
|
+
</Badge>
|
|
96
104
|
</div>
|
|
97
|
-
<
|
|
105
|
+
<Button
|
|
98
106
|
type="button"
|
|
107
|
+
size="sm"
|
|
99
108
|
onClick={handleAdd}
|
|
100
109
|
disabled={adding}
|
|
101
|
-
className=
|
|
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
|
-
</
|
|
113
|
+
</Button>
|
|
108
114
|
</div>
|
|
109
|
-
</
|
|
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
|
-
<
|
|
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
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
115
|
+
<Button
|
|
129
116
|
type="button"
|
|
117
|
+
size="sm"
|
|
130
118
|
onClick={handleUpgrade}
|
|
131
119
|
disabled={upgrading}
|
|
132
|
-
className=
|
|
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
|
-
</
|
|
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
|
-
<
|
|
38
|
+
<ShoppingBag
|
|
35
39
|
className="text-muted-foreground mx-auto mb-4 h-16 w-16"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
<
|
|
50
|
-
href="/products"
|
|
51
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
123
|
-
href="/checkout"
|
|
124
|
-
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
116
|
+
<Button
|
|
129
117
|
type="button"
|
|
118
|
+
variant="outline"
|
|
130
119
|
onClick={handleApply}
|
|
131
120
|
disabled={applying || !code.trim()}
|
|
132
|
-
className="
|
|
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
|
-
</
|
|
128
|
+
</Button>
|
|
140
129
|
</div>
|
|
141
130
|
|
|
142
131
|
{error && <p className="text-destructive text-xs">{error}</p>}
|