@thorprovider/blocks 0.1.0 → 0.1.2
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/.turbo/turbo-build.log +15 -15
- package/.turbo/turbo-test.log +32 -5
- package/AGENTS.md +1 -1
- package/README.md +52 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts.d.mts +1111 -90
- package/dist/contracts.d.ts +1111 -90
- package/dist/contracts.js +189 -6
- package/dist/contracts.js.map +1 -1
- package/dist/contracts.mjs +173 -7
- package/dist/contracts.mjs.map +1 -1
- package/dist/index.d.mts +467 -89
- package/dist/index.d.ts +467 -89
- package/dist/index.js +2967 -2250
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2989 -2282
- package/dist/index.mjs.map +1 -1
- package/docs/standards/theming.md +32 -5
- package/package.json +4 -3
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +136 -93
- package/src/blocks/e-commerce/category-filters/category-filter-2.test.tsx +513 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +728 -148
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +14 -8
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.test.tsx +136 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +145 -99
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +215 -102
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +6 -2
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +51 -2
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +216 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +114 -11
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +42 -2
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +10 -8
- package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +51 -0
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +56 -36
- package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +182 -0
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +298 -129
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +137 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +231 -89
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +65 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +19 -0
- package/src/blocks/marketing/footers/footer-section-1.test.tsx +117 -0
- package/src/blocks/marketing/footers/footer-section-1.tsx +46 -9
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.test.tsx +54 -0
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +43 -13
- package/src/blocks/marketing/navbars/navbar-4.test.tsx +112 -0
- package/src/blocks/marketing/navbars/navbar-4.tsx +101 -28
- package/src/index.ts +38 -0
- package/src/lib/cart.test.ts +15 -0
- package/src/lib/cart.ts +11 -0
- package/src/lib/contracts-curated.test.ts +207 -0
- package/src/lib/contracts-server-boundary.test.ts +52 -0
- package/src/lib/contracts.ts +182 -4
- package/src/lib/money.test.ts +64 -0
- package/src/lib/money.ts +65 -0
- package/styles.css +1 -0
- package/tsup.config.ts +0 -1
- package/vitest.config.ts +9 -1
|
@@ -23,8 +23,8 @@ const STAGE_LABELS: Record<FulfilmentStage, string> = {
|
|
|
23
23
|
|
|
24
24
|
interface CheckoutConfirmationFulfilmentProgressProps {
|
|
25
25
|
order: {
|
|
26
|
-
currentStage
|
|
27
|
-
estimatedDelivery
|
|
26
|
+
currentStage?: FulfilmentStage
|
|
27
|
+
estimatedDelivery?: string | null
|
|
28
28
|
trackingHref?: string
|
|
29
29
|
}
|
|
30
30
|
/** Ordered by `Object.keys(fulfilmentDetails)` from the orchestrator. */
|
|
@@ -72,17 +72,21 @@ export function CheckoutConfirmationFulfilmentProgress({
|
|
|
72
72
|
locationLabel = 'Ubicación',
|
|
73
73
|
trackOrderLabel = 'Seguir este pedido',
|
|
74
74
|
}: CheckoutConfirmationFulfilmentProgressProps) {
|
|
75
|
-
const currentIndex =
|
|
76
|
-
|
|
75
|
+
const currentIndex = order.currentStage
|
|
76
|
+
? stages.findIndex(stage => stage.stage === order.currentStage)
|
|
77
|
+
: -1
|
|
78
|
+
const [selected, setSelected] = useState<string>(order.currentStage ?? stages[0]?.stage ?? '')
|
|
77
79
|
|
|
78
80
|
return (
|
|
79
81
|
<Card className='mt-10'>
|
|
80
82
|
<CardHeader>
|
|
81
83
|
<CardTitle className='flex flex-wrap items-center justify-between gap-2 text-base'>
|
|
82
84
|
{progressLabel}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
{order.estimatedDelivery ? (
|
|
86
|
+
<span className='text-muted-foreground text-sm font-normal'>
|
|
87
|
+
{`${estimatedDeliveryLabel} ${longDate.format(new Date(order.estimatedDelivery))}`}
|
|
88
|
+
</span>
|
|
89
|
+
) : null}
|
|
86
90
|
</CardTitle>
|
|
87
91
|
</CardHeader>
|
|
88
92
|
|
|
@@ -156,7 +160,9 @@ export function CheckoutConfirmationFulfilmentProgress({
|
|
|
156
160
|
? stampDate.format(new Date(detail.at))
|
|
157
161
|
: done
|
|
158
162
|
? inProgressLabel
|
|
159
|
-
:
|
|
163
|
+
: order.estimatedDelivery
|
|
164
|
+
? `${expectedByLabel} ${longDate.format(new Date(order.estimatedDelivery))}`
|
|
165
|
+
: null}
|
|
160
166
|
</dd>
|
|
161
167
|
</div>
|
|
162
168
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { CheckoutConfirmation, type Order, type OrderLine } from './checkout-confirmation'
|
|
4
|
+
|
|
5
|
+
const order: Order = {
|
|
6
|
+
number: '1001',
|
|
7
|
+
placedAt: '2026-10-06T00:00:00.000Z',
|
|
8
|
+
totals: {},
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const orderLines: OrderLine[] = [
|
|
12
|
+
{ id: 'l1', name: 'Taladro', quantity: 1, lineTotal: { amount: '45.00', currencyCode: 'USD' } },
|
|
13
|
+
{
|
|
14
|
+
id: 'l2',
|
|
15
|
+
name: 'Batería',
|
|
16
|
+
quantity: 2,
|
|
17
|
+
lineTotal: { amount: '40.00', currencyCode: 'USD' },
|
|
18
|
+
image: '',
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
describe('CheckoutConfirmation — truthful optional data', () => {
|
|
23
|
+
it('omits email, ETA, progress, addresses, and totals when the backend supplies none', () => {
|
|
24
|
+
const html = renderToStaticMarkup(<CheckoutConfirmation order={order} orderLines={orderLines} />)
|
|
25
|
+
expect(html).toContain('Pedido 1001 realizado')
|
|
26
|
+
expect(html).not.toContain('Enviamos la confirmación')
|
|
27
|
+
expect(html).not.toContain('Entrega estimada')
|
|
28
|
+
expect(html).not.toContain('Progreso')
|
|
29
|
+
expect(html).not.toContain('Subtotal')
|
|
30
|
+
expect(html).not.toContain('Total')
|
|
31
|
+
expect(html).not.toContain('Gratis')
|
|
32
|
+
expect(html).not.toContain('Facturación')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders supplied email, totals, and a zero-shipping free label', () => {
|
|
36
|
+
const html = renderToStaticMarkup(
|
|
37
|
+
<CheckoutConfirmation
|
|
38
|
+
order={{
|
|
39
|
+
...order,
|
|
40
|
+
email: 'ana@example.com',
|
|
41
|
+
totals: {
|
|
42
|
+
subtotal: { amount: '85.00', currencyCode: 'USD' },
|
|
43
|
+
shipping: { amount: '0', currencyCode: 'USD' },
|
|
44
|
+
tax: { amount: '6.80', currencyCode: 'USD' },
|
|
45
|
+
total: { amount: '91.80', currencyCode: 'USD' },
|
|
46
|
+
},
|
|
47
|
+
}}
|
|
48
|
+
orderLines={orderLines}
|
|
49
|
+
formatPrice={money => `${money.currencyCode} ${money.amount}`}
|
|
50
|
+
/>,
|
|
51
|
+
)
|
|
52
|
+
expect(html).toContain('ana@example.com')
|
|
53
|
+
expect(html).toContain('USD 85.00')
|
|
54
|
+
expect(html).toContain('Gratis')
|
|
55
|
+
expect(html).toContain('USD 91.80')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('renders the fulfilment progress only when stages and a current stage are supplied', () => {
|
|
59
|
+
const withProgress = renderToStaticMarkup(
|
|
60
|
+
<CheckoutConfirmation
|
|
61
|
+
order={{ ...order, currentStage: 'Confirmed', estimatedDelivery: '2026-10-10T00:00:00.000Z' }}
|
|
62
|
+
orderLines={orderLines}
|
|
63
|
+
fulfilmentDetails={{
|
|
64
|
+
Confirmed: { stage: 'Confirmed', summary: 'Confirmado', location: 'Bodega', at: '2026-10-06T00:00:00.000Z' },
|
|
65
|
+
Packed: { stage: 'Packed', summary: 'Empaquetado', location: 'Bodega', at: null },
|
|
66
|
+
Shipped: { stage: 'Shipped', summary: 'Enviado', location: 'Bodega', at: null },
|
|
67
|
+
Delivered: { stage: 'Delivered', summary: 'Entregado', location: 'Bodega', at: null },
|
|
68
|
+
}}
|
|
69
|
+
/>,
|
|
70
|
+
)
|
|
71
|
+
expect(withProgress).toContain('Progreso')
|
|
72
|
+
expect(withProgress).toContain('Entrega estimada')
|
|
73
|
+
|
|
74
|
+
const withoutProgress = renderToStaticMarkup(
|
|
75
|
+
<CheckoutConfirmation order={order} orderLines={orderLines} />,
|
|
76
|
+
)
|
|
77
|
+
expect(withoutProgress).not.toContain('Progreso')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('omits an absent line image without emitting an empty src', () => {
|
|
81
|
+
const html = renderToStaticMarkup(<CheckoutConfirmation order={order} orderLines={orderLines} />)
|
|
82
|
+
expect(html).not.toMatch(/src=""/)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('renders duplicate address lines without colliding keys', () => {
|
|
86
|
+
const html = renderToStaticMarkup(
|
|
87
|
+
<CheckoutConfirmation
|
|
88
|
+
order={{
|
|
89
|
+
...order,
|
|
90
|
+
shipping: { name: 'Ana', lines: ['Calle 1', 'Calle 1'] },
|
|
91
|
+
}}
|
|
92
|
+
orderLines={orderLines}
|
|
93
|
+
/>,
|
|
94
|
+
)
|
|
95
|
+
expect((html.match(/Calle 1/g) ?? []).length).toBe(2)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('never renders a fake zero for absent or malformed line/total amounts', () => {
|
|
99
|
+
const html = renderToStaticMarkup(
|
|
100
|
+
<CheckoutConfirmation
|
|
101
|
+
order={{
|
|
102
|
+
...order,
|
|
103
|
+
totals: {
|
|
104
|
+
subtotal: { amount: '', currencyCode: 'USD' },
|
|
105
|
+
total: { amount: 'abc', currencyCode: 'USD' },
|
|
106
|
+
},
|
|
107
|
+
}}
|
|
108
|
+
orderLines={[
|
|
109
|
+
{ id: 'l1', name: 'Pieza', quantity: 1, lineTotal: { amount: '', currencyCode: 'USD' } },
|
|
110
|
+
]}
|
|
111
|
+
formatPrice={money => `${money.currencyCode} ${money.amount}`}
|
|
112
|
+
/>,
|
|
113
|
+
)
|
|
114
|
+
expect(html).not.toContain('USD 0')
|
|
115
|
+
expect(html).toContain('—')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('preserves a legitimate zero shipping and total', () => {
|
|
119
|
+
const html = renderToStaticMarkup(
|
|
120
|
+
<CheckoutConfirmation
|
|
121
|
+
order={{
|
|
122
|
+
...order,
|
|
123
|
+
totals: {
|
|
124
|
+
subtotal: { amount: '0', currencyCode: 'USD' },
|
|
125
|
+
shipping: { amount: '0', currencyCode: 'USD' },
|
|
126
|
+
total: { amount: '0', currencyCode: 'USD' },
|
|
127
|
+
},
|
|
128
|
+
}}
|
|
129
|
+
orderLines={orderLines}
|
|
130
|
+
formatPrice={money => `${money.currencyCode} ${money.amount}`}
|
|
131
|
+
/>,
|
|
132
|
+
)
|
|
133
|
+
expect(html).toContain('USD 0')
|
|
134
|
+
expect(html).toContain('Gratis')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -2,6 +2,8 @@ import { CircleCheck } from 'lucide-react'
|
|
|
2
2
|
import { Button } from '../../../ui/button'
|
|
3
3
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
4
4
|
import { Separator } from '../../../ui/separator'
|
|
5
|
+
import { formatMoney, isAvailableMoney } from '../../../lib/money'
|
|
6
|
+
import type { Money } from '@thorprovider/types'
|
|
5
7
|
import { CheckoutConfirmationFulfilmentProgress } from './checkout-confirmation-fulfilment-progress'
|
|
6
8
|
|
|
7
9
|
export type FulfilmentStage = 'Confirmed' | 'Packed' | 'Shipped' | 'Delivered'
|
|
@@ -9,10 +11,13 @@ export type FulfilmentStage = 'Confirmed' | 'Packed' | 'Shipped' | 'Delivered'
|
|
|
9
11
|
export interface OrderLine {
|
|
10
12
|
id: string
|
|
11
13
|
name: string
|
|
12
|
-
variant
|
|
14
|
+
variant?: string
|
|
13
15
|
quantity: number
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/** Canonical backend line total. */
|
|
17
|
+
lineTotal: Money
|
|
18
|
+
unitPrice?: Money
|
|
19
|
+
/** Optional; an absent image is omitted rather than serialized as `""`. */
|
|
20
|
+
image?: string
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export interface FulfilmentStageDetail {
|
|
@@ -32,26 +37,29 @@ export interface FulfilmentStageDetail {
|
|
|
32
37
|
export interface OrderAddress {
|
|
33
38
|
name: string
|
|
34
39
|
lines: string[]
|
|
35
|
-
method
|
|
40
|
+
method?: string
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
export interface OrderTotals {
|
|
39
|
-
subtotal
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
subtotal?: Money
|
|
45
|
+
discount?: Money
|
|
46
|
+
shipping?: Money
|
|
47
|
+
tax?: Money
|
|
48
|
+
total?: Money
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface Order {
|
|
45
52
|
number: string
|
|
46
53
|
/** ISO date */
|
|
47
54
|
placedAt: string
|
|
48
|
-
email
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
email?: string
|
|
56
|
+
/** Present only when the backend reports a fulfilment stage. */
|
|
57
|
+
currentStage?: FulfilmentStage
|
|
58
|
+
/** ISO date. Rendered only when supplied; no delivery estimate is fabricated. */
|
|
59
|
+
estimatedDelivery?: string | null
|
|
52
60
|
trackingHref?: string
|
|
53
|
-
shipping
|
|
54
|
-
billing
|
|
61
|
+
shipping?: OrderAddress
|
|
62
|
+
billing?: OrderAddress
|
|
55
63
|
totals: OrderTotals
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -59,22 +67,28 @@ export interface CheckoutConfirmationProps {
|
|
|
59
67
|
order: Order
|
|
60
68
|
orderLines: OrderLine[]
|
|
61
69
|
/**
|
|
62
|
-
* Keyed by `FulfilmentStage`.
|
|
63
|
-
* so define the record in the canonical
|
|
70
|
+
* Keyed by `FulfilmentStage`. Rendered only when supplied; the stage strip is
|
|
71
|
+
* ordered by `Object.keys`, so define the record in the canonical
|
|
72
|
+
* `Confirmed → Packed → Shipped → Delivered` order.
|
|
64
73
|
*/
|
|
65
|
-
fulfilmentDetails
|
|
66
|
-
/** Override
|
|
67
|
-
formatPrice?: (
|
|
74
|
+
fulfilmentDetails?: Record<FulfilmentStage, FulfilmentStageDetail>
|
|
75
|
+
/** Override money formatting (locale, currency). Defaults to `Money.currencyCode`. */
|
|
76
|
+
formatPrice?: (money: Money) => string
|
|
68
77
|
titleLabel?: string
|
|
69
78
|
/** Template with `{number}`/`{date}`/`{email}` placeholders, resolved internally. */
|
|
70
79
|
orderPlacedLabel?: string
|
|
80
|
+
/** Used when the order carries no email. */
|
|
81
|
+
orderPlacedNoEmailLabel?: string
|
|
71
82
|
itemsLabel?: string
|
|
72
83
|
quantityLabel?: string
|
|
73
84
|
subtotalLabel?: string
|
|
85
|
+
discountLabel?: string
|
|
74
86
|
shippingLabel?: string
|
|
75
87
|
taxLabel?: string
|
|
76
88
|
totalLabel?: string
|
|
77
89
|
freeLabel?: string
|
|
90
|
+
/** Shown for a line whose supplied line total is absent or malformed. */
|
|
91
|
+
amountUnavailableLabel?: string
|
|
78
92
|
shippingAddressLabel?: string
|
|
79
93
|
billingAddressLabel?: string
|
|
80
94
|
continueShoppingLabel?: string
|
|
@@ -95,7 +109,6 @@ export interface CheckoutConfirmationProps {
|
|
|
95
109
|
onContinueShopping?: () => void
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
const usdFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
|
|
99
112
|
const longDate = new Intl.DateTimeFormat('en-US', { dateStyle: 'long' })
|
|
100
113
|
|
|
101
114
|
/**
|
|
@@ -110,15 +123,18 @@ export function CheckoutConfirmation({
|
|
|
110
123
|
orderLines,
|
|
111
124
|
fulfilmentDetails,
|
|
112
125
|
formatPrice,
|
|
113
|
-
titleLabel = '
|
|
114
|
-
orderPlacedLabel = 'Pedido {number}
|
|
115
|
-
|
|
126
|
+
titleLabel = 'Pedido registrado',
|
|
127
|
+
orderPlacedLabel = 'Pedido {number} · {date} · Correo de contacto: {email}',
|
|
128
|
+
orderPlacedNoEmailLabel = 'Pedido {number} realizado el {date}.',
|
|
129
|
+
itemsLabel = 'Comprobante del pedido',
|
|
116
130
|
quantityLabel = 'Cantidad',
|
|
117
131
|
subtotalLabel = 'Subtotal',
|
|
132
|
+
discountLabel = 'Descuento',
|
|
118
133
|
shippingLabel = 'Envío',
|
|
119
134
|
taxLabel = 'Impuestos',
|
|
120
135
|
totalLabel = 'Total',
|
|
121
136
|
freeLabel = 'Gratis',
|
|
137
|
+
amountUnavailableLabel = '—',
|
|
122
138
|
shippingAddressLabel = 'Envío',
|
|
123
139
|
billingAddressLabel = 'Facturación',
|
|
124
140
|
continueShoppingLabel = 'Seguir comprando',
|
|
@@ -137,9 +153,21 @@ export function CheckoutConfirmation({
|
|
|
137
153
|
supportHref,
|
|
138
154
|
onContinueShopping,
|
|
139
155
|
}: CheckoutConfirmationProps) {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
156
|
+
const format = (value: Money | null | undefined) => formatMoney(value, formatPrice)
|
|
157
|
+
const placedDate = longDate.format(new Date(order.placedAt))
|
|
158
|
+
const placedText = order.email
|
|
159
|
+
? orderPlacedLabel
|
|
160
|
+
.replace('{number}', order.number)
|
|
161
|
+
.replace('{date}', placedDate)
|
|
162
|
+
.replace('{email}', order.email)
|
|
163
|
+
: orderPlacedNoEmailLabel.replace('{number}', order.number).replace('{date}', placedDate)
|
|
164
|
+
const fulfilmentStages = fulfilmentDetails
|
|
165
|
+
? (Object.keys(fulfilmentDetails) as FulfilmentStage[])
|
|
166
|
+
: []
|
|
167
|
+
const addresses = [
|
|
168
|
+
{ title: shippingAddressLabel, address: order.shipping },
|
|
169
|
+
{ title: billingAddressLabel, address: order.billing },
|
|
170
|
+
].filter((section): section is { title: string; address: OrderAddress } => Boolean(section.address))
|
|
143
171
|
|
|
144
172
|
return (
|
|
145
173
|
<div className='w-full py-16 sm:py-24'>
|
|
@@ -152,31 +180,28 @@ export function CheckoutConfirmation({
|
|
|
152
180
|
<CircleCheck className='size-6' />
|
|
153
181
|
</span>
|
|
154
182
|
<h1 className='mt-4 text-3xl font-semibold tracking-tight'>{titleLabel}</h1>
|
|
155
|
-
<p className='text-muted-foreground mt-3'>
|
|
156
|
-
{orderPlacedLabel
|
|
157
|
-
.replace('{number}', order.number)
|
|
158
|
-
.replace('{date}', longDate.format(new Date(order.placedAt)))
|
|
159
|
-
.replace('{email}', order.email)}
|
|
160
|
-
</p>
|
|
183
|
+
<p className='text-muted-foreground mt-3'>{placedText}</p>
|
|
161
184
|
</div>
|
|
162
185
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
{fulfilmentDetails && order.currentStage ? (
|
|
187
|
+
<CheckoutConfirmationFulfilmentProgress
|
|
188
|
+
order={{
|
|
189
|
+
currentStage: order.currentStage,
|
|
190
|
+
estimatedDelivery: order.estimatedDelivery,
|
|
191
|
+
trackingHref: order.trackingHref,
|
|
192
|
+
}}
|
|
193
|
+
stages={fulfilmentStages.map(stage => fulfilmentDetails[stage])}
|
|
194
|
+
progressLabel={progressLabel}
|
|
195
|
+
estimatedDeliveryLabel={estimatedDeliveryLabel}
|
|
196
|
+
currentStageLabel={currentStageLabel}
|
|
197
|
+
inProgressLabel={inProgressLabel}
|
|
198
|
+
expectedByLabel={expectedByLabel}
|
|
199
|
+
completedLabel={completedLabel}
|
|
200
|
+
statusLabel={statusLabel}
|
|
201
|
+
locationLabel={locationLabel}
|
|
202
|
+
trackOrderLabel={trackOrderLabel}
|
|
203
|
+
/>
|
|
204
|
+
) : null}
|
|
180
205
|
|
|
181
206
|
<Card className='mt-6'>
|
|
182
207
|
<CardHeader>
|
|
@@ -186,22 +211,26 @@ export function CheckoutConfirmation({
|
|
|
186
211
|
<ul className='divide-border divide-y'>
|
|
187
212
|
{orderLines.map(line => (
|
|
188
213
|
<li key={line.id} className='flex gap-4 py-4 first:pt-0 last:pb-0'>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
{line.image ? (
|
|
215
|
+
<img
|
|
216
|
+
src={line.image || undefined}
|
|
217
|
+
alt={line.name}
|
|
218
|
+
width={72}
|
|
219
|
+
height={88}
|
|
220
|
+
className='bg-muted h-22 w-18 shrink-0 rounded-md object-cover'
|
|
221
|
+
/>
|
|
222
|
+
) : (
|
|
223
|
+
<div aria-hidden='true' className='bg-muted h-22 w-18 shrink-0 rounded-md' />
|
|
224
|
+
)}
|
|
196
225
|
<div className='min-w-0 flex-1'>
|
|
197
226
|
<p className='text-sm font-medium'>{line.name}</p>
|
|
198
|
-
<p className='text-muted-foreground text-sm'>{line.variant}</p>
|
|
227
|
+
{line.variant ? <p className='text-muted-foreground text-sm'>{line.variant}</p> : null}
|
|
199
228
|
<p className='text-muted-foreground text-sm tabular-nums'>
|
|
200
229
|
{`${quantityLabel} ${line.quantity}`}
|
|
201
230
|
</p>
|
|
202
231
|
</div>
|
|
203
232
|
<p className='text-sm font-medium tabular-nums'>
|
|
204
|
-
{format(line.
|
|
233
|
+
{format(line.lineTotal) ?? amountUnavailableLabel}
|
|
205
234
|
</p>
|
|
206
235
|
</li>
|
|
207
236
|
))}
|
|
@@ -210,51 +239,68 @@ export function CheckoutConfirmation({
|
|
|
210
239
|
<Separator className='my-4' />
|
|
211
240
|
|
|
212
241
|
<dl className='flex flex-col gap-2 text-sm'>
|
|
213
|
-
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
{isAvailableMoney(order.totals.subtotal) ? (
|
|
243
|
+
<div className='flex justify-between'>
|
|
244
|
+
<dt className='text-muted-foreground'>{subtotalLabel}</dt>
|
|
245
|
+
<dd className='tabular-nums'>{format(order.totals.subtotal)}</dd>
|
|
246
|
+
</div>
|
|
247
|
+
) : null}
|
|
248
|
+
{isAvailableMoney(order.totals.discount) ? (
|
|
249
|
+
<div className='flex justify-between'>
|
|
250
|
+
<dt className='text-muted-foreground'>{discountLabel}</dt>
|
|
251
|
+
<dd className='tabular-nums'>{format(order.totals.discount)}</dd>
|
|
252
|
+
</div>
|
|
253
|
+
) : null}
|
|
254
|
+
{isAvailableMoney(order.totals.shipping) ? (
|
|
255
|
+
<div className='flex justify-between'>
|
|
256
|
+
<dt className='text-muted-foreground'>{shippingLabel}</dt>
|
|
257
|
+
<dd className='tabular-nums'>
|
|
258
|
+
{Number(order.totals.shipping.amount) === 0
|
|
259
|
+
? freeLabel
|
|
260
|
+
: format(order.totals.shipping)}
|
|
261
|
+
</dd>
|
|
262
|
+
</div>
|
|
263
|
+
) : null}
|
|
264
|
+
{isAvailableMoney(order.totals.tax) ? (
|
|
265
|
+
<div className='flex justify-between'>
|
|
266
|
+
<dt className='text-muted-foreground'>{taxLabel}</dt>
|
|
267
|
+
<dd className='tabular-nums'>{format(order.totals.tax)}</dd>
|
|
268
|
+
</div>
|
|
269
|
+
) : null}
|
|
270
|
+
{isAvailableMoney(order.totals.total) ? (
|
|
271
|
+
<div className='flex justify-between pt-2 text-base font-semibold'>
|
|
272
|
+
<dt>{totalLabel}</dt>
|
|
273
|
+
<dd className='tabular-nums'>{format(order.totals.total)}</dd>
|
|
274
|
+
</div>
|
|
275
|
+
) : null}
|
|
231
276
|
</dl>
|
|
232
277
|
</CardContent>
|
|
233
278
|
</Card>
|
|
234
279
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
280
|
+
{addresses.length > 0 ? (
|
|
281
|
+
<div className='mt-6 grid gap-6 sm:grid-cols-2'>
|
|
282
|
+
{addresses.map(section => (
|
|
283
|
+
<Card key={section.title}>
|
|
284
|
+
<CardHeader>
|
|
285
|
+
<CardTitle className='text-base'>{section.title}</CardTitle>
|
|
286
|
+
</CardHeader>
|
|
287
|
+
<CardContent className='text-sm'>
|
|
288
|
+
<address className='text-muted-foreground not-italic'>
|
|
289
|
+
<span className='text-foreground block font-medium'>{section.address.name}</span>
|
|
290
|
+
{section.address.lines.map((line, index) => (
|
|
291
|
+
<span key={`${section.title}:${index}:${line}`} className='block'>
|
|
292
|
+
{line}
|
|
293
|
+
</span>
|
|
294
|
+
))}
|
|
295
|
+
</address>
|
|
296
|
+
{section.address.method ? (
|
|
297
|
+
<p className='text-muted-foreground mt-3'>{section.address.method}</p>
|
|
298
|
+
) : null}
|
|
299
|
+
</CardContent>
|
|
300
|
+
</Card>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
) : null}
|
|
258
304
|
|
|
259
305
|
<div className='mt-8 flex flex-col items-center gap-2'>
|
|
260
306
|
{continueShoppingHref ? (
|
|
@@ -280,4 +326,4 @@ export function CheckoutConfirmation({
|
|
|
280
326
|
)
|
|
281
327
|
}
|
|
282
328
|
|
|
283
|
-
export default CheckoutConfirmation
|
|
329
|
+
export default CheckoutConfirmation
|