@cartbase/storefront 0.17.0 → 0.18.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/LICENSE +21 -21
- package/package.json +258 -256
- package/src/api/auth.ts +108 -108
- package/src/api/carts.ts +523 -523
- package/src/api/categories.ts +184 -184
- package/src/api/checkout.ts +526 -526
- package/src/api/collections.ts +130 -130
- package/src/api/consent.ts +75 -75
- package/src/api/content.ts +125 -125
- package/src/api/customers.ts +303 -303
- package/src/api/gift-cards.ts +112 -112
- package/src/api/http.ts +180 -180
- package/src/api/index.ts +30 -30
- package/src/api/menus.ts +77 -77
- package/src/api/metaobjects.ts +136 -136
- package/src/api/orders.ts +290 -290
- package/src/api/products.ts +43 -1
- package/src/api/redirects.ts +37 -37
- package/src/api/regions.ts +200 -200
- package/src/api/reviews.ts +259 -259
- package/src/api/search.ts +163 -163
- package/src/api/store.ts +35 -35
- package/src/api/types.ts +91 -91
- package/src/cart-drawer/context.tsx +277 -164
- package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
- package/src/cart-drawer/gift-wrap.tsx +82 -82
- package/src/cart-drawer/index.ts +71 -68
- package/src/cart-drawer/item/index.tsx +162 -162
- package/src/cart-drawer/item/quantity.tsx +8 -14
- package/src/cart-drawer/item/upsell.tsx +110 -110
- package/src/cart-drawer/labels.ts +123 -123
- package/src/cart-drawer/mutation-queue.ts +78 -0
- package/src/cart-drawer/notes.tsx +131 -131
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/cart-drawer/summary-breakdown.tsx +197 -197
- package/src/checkout/address-error-copy.ts +117 -117
- package/src/checkout/boxnow-locker-selector.tsx +410 -410
- package/src/checkout/compare-addresses.ts +40 -40
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -63
- package/src/checkout/geocode.ts +154 -154
- package/src/checkout/gift-card-section.tsx +224 -224
- package/src/checkout/index.ts +72 -72
- package/src/checkout/labels.ts +495 -495
- package/src/checkout/payment-button.tsx +372 -372
- package/src/checkout/payment-error-copy.ts +154 -154
- package/src/checkout/promotion-error-copy.ts +91 -91
- package/src/checkout/use-checkout-orchestration.ts +1592 -1592
- package/src/common/country-flag.tsx +52 -52
- package/src/common/country-select.tsx +11 -11
- package/src/common/index.ts +20 -20
- package/src/common/market-select.tsx +57 -57
- package/src/index.ts +12 -12
- package/src/lib/country-name.ts +59 -59
- package/src/lib/get-product-price.ts +133 -133
- package/src/lib/media-image.tsx +39 -39
- package/src/lib/payment-constants.ts +53 -53
- package/src/lib/platform.ts +13 -13
- package/src/lib/price.tsx +39 -39
- package/src/lib/store-api-error.ts +36 -36
- package/src/lib/variant-caption.ts +32 -32
- package/src/locales/bg.ts +467 -467
- package/src/locales/context.ts +37 -37
- package/src/locales/en.ts +26 -26
- package/src/locales/es.ts +466 -466
- package/src/locales/index.ts +19 -19
- package/src/locales/provider.tsx +59 -59
- package/src/locales/types.ts +77 -77
- package/src/order/index.ts +62 -62
- package/src/order/labels.ts +79 -79
- package/src/order/order-totals.tsx +250 -250
- package/src/primitives/select-field.tsx +93 -93
- package/src/products/image-gallery.tsx +43 -43
- package/src/products/index.ts +68 -59
- package/src/products/option-select.tsx +67 -67
- package/src/products/product-actions-wrapper.tsx +63 -62
- package/src/products/product-actions.tsx +151 -150
- package/src/products/product-price.tsx +69 -69
- package/src/products/product-promises.tsx +61 -61
- package/src/products/product-specs.tsx +65 -65
- package/src/products/product-tabs.tsx +123 -123
- package/src/products/product-template.tsx +163 -162
- package/src/products/purchase-options.tsx +130 -130
- package/src/products/sets.ts +110 -0
- package/src/products/use-product-actions.ts +199 -164
- package/src/products/variant-matching.ts +71 -71
- package/src/products/variant-url.ts +74 -74
- package/src/reviews-ui/index.ts +73 -73
- package/src/store/category-template.tsx +136 -136
- package/src/store/index.ts +40 -40
- package/src/tracking/chatgpt-pixel.tsx +99 -99
- package/src/tracking/consent-init.tsx +62 -62
- package/src/tracking/events.ts +348 -348
- package/src/tracking/ga4.tsx +93 -93
- package/src/tracking/google-ads.ts +84 -84
- package/src/tracking/gtm.tsx +60 -60
- package/src/tracking/inline-script.ts +49 -49
- package/src/tracking/oaiq.ts +206 -206
- package/src/tracking/tiktok-pixel.tsx +91 -91
- package/src/tracking/track-init.tsx +56 -56
- package/src/tracking/track-order-purchase.tsx +122 -122
- package/src/tracking/ttq.ts +180 -180
- package/src/tracking/use-tracking-config.ts +54 -54
- package/theme/index.css +25 -25
- package/theme/theme.css +150 -150
- package/theme/tokens.css +106 -106
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { StoreImage as Image } from "../lib/media-image"
|
|
4
|
-
import Link from "next/link"
|
|
5
|
-
|
|
6
|
-
import type { StoreProduct } from "../api/products"
|
|
7
|
-
import { convertToLocale } from "../lib/money"
|
|
8
|
-
import { useCartDrawer } from "./context"
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* CartCrossSellSidebar — vertical product-card list for desktop cart
|
|
12
|
-
* drawer's left sidebar.
|
|
13
|
-
*
|
|
14
|
-
* Ported from `@1click/ui/src/cart-drawer/cross-sell-sidebar.tsx` (v2.3.1).
|
|
15
|
-
* Cartbase data seam: source stores fed this from Medusa product fetches —
|
|
16
|
-
* feed it from `@cartbase/storefront/api/products` `listProducts` (curated
|
|
17
|
-
* collection/tag) or `@cartbase/storefront/api/search` `listRelatedProducts`
|
|
18
|
-
* (anchor-product complements), mapped through `toCrossSellProduct` below.
|
|
19
|
-
* Wire `onAdd` to the provider's `addItem(variantId)`. Prices are EUR
|
|
20
|
-
* major units and come server-computed via `calculated_price` — never
|
|
21
|
-
* recompute client-side.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export type CrossSellSidebarProduct = {
|
|
25
|
-
id: string
|
|
26
|
-
title: string
|
|
27
|
-
handle: string
|
|
28
|
-
thumbnail: string | null
|
|
29
|
-
price: number
|
|
30
|
-
currencyCode: string
|
|
31
|
-
variantId?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Map a store-API product (products/search/related responses all reuse the
|
|
36
|
-
* canonical `StoreProduct` shape) to a cross-sell card. Picks the first
|
|
37
|
-
* variant carrying a server-computed `calculated_price` for the current
|
|
38
|
-
* pricing context; returns null when no variant is priced (pass
|
|
39
|
-
* `currency_code`/`region_id` on the listing call, and skip null results).
|
|
40
|
-
* Pure — exported for unit tests and for building `crossSell.loader`s.
|
|
41
|
-
*/
|
|
42
|
-
export function toCrossSellProduct(
|
|
43
|
-
product: StoreProduct
|
|
44
|
-
): CrossSellSidebarProduct | null {
|
|
45
|
-
const variant = (product.variants ?? []).find((v) => v.calculated_price)
|
|
46
|
-
const price = variant?.calculated_price
|
|
47
|
-
if (!variant || !price) return null
|
|
48
|
-
return {
|
|
49
|
-
id: product.id,
|
|
50
|
-
title: product.title,
|
|
51
|
-
handle: product.handle,
|
|
52
|
-
thumbnail: product.thumbnail,
|
|
53
|
-
price: price.calculated_amount,
|
|
54
|
-
currencyCode: price.currency_code,
|
|
55
|
-
variantId: variant.id,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type CartCrossSellSidebarProps = {
|
|
60
|
-
label?: string
|
|
61
|
-
products: CrossSellSidebarProduct[]
|
|
62
|
-
onAdd?: (productId: string, variantId?: string) => void
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function CartCrossSellSidebar({
|
|
66
|
-
label,
|
|
67
|
-
products,
|
|
68
|
-
onAdd,
|
|
69
|
-
}: CartCrossSellSidebarProps) {
|
|
70
|
-
const { labels, hrefs } = useCartDrawer()
|
|
71
|
-
|
|
72
|
-
if (!products.length) return null
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="h-full flex flex-col">
|
|
76
|
-
<div className="px-5 pt-6 pb-4">
|
|
77
|
-
<h3 className="text-xs font-bold text-foreground uppercase tracking-widest">
|
|
78
|
-
{label ?? labels.youMightAlsoLike}
|
|
79
|
-
</h3>
|
|
80
|
-
</div>
|
|
81
|
-
<div className="flex-1 overflow-y-auto no-scrollbar px-4 pb-6 space-y-3">
|
|
82
|
-
{products.map((product) => (
|
|
83
|
-
<div
|
|
84
|
-
key={product.id}
|
|
85
|
-
className="bg-card rounded-[2px] p-3.5 transition-shadow hover:shadow-md"
|
|
86
|
-
style={{
|
|
87
|
-
boxShadow: "0 1px 4px rgba(0,0,0,0.06), 0 0 1px rgba(0,0,0,0.08)",
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
90
|
-
<Link href={`${hrefs.productPrefix}/${product.handle}`}>
|
|
91
|
-
<div className="w-full aspect-square rounded-xl overflow-hidden bg-muted relative mb-3">
|
|
92
|
-
{product.thumbnail ? (
|
|
93
|
-
<Image
|
|
94
|
-
src={product.thumbnail}
|
|
95
|
-
alt={product.title}
|
|
96
|
-
fill
|
|
97
|
-
className="object-cover"
|
|
98
|
-
sizes="200px"
|
|
99
|
-
/>
|
|
100
|
-
) : (
|
|
101
|
-
<div className="w-full h-full flex items-center justify-center">
|
|
102
|
-
<svg
|
|
103
|
-
width="32"
|
|
104
|
-
height="32"
|
|
105
|
-
viewBox="0 0 24 24"
|
|
106
|
-
fill="none"
|
|
107
|
-
stroke="currentColor"
|
|
108
|
-
className="text-muted-foreground"
|
|
109
|
-
strokeWidth="1.2"
|
|
110
|
-
>
|
|
111
|
-
<rect x="3" y="3" width="18" height="18" rx="3" />
|
|
112
|
-
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
113
|
-
<path
|
|
114
|
-
d="M21 15l-5-5L5 21"
|
|
115
|
-
strokeLinecap="round"
|
|
116
|
-
strokeLinejoin="round"
|
|
117
|
-
/>
|
|
118
|
-
</svg>
|
|
119
|
-
</div>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
</Link>
|
|
123
|
-
<Link
|
|
124
|
-
href={`${hrefs.productPrefix}/${product.handle}`}
|
|
125
|
-
className="text-[13px] font-semibold text-foreground leading-tight line-clamp-2 hover:text-muted-foreground transition-colors block"
|
|
126
|
-
>
|
|
127
|
-
{product.title}
|
|
128
|
-
</Link>
|
|
129
|
-
<p className="text-sm font-semibold text-foreground mt-1.5">
|
|
130
|
-
{convertToLocale({
|
|
131
|
-
amount: product.price,
|
|
132
|
-
currency_code: product.currencyCode,
|
|
133
|
-
})}
|
|
134
|
-
</p>
|
|
135
|
-
<button
|
|
136
|
-
type="button"
|
|
137
|
-
onClick={() => onAdd?.(product.id, product.variantId)}
|
|
138
|
-
className="mt-3 w-full h-10 text-xs font-semibold text-foreground bg-card border border-border rounded-xl hover:bg-foreground hover:text-card hover:border-text-base transition-all flex items-center justify-center gap-1.5 active:scale-[0.97]"
|
|
139
|
-
>
|
|
140
|
-
{labels.addToCart}
|
|
141
|
-
<svg
|
|
142
|
-
width="12"
|
|
143
|
-
height="12"
|
|
144
|
-
viewBox="0 0 12 12"
|
|
145
|
-
fill="none"
|
|
146
|
-
stroke="currentColor"
|
|
147
|
-
strokeWidth="1.5"
|
|
148
|
-
strokeLinecap="round"
|
|
149
|
-
>
|
|
150
|
-
<path d="M2.5 6h7M7 3.5L9.5 6 7 8.5" />
|
|
151
|
-
</svg>
|
|
152
|
-
</button>
|
|
153
|
-
</div>
|
|
154
|
-
))}
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
)
|
|
158
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { StoreImage as Image } from "../lib/media-image"
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
|
|
6
|
+
import type { StoreProduct } from "../api/products"
|
|
7
|
+
import { convertToLocale } from "../lib/money"
|
|
8
|
+
import { useCartDrawer } from "./context"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CartCrossSellSidebar — vertical product-card list for desktop cart
|
|
12
|
+
* drawer's left sidebar.
|
|
13
|
+
*
|
|
14
|
+
* Ported from `@1click/ui/src/cart-drawer/cross-sell-sidebar.tsx` (v2.3.1).
|
|
15
|
+
* Cartbase data seam: source stores fed this from Medusa product fetches —
|
|
16
|
+
* feed it from `@cartbase/storefront/api/products` `listProducts` (curated
|
|
17
|
+
* collection/tag) or `@cartbase/storefront/api/search` `listRelatedProducts`
|
|
18
|
+
* (anchor-product complements), mapped through `toCrossSellProduct` below.
|
|
19
|
+
* Wire `onAdd` to the provider's `addItem(variantId)`. Prices are EUR
|
|
20
|
+
* major units and come server-computed via `calculated_price` — never
|
|
21
|
+
* recompute client-side.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export type CrossSellSidebarProduct = {
|
|
25
|
+
id: string
|
|
26
|
+
title: string
|
|
27
|
+
handle: string
|
|
28
|
+
thumbnail: string | null
|
|
29
|
+
price: number
|
|
30
|
+
currencyCode: string
|
|
31
|
+
variantId?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Map a store-API product (products/search/related responses all reuse the
|
|
36
|
+
* canonical `StoreProduct` shape) to a cross-sell card. Picks the first
|
|
37
|
+
* variant carrying a server-computed `calculated_price` for the current
|
|
38
|
+
* pricing context; returns null when no variant is priced (pass
|
|
39
|
+
* `currency_code`/`region_id` on the listing call, and skip null results).
|
|
40
|
+
* Pure — exported for unit tests and for building `crossSell.loader`s.
|
|
41
|
+
*/
|
|
42
|
+
export function toCrossSellProduct(
|
|
43
|
+
product: StoreProduct
|
|
44
|
+
): CrossSellSidebarProduct | null {
|
|
45
|
+
const variant = (product.variants ?? []).find((v) => v.calculated_price)
|
|
46
|
+
const price = variant?.calculated_price
|
|
47
|
+
if (!variant || !price) return null
|
|
48
|
+
return {
|
|
49
|
+
id: product.id,
|
|
50
|
+
title: product.title,
|
|
51
|
+
handle: product.handle,
|
|
52
|
+
thumbnail: product.thumbnail,
|
|
53
|
+
price: price.calculated_amount,
|
|
54
|
+
currencyCode: price.currency_code,
|
|
55
|
+
variantId: variant.id,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type CartCrossSellSidebarProps = {
|
|
60
|
+
label?: string
|
|
61
|
+
products: CrossSellSidebarProduct[]
|
|
62
|
+
onAdd?: (productId: string, variantId?: string) => void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function CartCrossSellSidebar({
|
|
66
|
+
label,
|
|
67
|
+
products,
|
|
68
|
+
onAdd,
|
|
69
|
+
}: CartCrossSellSidebarProps) {
|
|
70
|
+
const { labels, hrefs } = useCartDrawer()
|
|
71
|
+
|
|
72
|
+
if (!products.length) return null
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="h-full flex flex-col">
|
|
76
|
+
<div className="px-5 pt-6 pb-4">
|
|
77
|
+
<h3 className="text-xs font-bold text-foreground uppercase tracking-widest">
|
|
78
|
+
{label ?? labels.youMightAlsoLike}
|
|
79
|
+
</h3>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="flex-1 overflow-y-auto no-scrollbar px-4 pb-6 space-y-3">
|
|
82
|
+
{products.map((product) => (
|
|
83
|
+
<div
|
|
84
|
+
key={product.id}
|
|
85
|
+
className="bg-card rounded-[2px] p-3.5 transition-shadow hover:shadow-md"
|
|
86
|
+
style={{
|
|
87
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.06), 0 0 1px rgba(0,0,0,0.08)",
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<Link href={`${hrefs.productPrefix}/${product.handle}`}>
|
|
91
|
+
<div className="w-full aspect-square rounded-xl overflow-hidden bg-muted relative mb-3">
|
|
92
|
+
{product.thumbnail ? (
|
|
93
|
+
<Image
|
|
94
|
+
src={product.thumbnail}
|
|
95
|
+
alt={product.title}
|
|
96
|
+
fill
|
|
97
|
+
className="object-cover"
|
|
98
|
+
sizes="200px"
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<div className="w-full h-full flex items-center justify-center">
|
|
102
|
+
<svg
|
|
103
|
+
width="32"
|
|
104
|
+
height="32"
|
|
105
|
+
viewBox="0 0 24 24"
|
|
106
|
+
fill="none"
|
|
107
|
+
stroke="currentColor"
|
|
108
|
+
className="text-muted-foreground"
|
|
109
|
+
strokeWidth="1.2"
|
|
110
|
+
>
|
|
111
|
+
<rect x="3" y="3" width="18" height="18" rx="3" />
|
|
112
|
+
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
113
|
+
<path
|
|
114
|
+
d="M21 15l-5-5L5 21"
|
|
115
|
+
strokeLinecap="round"
|
|
116
|
+
strokeLinejoin="round"
|
|
117
|
+
/>
|
|
118
|
+
</svg>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</Link>
|
|
123
|
+
<Link
|
|
124
|
+
href={`${hrefs.productPrefix}/${product.handle}`}
|
|
125
|
+
className="text-[13px] font-semibold text-foreground leading-tight line-clamp-2 hover:text-muted-foreground transition-colors block"
|
|
126
|
+
>
|
|
127
|
+
{product.title}
|
|
128
|
+
</Link>
|
|
129
|
+
<p className="text-sm font-semibold text-foreground mt-1.5">
|
|
130
|
+
{convertToLocale({
|
|
131
|
+
amount: product.price,
|
|
132
|
+
currency_code: product.currencyCode,
|
|
133
|
+
})}
|
|
134
|
+
</p>
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={() => onAdd?.(product.id, product.variantId)}
|
|
138
|
+
className="mt-3 w-full h-10 text-xs font-semibold text-foreground bg-card border border-border rounded-xl hover:bg-foreground hover:text-card hover:border-text-base transition-all flex items-center justify-center gap-1.5 active:scale-[0.97]"
|
|
139
|
+
>
|
|
140
|
+
{labels.addToCart}
|
|
141
|
+
<svg
|
|
142
|
+
width="12"
|
|
143
|
+
height="12"
|
|
144
|
+
viewBox="0 0 12 12"
|
|
145
|
+
fill="none"
|
|
146
|
+
stroke="currentColor"
|
|
147
|
+
strokeWidth="1.5"
|
|
148
|
+
strokeLinecap="round"
|
|
149
|
+
>
|
|
150
|
+
<path d="M2.5 6h7M7 3.5L9.5 6 7 8.5" />
|
|
151
|
+
</svg>
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useState } from "react"
|
|
4
|
-
|
|
5
|
-
import { Price } from "../lib/price"
|
|
6
|
-
import { cn } from "../lib/utils"
|
|
7
|
-
import { useCartDrawer } from "./context"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
|
|
11
|
-
*
|
|
12
|
-
* Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
|
|
13
|
-
* originally extracted from mindpages-storefront
|
|
14
|
-
* src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
|
|
15
|
-
* The store owns the effect: wire `onToggle` to add/remove the store's
|
|
16
|
-
* gift-wrap variant via the provider's `addItem` / `removeItem`.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
type CartGiftWrapProps = {
|
|
20
|
-
price: number
|
|
21
|
-
currencyCode: string
|
|
22
|
-
enabled?: boolean
|
|
23
|
-
onToggle?: (enabled: boolean) => void
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function CartGiftWrap({
|
|
27
|
-
price,
|
|
28
|
-
currencyCode,
|
|
29
|
-
enabled = false,
|
|
30
|
-
onToggle,
|
|
31
|
-
}: CartGiftWrapProps) {
|
|
32
|
-
const { labels } = useCartDrawer()
|
|
33
|
-
const [checked, setChecked] = useState(enabled)
|
|
34
|
-
|
|
35
|
-
const handleToggle = () => {
|
|
36
|
-
const next = !checked
|
|
37
|
-
setChecked(next)
|
|
38
|
-
onToggle?.(next)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className="px-5 sm:px-6 py-2">
|
|
43
|
-
<button
|
|
44
|
-
type="button"
|
|
45
|
-
onClick={handleToggle}
|
|
46
|
-
className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
|
|
47
|
-
>
|
|
48
|
-
<div
|
|
49
|
-
className={cn(
|
|
50
|
-
"w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
|
|
51
|
-
checked
|
|
52
|
-
? "bg-foreground border-text-base"
|
|
53
|
-
: "border-border group-hover:border-text-muted group-active:border-text-muted"
|
|
54
|
-
)}
|
|
55
|
-
>
|
|
56
|
-
{checked && (
|
|
57
|
-
<svg
|
|
58
|
-
width="10"
|
|
59
|
-
height="10"
|
|
60
|
-
viewBox="0 0 10 10"
|
|
61
|
-
fill="none"
|
|
62
|
-
stroke="white"
|
|
63
|
-
strokeWidth="2"
|
|
64
|
-
strokeLinecap="round"
|
|
65
|
-
strokeLinejoin="round"
|
|
66
|
-
>
|
|
67
|
-
<path d="M2 5l2 2 4-4" />
|
|
68
|
-
</svg>
|
|
69
|
-
)}
|
|
70
|
-
</div>
|
|
71
|
-
<span className="text-[13px] text-foreground flex-1">
|
|
72
|
-
{labels.addGiftWrap}
|
|
73
|
-
</span>
|
|
74
|
-
<Price
|
|
75
|
-
amount={price}
|
|
76
|
-
currencyCode={currencyCode}
|
|
77
|
-
className="text-[13px] text-muted-foreground font-medium"
|
|
78
|
-
/>
|
|
79
|
-
</button>
|
|
80
|
-
</div>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
|
|
5
|
+
import { Price } from "../lib/price"
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
import { useCartDrawer } from "./context"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
|
|
11
|
+
*
|
|
12
|
+
* Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
|
|
13
|
+
* originally extracted from mindpages-storefront
|
|
14
|
+
* src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
|
|
15
|
+
* The store owns the effect: wire `onToggle` to add/remove the store's
|
|
16
|
+
* gift-wrap variant via the provider's `addItem` / `removeItem`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type CartGiftWrapProps = {
|
|
20
|
+
price: number
|
|
21
|
+
currencyCode: string
|
|
22
|
+
enabled?: boolean
|
|
23
|
+
onToggle?: (enabled: boolean) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function CartGiftWrap({
|
|
27
|
+
price,
|
|
28
|
+
currencyCode,
|
|
29
|
+
enabled = false,
|
|
30
|
+
onToggle,
|
|
31
|
+
}: CartGiftWrapProps) {
|
|
32
|
+
const { labels } = useCartDrawer()
|
|
33
|
+
const [checked, setChecked] = useState(enabled)
|
|
34
|
+
|
|
35
|
+
const handleToggle = () => {
|
|
36
|
+
const next = !checked
|
|
37
|
+
setChecked(next)
|
|
38
|
+
onToggle?.(next)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="px-5 sm:px-6 py-2">
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={handleToggle}
|
|
46
|
+
className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
className={cn(
|
|
50
|
+
"w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
|
|
51
|
+
checked
|
|
52
|
+
? "bg-foreground border-text-base"
|
|
53
|
+
: "border-border group-hover:border-text-muted group-active:border-text-muted"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{checked && (
|
|
57
|
+
<svg
|
|
58
|
+
width="10"
|
|
59
|
+
height="10"
|
|
60
|
+
viewBox="0 0 10 10"
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="white"
|
|
63
|
+
strokeWidth="2"
|
|
64
|
+
strokeLinecap="round"
|
|
65
|
+
strokeLinejoin="round"
|
|
66
|
+
>
|
|
67
|
+
<path d="M2 5l2 2 4-4" />
|
|
68
|
+
</svg>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
<span className="text-[13px] text-foreground flex-1">
|
|
72
|
+
{labels.addGiftWrap}
|
|
73
|
+
</span>
|
|
74
|
+
<Price
|
|
75
|
+
amount={price}
|
|
76
|
+
currencyCode={currencyCode}
|
|
77
|
+
className="text-[13px] text-muted-foreground font-medium"
|
|
78
|
+
/>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
package/src/cart-drawer/index.ts
CHANGED
|
@@ -1,68 +1,71 @@
|
|
|
1
|
-
// @cartbase/storefront/cart-drawer — barrel export
|
|
2
|
-
//
|
|
3
|
-
// Re-exports every cart drawer primitive + the optional default assembly.
|
|
4
|
-
// Stores can import from this subpath:
|
|
5
|
-
//
|
|
6
|
-
// import {
|
|
7
|
-
// CartDrawerProvider,
|
|
8
|
-
// CartDrawer,
|
|
9
|
-
// CartDrawerHeader,
|
|
10
|
-
// CartItem,
|
|
11
|
-
// CartStickyFooter,
|
|
12
|
-
// CartDrawerTemplate,
|
|
13
|
-
// } from "@cartbase/storefront/cart-drawer"
|
|
14
|
-
//
|
|
15
|
-
// Prefer the per-file subpaths in app code (tree-shaking + RSC boundary
|
|
16
|
-
// clarity) — every file here is also exported individually in package.json.
|
|
17
|
-
|
|
18
|
-
export {
|
|
19
|
-
CartDrawerProvider,
|
|
20
|
-
useCartDrawer,
|
|
21
|
-
reduceCart,
|
|
22
|
-
makeOptimisticLine,
|
|
23
|
-
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
export {
|
|
46
|
-
export {
|
|
47
|
-
export {
|
|
48
|
-
export {
|
|
49
|
-
export {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export {
|
|
68
|
-
export {
|
|
1
|
+
// @cartbase/storefront/cart-drawer — barrel export
|
|
2
|
+
//
|
|
3
|
+
// Re-exports every cart drawer primitive + the optional default assembly.
|
|
4
|
+
// Stores can import from this subpath:
|
|
5
|
+
//
|
|
6
|
+
// import {
|
|
7
|
+
// CartDrawerProvider,
|
|
8
|
+
// CartDrawer,
|
|
9
|
+
// CartDrawerHeader,
|
|
10
|
+
// CartItem,
|
|
11
|
+
// CartStickyFooter,
|
|
12
|
+
// CartDrawerTemplate,
|
|
13
|
+
// } from "@cartbase/storefront/cart-drawer"
|
|
14
|
+
//
|
|
15
|
+
// Prefer the per-file subpaths in app code (tree-shaking + RSC boundary
|
|
16
|
+
// clarity) — every file here is also exported individually in package.json.
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
CartDrawerProvider,
|
|
20
|
+
useCartDrawer,
|
|
21
|
+
reduceCart,
|
|
22
|
+
makeOptimisticLine,
|
|
23
|
+
isPendingLine,
|
|
24
|
+
type CartMutationResult,
|
|
25
|
+
type OptimisticCartAction,
|
|
26
|
+
type OptimisticAddDisplay,
|
|
27
|
+
type OptimisticCartError,
|
|
28
|
+
} from "./context"
|
|
29
|
+
export { createCartMutationQueue, type CartMutationQueue } from "./mutation-queue"
|
|
30
|
+
export {
|
|
31
|
+
defaultCartDrawerLabels,
|
|
32
|
+
type CartDrawerLabels,
|
|
33
|
+
} from "./labels"
|
|
34
|
+
|
|
35
|
+
export { CartDrawer } from "./cart-drawer"
|
|
36
|
+
export { CartDrawerHeader } from "./header"
|
|
37
|
+
export { CartPromoBanner } from "./promo-banner"
|
|
38
|
+
export {
|
|
39
|
+
CartTieredProgress,
|
|
40
|
+
computeTierProgress,
|
|
41
|
+
type CartTier,
|
|
42
|
+
type TierProgress,
|
|
43
|
+
} from "./tiered-progress"
|
|
44
|
+
export { CartItem } from "./item"
|
|
45
|
+
export { CartItemQuantity } from "./item/quantity"
|
|
46
|
+
export { CartItemVariant } from "./item/variant"
|
|
47
|
+
export { CartItemUpsell, type UpsellProduct } from "./item/upsell"
|
|
48
|
+
export { CartFreeGift } from "./free-gift"
|
|
49
|
+
export { CartGiftWrap } from "./gift-wrap"
|
|
50
|
+
export { CartNotes } from "./notes"
|
|
51
|
+
export { CartRewardsPoints } from "./rewards-points"
|
|
52
|
+
export {
|
|
53
|
+
CartCrossSellSidebar,
|
|
54
|
+
toCrossSellProduct,
|
|
55
|
+
type CrossSellSidebarProduct,
|
|
56
|
+
} from "./cross-sell-sidebar"
|
|
57
|
+
export {
|
|
58
|
+
CartCrossSellCarousel,
|
|
59
|
+
type CrossSellCarouselProduct,
|
|
60
|
+
} from "./cross-sell-carousel"
|
|
61
|
+
export {
|
|
62
|
+
CartSummaryBreakdown,
|
|
63
|
+
selectSummaryRows,
|
|
64
|
+
type SummaryCartTotals,
|
|
65
|
+
type SummaryRow,
|
|
66
|
+
} from "./summary-breakdown"
|
|
67
|
+
export { CartStickyFooter } from "./sticky-footer"
|
|
68
|
+
export { CartPaymentBadges } from "./payment-badges"
|
|
69
|
+
export { CartContinueShopping } from "./continue-shopping"
|
|
70
|
+
export { CartEmpty } from "./empty"
|
|
71
|
+
export { CartDrawerTemplate, type CartDrawerConfig } from "./template"
|