create-brainerce-store 1.72.0 → 1.74.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/dist/index.js +37 -4
- package/messages/en.json +30 -1
- package/messages/he.json +30 -1
- package/package.json +1 -1
- package/templates/nextjs/base/AGENTS.md.ejs +63 -16
- package/templates/nextjs/base/AI-GUIDE.md +17 -4
- package/templates/nextjs/base/CLAUDE.md.ejs +67 -20
- package/templates/nextjs/base/TRANSLATIONS.md +207 -207
- package/templates/nextjs/base/src/app/account/page.tsx +168 -122
- package/templates/nextjs/base/src/app/category/[slug]/page.tsx +10 -1
- package/templates/nextjs/base/src/app/checkout/page.tsx +1179 -1074
- package/templates/nextjs/base/src/app/layout.tsx.ejs +48 -4
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +98 -3
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +14 -2
- package/templates/nextjs/base/src/components/account/loyalty-panel.tsx +226 -0
- package/templates/nextjs/base/src/components/account/order-history.tsx +422 -371
- package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +85 -85
- package/templates/nextjs/base/src/components/account/saved-payment-methods.tsx +104 -0
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +618 -612
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +354 -306
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +141 -74
- package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +218 -127
- package/templates/nextjs/base/src/core/hooks/use-home-data.ts +63 -51
- package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +379 -295
- package/templates/nextjs/base/src/core/hooks/use-product-page.ts +33 -1
- package/templates/nextjs/base/src/core/lib/capabilities.ts +65 -0
- package/templates/nextjs/base/src/core/lib/display-price.ts +139 -0
- package/templates/nextjs/base/src/core/lib/region.server.ts +112 -0
- package/templates/nextjs/base/src/core/lib/region.ts +112 -0
- package/templates/nextjs/base/src/core/lib/store-info.ts +19 -0
- package/templates/nextjs/base/src/core/providers/store-provider.tsx.ejs +423 -243
- package/templates/nextjs/base/src/ui/cart/cart-item.tsx +174 -164
- package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +109 -110
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +219 -176
- package/templates/nextjs/base/src/ui/cart/gift-card-input.tsx +191 -0
- package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +137 -137
- package/templates/nextjs/base/src/ui/cart/tax-estimate-line.tsx +108 -0
- package/templates/nextjs/base/src/ui/home/home-client.tsx +27 -29
- package/templates/nextjs/base/src/ui/layout/header-search.tsx +249 -0
- package/templates/nextjs/base/src/ui/layout/region-switcher.tsx +59 -0
- package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +171 -152
- package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +197 -191
- package/templates/nextjs/base/src/ui/product/product-card.tsx +25 -52
- package/templates/nextjs/base/src/ui/product/product-client-section.tsx +12 -3
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +460 -446
- package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +108 -102
- package/templates/nextjs/base/src/ui/product/review-form.tsx +33 -11
- package/templates/nextjs/base/src/ui/product/stock-badge.tsx +15 -3
- package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +48 -4
- package/templates/nextjs/designs/atelier/messages-patch/en.json +21 -25
- package/templates/nextjs/designs/atelier/messages-patch/he.json +21 -25
- package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +177 -181
- package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +168 -158
- package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +106 -107
- package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +229 -184
- package/templates/nextjs/designs/atelier/ui/cart/reservation-countdown.tsx +131 -131
- package/templates/nextjs/designs/atelier/ui/cart/tax-estimate-line.tsx +108 -0
- package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +10 -1
- package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +178 -174
- package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +4 -6
- package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +150 -78
- package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +79 -46
- package/templates/nextjs/designs/atelier/ui/layout/header-search.tsx +264 -0
- package/templates/nextjs/designs/atelier/ui/layout/region-switcher.tsx +44 -0
- package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +5 -0
- package/templates/nextjs/designs/atelier/ui/product/frequently-bought-together.tsx +202 -196
- package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +27 -52
- package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +12 -3
- package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +332 -260
- package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +101 -95
- package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +30 -10
- package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +17 -2
- package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +9 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +137 -137
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +98 -99
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +140 -140
- package/templates/nextjs/ui-canvas/cart/gift-card-input.tsx +142 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +124 -124
- package/templates/nextjs/ui-canvas/cart/tax-estimate-line.tsx +104 -0
- package/templates/nextjs/ui-canvas/home/home-client.tsx +24 -26
- package/templates/nextjs/ui-canvas/layout/region-switcher.tsx +51 -0
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +3 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +174 -168
- package/templates/nextjs/ui-canvas/product/product-card.tsx +9 -51
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +5 -3
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +105 -99
- package/templates/nextjs/ui-canvas/product/review-form.tsx +9 -1
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +17 -2
|
@@ -1,184 +1,229 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Link } from '@/core/lib/navigation';
|
|
4
|
-
import { useCartPage } from '@/core/hooks/use-cart-page';
|
|
5
|
-
import { CartItem } from '@/ui/cart/cart-item';
|
|
6
|
-
import { CartUpgradeBanner } from '@/ui/cart/cart-upgrade-banner';
|
|
7
|
-
import { CartBundleOfferCard } from '@/ui/cart/cart-bundle-offer';
|
|
8
|
-
import { CartSummary } from '@/ui/cart/cart-summary';
|
|
9
|
-
import { CouponInput } from '@/ui/cart/coupon-input';
|
|
10
|
-
import { CartNudges } from '@/ui/cart/cart-nudges';
|
|
11
|
-
import { FreeShippingBar } from '@/ui/cart/free-shipping-bar';
|
|
12
|
-
import { ReservationCountdown } from '@/ui/cart/reservation-countdown';
|
|
13
|
-
import { CartRecommendationSection } from '@/ui/product/recommendation-section';
|
|
14
|
-
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
15
|
-
import { useTranslations } from '@/core/lib/translations';
|
|
16
|
-
import { IconBag, IconArrowEnd, IconShield } from '@/ui/shared/icons';
|
|
17
|
-
|
|
18
|
-
export function CartView() {
|
|
19
|
-
const t = useTranslations('cart');
|
|
20
|
-
const tc = useTranslations('common');
|
|
21
|
-
const tr = useTranslations('reservation');
|
|
22
|
-
const {
|
|
23
|
-
cart,
|
|
24
|
-
cartLoading,
|
|
25
|
-
refreshCart,
|
|
26
|
-
itemCount,
|
|
27
|
-
cartRecs,
|
|
28
|
-
upgrades,
|
|
29
|
-
bundles,
|
|
30
|
-
reservationExpired,
|
|
31
|
-
unavailableItems,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
</
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Link } from '@/core/lib/navigation';
|
|
4
|
+
import { useCartPage } from '@/core/hooks/use-cart-page';
|
|
5
|
+
import { CartItem } from '@/ui/cart/cart-item';
|
|
6
|
+
import { CartUpgradeBanner } from '@/ui/cart/cart-upgrade-banner';
|
|
7
|
+
import { CartBundleOfferCard } from '@/ui/cart/cart-bundle-offer';
|
|
8
|
+
import { CartSummary } from '@/ui/cart/cart-summary';
|
|
9
|
+
import { CouponInput } from '@/ui/cart/coupon-input';
|
|
10
|
+
import { CartNudges } from '@/ui/cart/cart-nudges';
|
|
11
|
+
import { FreeShippingBar } from '@/ui/cart/free-shipping-bar';
|
|
12
|
+
import { ReservationCountdown } from '@/ui/cart/reservation-countdown';
|
|
13
|
+
import { CartRecommendationSection } from '@/ui/product/recommendation-section';
|
|
14
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
15
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
16
|
+
import { IconBag, IconArrowEnd, IconShield } from '@/ui/shared/icons';
|
|
17
|
+
|
|
18
|
+
export function CartView() {
|
|
19
|
+
const t = useTranslations('cart');
|
|
20
|
+
const tc = useTranslations('common');
|
|
21
|
+
const tr = useTranslations('reservation');
|
|
22
|
+
const {
|
|
23
|
+
cart,
|
|
24
|
+
cartLoading,
|
|
25
|
+
refreshCart,
|
|
26
|
+
itemCount,
|
|
27
|
+
cartRecs,
|
|
28
|
+
upgrades,
|
|
29
|
+
bundles,
|
|
30
|
+
reservationExpired,
|
|
31
|
+
unavailableItems,
|
|
32
|
+
selectedItemIds,
|
|
33
|
+
selectedItems,
|
|
34
|
+
toggleItemSelected,
|
|
35
|
+
allSelected,
|
|
36
|
+
setAllSelected,
|
|
37
|
+
checkoutHref,
|
|
38
|
+
canProceedToCheckout,
|
|
39
|
+
onReservationExpired,
|
|
40
|
+
} = useCartPage();
|
|
41
|
+
|
|
42
|
+
if (cartLoading) {
|
|
43
|
+
return <LoadingSpinner size="lg" className="min-h-[60vh]" />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Empty cart state — designed, never a bare sentence in blank space.
|
|
47
|
+
if (!cart || cart.items.length === 0) {
|
|
48
|
+
return (
|
|
49
|
+
<section className="container-narrow flex min-h-[60vh] items-center justify-center py-16">
|
|
50
|
+
<div className="card flex w-full max-w-md flex-col items-center gap-3 px-8 py-14 text-center">
|
|
51
|
+
<span className="bg-secondary text-primary flex h-16 w-16 items-center justify-center rounded-full">
|
|
52
|
+
<IconBag size={32} />
|
|
53
|
+
</span>
|
|
54
|
+
<h1 className="text-2xl sm:text-3xl">{t('emptyTitle')}</h1>
|
|
55
|
+
<p className="text-muted-foreground">{t('emptySubtitle')}</p>
|
|
56
|
+
<Link href="/products" className="btn-primary btn-md mt-3">
|
|
57
|
+
{tc('continueShopping')}
|
|
58
|
+
<IconArrowEnd size={18} className="rtl-flip" />
|
|
59
|
+
</Link>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<section className="container-narrow py-8 lg:py-12">
|
|
67
|
+
<h1 className="text-3xl sm:text-4xl">
|
|
68
|
+
{t('title')}{' '}
|
|
69
|
+
<span className="text-muted-foreground font-sans text-base font-normal">
|
|
70
|
+
({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
|
|
71
|
+
</span>
|
|
72
|
+
</h1>
|
|
73
|
+
|
|
74
|
+
{/* Reservation countdown. onExpire refreshes the cart and closes the
|
|
75
|
+
checkout gate below. Passing it is what makes expiry mean anything. */}
|
|
76
|
+
{cart.reservation?.hasReservation && (
|
|
77
|
+
<ReservationCountdown
|
|
78
|
+
reservation={cart.reservation}
|
|
79
|
+
onExpire={onReservationExpired}
|
|
80
|
+
className="mt-4"
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<div className="mt-6 grid grid-cols-1 items-start gap-8 lg:grid-cols-3 lg:gap-10">
|
|
85
|
+
{/* Cart Items */}
|
|
86
|
+
<div className="lg:col-span-2">
|
|
87
|
+
{/* Nudges */}
|
|
88
|
+
{cart.nudges && cart.nudges.length > 0 && (
|
|
89
|
+
<CartNudges nudges={cart.nudges} className="mb-4" />
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{/* Select-all. Partial checkout: only the ticked lines are sent to
|
|
93
|
+
createCheckout, and whatever stays unticked is still in the cart
|
|
94
|
+
(and still for sale) after payment. */}
|
|
95
|
+
<div className="mb-3 flex items-center gap-3 px-1">
|
|
96
|
+
<input
|
|
97
|
+
id="cart-select-all"
|
|
98
|
+
type="checkbox"
|
|
99
|
+
checked={allSelected}
|
|
100
|
+
onChange={(event) => setAllSelected(event.target.checked)}
|
|
101
|
+
className="accent-primary h-4 w-4 cursor-pointer"
|
|
102
|
+
/>
|
|
103
|
+
<label htmlFor="cart-select-all" className="text-foreground cursor-pointer text-sm">
|
|
104
|
+
{tc('all')}
|
|
105
|
+
</label>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Cart items */}
|
|
109
|
+
<ul className="card divide-y overflow-hidden">
|
|
110
|
+
{cart.items.map((item) => (
|
|
111
|
+
<li key={item.id} className="flex items-start gap-3 p-4 sm:p-5">
|
|
112
|
+
<input
|
|
113
|
+
type="checkbox"
|
|
114
|
+
checked={selectedItemIds.includes(item.id)}
|
|
115
|
+
onChange={() => toggleItemSelected(item.id)}
|
|
116
|
+
aria-label={item.product.name}
|
|
117
|
+
className="accent-primary mt-8 h-4 w-4 shrink-0 cursor-pointer"
|
|
118
|
+
/>
|
|
119
|
+
<div className="min-w-0 flex-1">
|
|
120
|
+
<CartItem item={item} onUpdate={refreshCart} />
|
|
121
|
+
{upgrades?.upgrades?.[item.productId] && (
|
|
122
|
+
<CartUpgradeBanner
|
|
123
|
+
suggestion={upgrades.upgrades[item.productId]}
|
|
124
|
+
cartItem={item}
|
|
125
|
+
onUpgrade={refreshCart}
|
|
126
|
+
className="mt-4"
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</li>
|
|
131
|
+
))}
|
|
132
|
+
</ul>
|
|
133
|
+
|
|
134
|
+
{/* Bundle offers */}
|
|
135
|
+
{bundles?.bundles && bundles.bundles.length > 0 && (
|
|
136
|
+
<section className="mt-6">
|
|
137
|
+
<h3 className="mb-3 text-xl">{t('bundleOffers')}</h3>
|
|
138
|
+
<div className="space-y-4">
|
|
139
|
+
{bundles.bundles.map((offer) => (
|
|
140
|
+
<CartBundleOfferCard
|
|
141
|
+
key={offer.id}
|
|
142
|
+
offer={offer}
|
|
143
|
+
cartId={cart.id}
|
|
144
|
+
onAdd={refreshCart}
|
|
145
|
+
/>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
</section>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{/* Coupon input */}
|
|
152
|
+
<div className="mt-6">
|
|
153
|
+
<p className="text-foreground mb-2 text-sm font-semibold">{t('couponTitle')}</p>
|
|
154
|
+
<CouponInput cart={cart} onUpdate={refreshCart} />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Summary sidebar */}
|
|
159
|
+
<aside className="lg:sticky lg:top-24">
|
|
160
|
+
<div className="card space-y-5 p-5 sm:p-6">
|
|
161
|
+
<FreeShippingBar />
|
|
162
|
+
{/* Totals are for the WHOLE cart. A partial selection is priced
|
|
163
|
+
authoritatively on the checkout page, which is the only place
|
|
164
|
+
the server has scoped the discounts and tax to the subset. */}
|
|
165
|
+
<CartSummary />
|
|
166
|
+
|
|
167
|
+
{/* Shown once the shopper unticks anything. At zero selected this
|
|
168
|
+
is what explains the disabled button below — a count, so it
|
|
169
|
+
needs no copy of its own. */}
|
|
170
|
+
{!allSelected && (
|
|
171
|
+
<p className="text-muted-foreground text-center text-xs">
|
|
172
|
+
{selectedItems.length} {selectedItems.length === 1 ? tc('item') : tc('items')}
|
|
173
|
+
</p>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* Proceed to checkout. When the gate is closed this must be a
|
|
177
|
+
real disabled control, never a styled-down link: an anchor
|
|
178
|
+
ignores `disabled` and would still navigate. */}
|
|
179
|
+
{canProceedToCheckout ? (
|
|
180
|
+
/* `checkoutHref` carries `?items=` only for a strict subset; a
|
|
181
|
+
full cart links to a bare /checkout exactly as before. */
|
|
182
|
+
<Link href={checkoutHref} className="btn-primary btn-lg w-full">
|
|
183
|
+
{t('proceedToCheckout')}
|
|
184
|
+
<IconArrowEnd size={20} className="rtl-flip" />
|
|
185
|
+
</Link>
|
|
186
|
+
) : (
|
|
187
|
+
<div>
|
|
188
|
+
<button type="button" disabled className="btn-primary btn-lg w-full opacity-50">
|
|
189
|
+
{t('proceedToCheckout')}
|
|
190
|
+
<IconArrowEnd size={20} className="rtl-flip" />
|
|
191
|
+
</button>
|
|
192
|
+
<p className="text-destructive mt-2 text-center text-xs">
|
|
193
|
+
{unavailableItems.length > 0
|
|
194
|
+
? t('unavailableItemsHint')
|
|
195
|
+
: reservationExpired
|
|
196
|
+
? tr('expiredHint')
|
|
197
|
+
: null}
|
|
198
|
+
</p>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
<p className="text-muted-foreground flex items-center justify-center gap-1.5 text-xs">
|
|
203
|
+
<IconShield size={16} />
|
|
204
|
+
{t('secureCheckoutNote')}
|
|
205
|
+
</p>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<p className="mt-4 text-center">
|
|
209
|
+
<Link
|
|
210
|
+
href="/products"
|
|
211
|
+
className="text-primary text-sm font-medium underline-offset-2 hover:underline"
|
|
212
|
+
>
|
|
213
|
+
{tc('continueShopping')}
|
|
214
|
+
</Link>
|
|
215
|
+
</p>
|
|
216
|
+
</aside>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Cross-sell recommendations */}
|
|
220
|
+
{cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
|
|
221
|
+
<CartRecommendationSection
|
|
222
|
+
title={t('youMightAlsoNeed')}
|
|
223
|
+
items={cartRecs.recommendations}
|
|
224
|
+
className="mt-12"
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</section>
|
|
228
|
+
);
|
|
229
|
+
}
|