@cartbase/storefront 0.8.0 → 0.10.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/package.json +7 -8
- package/src/cart-drawer/cross-sell-carousel.tsx +2 -3
- package/src/cart-drawer/gift-wrap.tsx +82 -83
- package/src/cart-drawer/index.ts +0 -1
- package/src/cart-drawer/item/index.tsx +4 -6
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/checkout/address-error-copy.ts +117 -119
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -0
- package/src/checkout/gift-card-section.tsx +4 -4
- package/src/checkout/index.ts +4 -5
- package/src/checkout/labels.ts +218 -0
- package/src/checkout/line-item-card.tsx +3 -3
- package/src/checkout/order-summary.tsx +11 -12
- package/src/checkout/payment-button.tsx +372 -373
- package/src/checkout/payment-error-copy.ts +154 -269
- package/src/checkout/promotion-error-copy.ts +91 -124
- package/src/checkout/shipping-method-list.tsx +2 -2
- package/src/checkout/use-checkout-orchestration.ts +10 -8
- package/src/lib/money.ts +1 -1
- package/src/lib/price.tsx +39 -0
- package/src/locales/bg.ts +468 -0
- package/src/locales/en.ts +26 -0
- package/src/locales/es.ts +467 -0
- package/src/locales/index.ts +19 -0
- package/src/locales/provider.tsx +63 -0
- package/src/locales/types.ts +49 -0
- package/src/order/index.ts +0 -1
- package/src/order/order-delivery-card.tsx +2 -2
- package/src/order/order-item.tsx +3 -3
- package/src/order/order-totals.tsx +4 -4
- package/src/products/index.ts +0 -1
- package/src/reviews-ui/index.ts +0 -1
- package/src/store/index.ts +0 -1
- package/src/tracking/attribution.ts +13 -0
- package/src/tracking/chatgpt-pixel.tsx +92 -0
- package/src/tracking/consent-banner.tsx +5 -1
- package/src/tracking/consent.ts +12 -0
- package/src/tracking/events.ts +56 -2
- package/src/tracking/index.ts +12 -0
- package/src/tracking/oaiq.ts +206 -0
- package/src/tracking/storefront-tags.tsx +2 -0
- package/src/tracking/types.ts +4 -0
- package/src/cart-drawer/labels-bg.ts +0 -72
- package/src/checkout/labels-bg.ts +0 -128
- package/src/lib/dual-price.tsx +0 -73
- package/src/order/labels-bg.ts +0 -39
- package/src/products/labels-bg.ts +0 -35
- package/src/reviews-ui/labels-bg.ts +0 -91
- package/src/store/labels-bg.ts +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartbase/storefront",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Storefront SDK + UI component library for Cartbase stores: typed API client, checkout orchestration, cart drawer, product/catalog components, tracking. Source-shipped TypeScript — add it to transpilePackages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"./lib/get-percentage-diff": "./src/lib/get-percentage-diff.ts",
|
|
50
50
|
"./lib/product": "./src/lib/product.ts",
|
|
51
51
|
"./lib/sort-products": "./src/lib/sort-products.ts",
|
|
52
|
-
"./lib/
|
|
52
|
+
"./lib/price": "./src/lib/price.tsx",
|
|
53
53
|
"./lib/payment-constants": "./src/lib/payment-constants.ts",
|
|
54
54
|
"./lib/store-api-error": "./src/lib/store-api-error.ts",
|
|
55
55
|
"./lib/hooks/use-intersection": "./src/lib/hooks/use-intersection.ts",
|
|
@@ -103,7 +103,6 @@
|
|
|
103
103
|
"./checkout": "./src/checkout/index.ts",
|
|
104
104
|
"./checkout/context": "./src/checkout/context.tsx",
|
|
105
105
|
"./checkout/labels": "./src/checkout/labels.ts",
|
|
106
|
-
"./checkout/labels-bg": "./src/checkout/labels-bg.ts",
|
|
107
106
|
"./checkout/error-message": "./src/checkout/error-message.tsx",
|
|
108
107
|
"./checkout/stripe-wrapper": "./src/checkout/stripe-wrapper.tsx",
|
|
109
108
|
"./checkout/payment-wrapper": "./src/checkout/payment-wrapper.tsx",
|
|
@@ -127,7 +126,6 @@
|
|
|
127
126
|
"./cart-drawer": "./src/cart-drawer/index.ts",
|
|
128
127
|
"./cart-drawer/context": "./src/cart-drawer/context.tsx",
|
|
129
128
|
"./cart-drawer/labels": "./src/cart-drawer/labels.ts",
|
|
130
|
-
"./cart-drawer/labels-bg": "./src/cart-drawer/labels-bg.ts",
|
|
131
129
|
"./cart-drawer/cart-drawer": "./src/cart-drawer/cart-drawer.tsx",
|
|
132
130
|
"./cart-drawer/header": "./src/cart-drawer/header.tsx",
|
|
133
131
|
"./cart-drawer/promo-banner": "./src/cart-drawer/promo-banner.tsx",
|
|
@@ -151,7 +149,6 @@
|
|
|
151
149
|
"./products": "./src/products/index.ts",
|
|
152
150
|
"./products/context": "./src/products/context.tsx",
|
|
153
151
|
"./products/labels": "./src/products/labels.ts",
|
|
154
|
-
"./products/labels-bg": "./src/products/labels-bg.ts",
|
|
155
152
|
"./products/thumbnail": "./src/products/thumbnail.tsx",
|
|
156
153
|
"./products/preview-price": "./src/products/preview-price.tsx",
|
|
157
154
|
"./products/product-price": "./src/products/product-price.tsx",
|
|
@@ -167,7 +164,6 @@
|
|
|
167
164
|
"./products/product-template": "./src/products/product-template.tsx",
|
|
168
165
|
"./store": "./src/store/index.ts",
|
|
169
166
|
"./store/labels": "./src/store/labels.ts",
|
|
170
|
-
"./store/labels-bg": "./src/store/labels-bg.ts",
|
|
171
167
|
"./store/pagination": "./src/store/pagination.tsx",
|
|
172
168
|
"./store/sort-select": "./src/store/sort-select.tsx",
|
|
173
169
|
"./store/paginated-products": "./src/store/paginated-products.tsx",
|
|
@@ -179,7 +175,6 @@
|
|
|
179
175
|
"./order": "./src/order/index.ts",
|
|
180
176
|
"./order/context": "./src/order/context.tsx",
|
|
181
177
|
"./order/labels": "./src/order/labels.ts",
|
|
182
|
-
"./order/labels-bg": "./src/order/labels-bg.ts",
|
|
183
178
|
"./order/order-confirmation-header": "./src/order/order-confirmation-header.tsx",
|
|
184
179
|
"./order/order-item": "./src/order/order-item.tsx",
|
|
185
180
|
"./order/order-items-list": "./src/order/order-items-list.tsx",
|
|
@@ -198,7 +193,11 @@
|
|
|
198
193
|
"./common/country-select": "./src/common/country-select.tsx",
|
|
199
194
|
"./common/language-select": "./src/common/language-select.tsx",
|
|
200
195
|
"./common/skeleton": "./src/common/skeleton.tsx",
|
|
201
|
-
"./reviews-ui": "./src/reviews-ui/index.ts"
|
|
196
|
+
"./reviews-ui": "./src/reviews-ui/index.ts",
|
|
197
|
+
"./locales": "./src/locales/index.ts",
|
|
198
|
+
"./locales/en": "./src/locales/en.ts",
|
|
199
|
+
"./locales/es": "./src/locales/es.ts",
|
|
200
|
+
"./locales/bg": "./src/locales/bg.ts"
|
|
202
201
|
},
|
|
203
202
|
"dependencies": {
|
|
204
203
|
"@stripe/react-stripe-js": "^6.1.0",
|
|
@@ -4,7 +4,7 @@ import Image from "next/image"
|
|
|
4
4
|
import Link from "next/link"
|
|
5
5
|
import { useRef, useState } from "react"
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Price } from "../lib/price"
|
|
8
8
|
import { cn } from "../lib/utils"
|
|
9
9
|
import { useCartDrawer } from "./context"
|
|
10
10
|
|
|
@@ -178,11 +178,10 @@ export function CartCrossSellCarousel({
|
|
|
178
178
|
>
|
|
179
179
|
{product.title}
|
|
180
180
|
</Link>
|
|
181
|
-
<
|
|
181
|
+
<Price
|
|
182
182
|
amount={product.price}
|
|
183
183
|
currencyCode={product.currencyCode}
|
|
184
184
|
className="text-[13px] font-bold text-foreground block mt-0.5"
|
|
185
|
-
bgnClassName="text-muted-foreground text-[10px] ml-1"
|
|
186
185
|
/>
|
|
187
186
|
</div>
|
|
188
187
|
|
|
@@ -1,83 +1,82 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useState } from "react"
|
|
4
|
-
|
|
5
|
-
import {
|
|
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
|
-
<
|
|
75
|
-
amount={price}
|
|
76
|
-
currencyCode={currencyCode}
|
|
77
|
-
className="text-[13px] text-muted-foreground font-medium"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
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
|
@@ -5,7 +5,7 @@ import Link from "next/link"
|
|
|
5
5
|
import { type ReactNode } from "react"
|
|
6
6
|
|
|
7
7
|
import type { CartLineItem } from "../../api/carts"
|
|
8
|
-
import {
|
|
8
|
+
import { Price } from "../../lib/price"
|
|
9
9
|
import { cn } from "../../lib/utils"
|
|
10
10
|
import { useCartDrawer } from "../context"
|
|
11
11
|
import { CartItemQuantity } from "./quantity"
|
|
@@ -13,7 +13,7 @@ import { CartItemVariant } from "./variant"
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* CartItem — a single cart line row: thumbnail, title, variant, quantity
|
|
16
|
-
* stepper, price (with
|
|
16
|
+
* stepper, price (with optional strikethrough), remove button.
|
|
17
17
|
*
|
|
18
18
|
* Ported from `@1click/ui/src/cart-drawer/item/index.tsx` (v2.3.1). Cartbase
|
|
19
19
|
* data seam: `item` is the SDK's decorated `CartLineItem`
|
|
@@ -111,22 +111,20 @@ export function CartItem({ item, currencyCode, children }: CartItemProps) {
|
|
|
111
111
|
<div className="text-right flex-shrink-0">
|
|
112
112
|
{hasDiscount && (
|
|
113
113
|
<span className="text-xs text-muted-foreground line-through block leading-none mb-0.5">
|
|
114
|
-
<
|
|
114
|
+
<Price
|
|
115
115
|
amount={originalTotal}
|
|
116
116
|
currencyCode={currencyCode}
|
|
117
117
|
className="text-xs text-muted-foreground"
|
|
118
|
-
bgnClassName="hidden"
|
|
119
118
|
/>
|
|
120
119
|
</span>
|
|
121
120
|
)}
|
|
122
|
-
<
|
|
121
|
+
<Price
|
|
123
122
|
amount={total}
|
|
124
123
|
currencyCode={currencyCode}
|
|
125
124
|
className={cn(
|
|
126
125
|
"text-sm font-bold",
|
|
127
126
|
hasDiscount ? "text-success" : "text-foreground"
|
|
128
127
|
)}
|
|
129
|
-
bgnClassName="text-muted-foreground text-[10px] ml-1"
|
|
130
128
|
/>
|
|
131
129
|
</div>
|
|
132
130
|
</div>
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import Link from "next/link"
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import { productTotal } from "../lib/cart-helpers"
|
|
7
|
-
import { useCartDrawer } from "./context"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* CartStickyFooter — bottom-of-drawer subtotal + checkout CTA with
|
|
11
|
-
* iPhone home-indicator safe-area padding.
|
|
12
|
-
*
|
|
13
|
-
* Ported from `@1click/ui/src/cart-drawer/sticky-footer.tsx` (v2.3.1);
|
|
14
|
-
* originally extracted from mindpages-storefront
|
|
15
|
-
* src/modules/cart-drawer/cart-sticky-footer.tsx. Amounts EUR major units.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export function CartStickyFooter() {
|
|
19
|
-
const { cart, close, labels, hrefs } = useCartDrawer()
|
|
20
|
-
if (!cart || !cart.items?.length) return null
|
|
21
|
-
|
|
22
|
-
// Drawer is pre-checkout context — never read `cart.total` here.
|
|
23
|
-
// `cart.total` includes shipping/tax/COD fee, all of which are
|
|
24
|
-
// checkout-context state that must not leak into the shopping
|
|
25
|
-
// drawer. Sum of product line totals only.
|
|
26
|
-
const total = productTotal(cart.items)
|
|
27
|
-
const currencyCode = cart.currency_code
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div
|
|
31
|
-
className="flex-shrink-0 bg-card px-5 sm:px-6 pt-4 sm:pt-5 relative z-10"
|
|
32
|
-
style={{
|
|
33
|
-
boxShadow: "0 -4px 12px rgba(0,0,0,0.04)",
|
|
34
|
-
paddingBottom: "max(1.25rem, env(safe-area-inset-bottom, 1.25rem))",
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<div className="flex items-baseline justify-between mb-1">
|
|
38
|
-
<span className="text-[15px] font-bold text-foreground">
|
|
39
|
-
{labels.subtotal}
|
|
40
|
-
</span>
|
|
41
|
-
<
|
|
42
|
-
amount={total}
|
|
43
|
-
currencyCode={currencyCode}
|
|
44
|
-
className="text-xl font-bold text-foreground tracking-tight"
|
|
45
|
-
/>
|
|
46
|
-
</div>
|
|
47
|
-
<p className="text-[11px] text-muted-foreground mb-3.5 sm:mb-4">
|
|
48
|
-
{labels.taxAndShipping}
|
|
49
|
-
</p>
|
|
50
|
-
<Link href={hrefs.checkout} onClick={close}>
|
|
51
|
-
<button
|
|
52
|
-
type="button"
|
|
53
|
-
className="w-full h-[52px] bg-foreground hover:bg-foreground/90 active:bg-foreground/95 text-card text-[15px] sm:text-sm font-semibold rounded-[2px] flex items-center justify-center gap-2.5 transition-colors active:scale-[0.98]"
|
|
54
|
-
>
|
|
55
|
-
<svg
|
|
56
|
-
width="16"
|
|
57
|
-
height="16"
|
|
58
|
-
viewBox="0 0 16 16"
|
|
59
|
-
fill="none"
|
|
60
|
-
stroke="currentColor"
|
|
61
|
-
strokeWidth="1.5"
|
|
62
|
-
strokeLinecap="round"
|
|
63
|
-
strokeLinejoin="round"
|
|
64
|
-
>
|
|
65
|
-
<rect x="3" y="6" width="10" height="8" rx="1.5" />
|
|
66
|
-
<path d="M5.5 6V4.5a2.5 2.5 0 015 0V6" />
|
|
67
|
-
</svg>
|
|
68
|
-
{labels.secureCheckout}
|
|
69
|
-
</button>
|
|
70
|
-
</Link>
|
|
71
|
-
</div>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import Link from "next/link"
|
|
4
|
+
|
|
5
|
+
import { Price } from "../lib/price"
|
|
6
|
+
import { productTotal } from "../lib/cart-helpers"
|
|
7
|
+
import { useCartDrawer } from "./context"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* CartStickyFooter — bottom-of-drawer subtotal + checkout CTA with
|
|
11
|
+
* iPhone home-indicator safe-area padding.
|
|
12
|
+
*
|
|
13
|
+
* Ported from `@1click/ui/src/cart-drawer/sticky-footer.tsx` (v2.3.1);
|
|
14
|
+
* originally extracted from mindpages-storefront
|
|
15
|
+
* src/modules/cart-drawer/cart-sticky-footer.tsx. Amounts EUR major units.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export function CartStickyFooter() {
|
|
19
|
+
const { cart, close, labels, hrefs } = useCartDrawer()
|
|
20
|
+
if (!cart || !cart.items?.length) return null
|
|
21
|
+
|
|
22
|
+
// Drawer is pre-checkout context — never read `cart.total` here.
|
|
23
|
+
// `cart.total` includes shipping/tax/COD fee, all of which are
|
|
24
|
+
// checkout-context state that must not leak into the shopping
|
|
25
|
+
// drawer. Sum of product line totals only.
|
|
26
|
+
const total = productTotal(cart.items)
|
|
27
|
+
const currencyCode = cart.currency_code
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className="flex-shrink-0 bg-card px-5 sm:px-6 pt-4 sm:pt-5 relative z-10"
|
|
32
|
+
style={{
|
|
33
|
+
boxShadow: "0 -4px 12px rgba(0,0,0,0.04)",
|
|
34
|
+
paddingBottom: "max(1.25rem, env(safe-area-inset-bottom, 1.25rem))",
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<div className="flex items-baseline justify-between mb-1">
|
|
38
|
+
<span className="text-[15px] font-bold text-foreground">
|
|
39
|
+
{labels.subtotal}
|
|
40
|
+
</span>
|
|
41
|
+
<Price
|
|
42
|
+
amount={total}
|
|
43
|
+
currencyCode={currencyCode}
|
|
44
|
+
className="text-xl font-bold text-foreground tracking-tight"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<p className="text-[11px] text-muted-foreground mb-3.5 sm:mb-4">
|
|
48
|
+
{labels.taxAndShipping}
|
|
49
|
+
</p>
|
|
50
|
+
<Link href={hrefs.checkout} onClick={close}>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className="w-full h-[52px] bg-foreground hover:bg-foreground/90 active:bg-foreground/95 text-card text-[15px] sm:text-sm font-semibold rounded-[2px] flex items-center justify-center gap-2.5 transition-colors active:scale-[0.98]"
|
|
54
|
+
>
|
|
55
|
+
<svg
|
|
56
|
+
width="16"
|
|
57
|
+
height="16"
|
|
58
|
+
viewBox="0 0 16 16"
|
|
59
|
+
fill="none"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
strokeWidth="1.5"
|
|
62
|
+
strokeLinecap="round"
|
|
63
|
+
strokeLinejoin="round"
|
|
64
|
+
>
|
|
65
|
+
<rect x="3" y="6" width="10" height="8" rx="1.5" />
|
|
66
|
+
<path d="M5.5 6V4.5a2.5 2.5 0 015 0V6" />
|
|
67
|
+
</svg>
|
|
68
|
+
{labels.secureCheckout}
|
|
69
|
+
</button>
|
|
70
|
+
</Link>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|