@ecomiq/storefront 2.0.9 → 2.0.11
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/bin/ecomiq.mjs +1 -1
- package/package.json +2 -2
- package/src/api-proxy.ts +2 -2
- package/src/client.ts +2 -2
- package/src/codegen.mjs +3 -8
- package/src/components/checkout/checkout-contact-section.tsx +70 -0
- package/src/components/checkout/checkout-order-summary.tsx +11 -2
- package/src/components/checkout/checkout-payment-section.tsx +4 -7
- package/src/components/checkout/checkout-session-view.tsx +311 -68
- package/src/components/checkout/checkout-shipping-address-section.tsx +119 -90
- package/src/components/checkout/checkout-shipping-section.tsx +22 -14
- package/src/components/checkout/checkout-step-card.tsx +101 -0
- package/src/components/checkout/checkout-steps.ts +96 -0
- package/src/components/checkout/form-schema.ts +152 -100
- package/src/components/checkout/format.ts +0 -5
- package/src/components/checkout/index.ts +4 -50
- package/src/components/checkout/types.ts +0 -2
- package/src/components/checkout/use-checkout-session.ts +72 -15
- package/src/config.d.ts +6 -12
- package/src/config.mjs +5 -64
- package/src/index.ts +8 -4
- package/src/public-config.ts +10 -30
- package/src/runtime/default-layout.tsx +5 -21
- package/src/runtime/head.ts +70 -47
- package/src/runtime/page.tsx +45 -13
- package/src/server-config-store.ts +1 -1
- package/templates/__root.tsx +1 -1
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import { useState, type FormEvent } from 'react'
|
|
2
|
-
import { ArrowLeft, Lock } from 'lucide-react'
|
|
1
|
+
import { useEffect, useState, type FormEvent } from 'react'
|
|
2
|
+
import { ArrowLeft, CreditCard, Lock, Truck, User } from 'lucide-react'
|
|
3
3
|
import type { CartCheckoutResponse } from '@ecomiq/core'
|
|
4
4
|
import { Link } from '../../link'
|
|
5
5
|
import { useAuth, useSettings } from '../../providers'
|
|
6
6
|
import { buttonVariants } from '../../ui'
|
|
7
|
+
import { CheckoutContactSection } from './checkout-contact-section'
|
|
7
8
|
import { CheckoutExternalUpdateBanner } from './checkout-external-update-banner'
|
|
8
9
|
import { CheckoutOrderBumps } from './checkout-line-items'
|
|
9
10
|
import { CheckoutOrderSummary } from './checkout-order-summary'
|
|
10
11
|
import { CheckoutPaymentSection } from './checkout-payment-section'
|
|
11
12
|
import { CheckoutShippingAddressSection } from './checkout-shipping-address-section'
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CheckoutStepCard,
|
|
15
|
+
CheckoutStepSummaryBlock,
|
|
16
|
+
} from './checkout-step-card'
|
|
17
|
+
import {
|
|
18
|
+
completeCheckoutStep,
|
|
19
|
+
createCheckoutStepFlow,
|
|
20
|
+
editCheckoutStep,
|
|
21
|
+
type CheckoutStepFlow,
|
|
22
|
+
type CheckoutStepId,
|
|
23
|
+
} from './checkout-steps'
|
|
13
24
|
import {
|
|
14
25
|
CheckoutAlreadyPurchasedState,
|
|
15
26
|
CheckoutEmptyState,
|
|
@@ -18,7 +29,11 @@ import {
|
|
|
18
29
|
} from './checkout-states'
|
|
19
30
|
import { formatMoney, paymentRedirectUrl, thankYouUrl } from './format'
|
|
20
31
|
import {
|
|
32
|
+
buildCheckoutContactStepSchema,
|
|
33
|
+
buildCheckoutDeliveryStepSchema,
|
|
21
34
|
buildCheckoutFormValidationSchema,
|
|
35
|
+
buildCheckoutPaymentStepSchema,
|
|
36
|
+
checkoutStepForField,
|
|
22
37
|
checkoutValidationInputFromContext,
|
|
23
38
|
firstInvalidFieldId,
|
|
24
39
|
focusCheckoutField,
|
|
@@ -33,12 +48,16 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
33
48
|
const { store } = useSettings()
|
|
34
49
|
const { session } = useAuth()
|
|
35
50
|
const checkout = useCheckoutSession(cartId)
|
|
51
|
+
const [stepFlow, setStepFlow] = useState<CheckoutStepFlow>(() => createCheckoutStepFlow())
|
|
36
52
|
const [fieldErrors, setFieldErrors] = useState<
|
|
37
53
|
Partial<Record<CheckoutFieldId, string>>
|
|
38
54
|
>({})
|
|
39
55
|
const [receiptFile, setReceiptFile] = useState<File | null>(null)
|
|
40
56
|
const [receiptUrl, setReceiptUrl] = useState<string | null>(null)
|
|
41
57
|
const [receiptError, setReceiptError] = useState<string | null>(null)
|
|
58
|
+
const [acceptedAge, setAcceptedAge] = useState(false)
|
|
59
|
+
const [acceptedPrivacy, setAcceptedPrivacy] = useState(false)
|
|
60
|
+
const [consentError, setConsentError] = useState<string | null>(null)
|
|
42
61
|
|
|
43
62
|
const geo = useCheckoutGeo(
|
|
44
63
|
checkout.contact.countryCode,
|
|
@@ -46,6 +65,13 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
46
65
|
checkout.contact.provinceCode,
|
|
47
66
|
)
|
|
48
67
|
|
|
68
|
+
const { ensureDefaultPaymentMethod } = checkout
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (stepFlow.active !== 'payment') return
|
|
72
|
+
ensureDefaultPaymentMethod()
|
|
73
|
+
}, [ensureDefaultPaymentMethod, stepFlow.active])
|
|
74
|
+
|
|
49
75
|
if (checkout.loadStatus === 'loading' || checkout.isReconciling) {
|
|
50
76
|
return <CheckoutLoadingState />
|
|
51
77
|
}
|
|
@@ -72,6 +98,9 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
72
98
|
const selectedMethod =
|
|
73
99
|
checkout.paymentMethods.find((m) => m.code === checkout.selectedPaymentCode) ?? null
|
|
74
100
|
const isManual = Boolean(selectedMethod?.isManual)
|
|
101
|
+
const selectedShipping = checkout.shippingFlat.find(
|
|
102
|
+
(o) => o.key === checkout.selectedShippingKey,
|
|
103
|
+
)
|
|
75
104
|
|
|
76
105
|
const clearFieldError = (id: CheckoutFieldId) => {
|
|
77
106
|
setFieldErrors((prev) => {
|
|
@@ -86,23 +115,86 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
86
115
|
checkout.setContact((c) => ({ ...c, ...patch }))
|
|
87
116
|
}
|
|
88
117
|
|
|
118
|
+
const openStep = (step: CheckoutStepId) => {
|
|
119
|
+
setStepFlow((flow) => editCheckoutStep(flow, step))
|
|
120
|
+
checkout.invalidateDeliveryPersistence()
|
|
121
|
+
setFieldErrors({})
|
|
122
|
+
setConsentError(null)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const validationContext = () =>
|
|
126
|
+
checkoutValidationInputFromContext({
|
|
127
|
+
contact: checkout.contact,
|
|
128
|
+
selectedShippingKey: checkout.selectedShippingKey,
|
|
129
|
+
shippingOptionsCount: checkout.shippingFlat.length,
|
|
130
|
+
selectedPaymentCode: checkout.selectedPaymentCode,
|
|
131
|
+
paymentMethodsCount: checkout.paymentMethods.length,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const handleContinueContact = () => {
|
|
135
|
+
const parsed = buildCheckoutContactStepSchema().safeParse({
|
|
136
|
+
email: checkout.contact.email,
|
|
137
|
+
})
|
|
138
|
+
if (!parsed.success) {
|
|
139
|
+
const errors = mapZodIssuesToFieldErrors(parsed.error)
|
|
140
|
+
setFieldErrors(errors)
|
|
141
|
+
const first = firstInvalidFieldId(errors)
|
|
142
|
+
if (first) focusCheckoutField(first)
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
setFieldErrors({})
|
|
146
|
+
setStepFlow((flow) => completeCheckoutStep(flow, 'contact'))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const handleContinueDelivery = async () => {
|
|
150
|
+
const parsed = buildCheckoutDeliveryStepSchema(store.currency).safeParse(validationContext())
|
|
151
|
+
if (!parsed.success) {
|
|
152
|
+
const errors = mapZodIssuesToFieldErrors(parsed.error)
|
|
153
|
+
setFieldErrors(errors)
|
|
154
|
+
const first = firstInvalidFieldId(errors)
|
|
155
|
+
if (first) focusCheckoutField(first)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
setFieldErrors({})
|
|
159
|
+
try {
|
|
160
|
+
await checkout.persistDeliveryStep()
|
|
161
|
+
setStepFlow((flow) => completeCheckoutStep(flow, 'delivery'))
|
|
162
|
+
} catch {
|
|
163
|
+
/* formError en sesión */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
89
167
|
const handlePay = async (event: FormEvent) => {
|
|
90
168
|
event.preventDefault()
|
|
169
|
+
if (stepFlow.active !== 'payment') return
|
|
170
|
+
|
|
171
|
+
if (!acceptedAge || !acceptedPrivacy) {
|
|
172
|
+
setConsentError('Debes confirmar edad y política de privacidad.')
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
setConsentError(null)
|
|
176
|
+
|
|
177
|
+
const paymentParsed = buildCheckoutPaymentStepSchema().safeParse(validationContext())
|
|
178
|
+
if (!paymentParsed.success) {
|
|
179
|
+
const errors = mapZodIssuesToFieldErrors(paymentParsed.error)
|
|
180
|
+
setFieldErrors(errors)
|
|
181
|
+
const first = firstInvalidFieldId(errors)
|
|
182
|
+
if (first) focusCheckoutField(first)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
91
186
|
const schema = buildCheckoutFormValidationSchema(store.currency)
|
|
92
|
-
const parsed = schema.safeParse(
|
|
93
|
-
checkoutValidationInputFromContext({
|
|
94
|
-
contact: checkout.contact,
|
|
95
|
-
selectedShippingKey: checkout.selectedShippingKey,
|
|
96
|
-
shippingOptionsCount: checkout.shippingFlat.length,
|
|
97
|
-
selectedPaymentCode: checkout.selectedPaymentCode,
|
|
98
|
-
paymentMethodsCount: checkout.paymentMethods.length,
|
|
99
|
-
}),
|
|
100
|
-
)
|
|
187
|
+
const parsed = schema.safeParse(validationContext())
|
|
101
188
|
if (!parsed.success) {
|
|
102
189
|
const errors = mapZodIssuesToFieldErrors(parsed.error)
|
|
103
190
|
setFieldErrors(errors)
|
|
104
191
|
const first = firstInvalidFieldId(errors)
|
|
105
|
-
if (first)
|
|
192
|
+
if (first) {
|
|
193
|
+
const step = checkoutStepForField(first)
|
|
194
|
+
setStepFlow((flow) => editCheckoutStep(flow, step))
|
|
195
|
+
if (step !== 'payment') checkout.invalidateDeliveryPersistence()
|
|
196
|
+
focusCheckoutField(first)
|
|
197
|
+
}
|
|
106
198
|
return
|
|
107
199
|
}
|
|
108
200
|
setFieldErrors({})
|
|
@@ -110,9 +202,19 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
110
202
|
try {
|
|
111
203
|
let uploadedReceipt = receiptUrl ?? undefined
|
|
112
204
|
if (isManual && receiptFile && !uploadedReceipt) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
205
|
+
try {
|
|
206
|
+
const uploaded = await checkout.uploadPaymentReceipt(receiptFile)
|
|
207
|
+
uploadedReceipt = uploaded.url
|
|
208
|
+
setReceiptUrl(uploaded.url)
|
|
209
|
+
setReceiptError(null)
|
|
210
|
+
} catch (error) {
|
|
211
|
+
setReceiptError(
|
|
212
|
+
error instanceof Error
|
|
213
|
+
? error.message
|
|
214
|
+
: 'No se pudo subir el comprobante de pago.',
|
|
215
|
+
)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
116
218
|
}
|
|
117
219
|
|
|
118
220
|
const result: CartCheckoutResponse | undefined = await checkout.payNow(
|
|
@@ -157,58 +259,147 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
157
259
|
onSubmit={(e) => void handlePay(e)}
|
|
158
260
|
className="grid items-start gap-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:gap-10"
|
|
159
261
|
>
|
|
160
|
-
<div className="space-y-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
262
|
+
<div className="space-y-4">
|
|
263
|
+
<CheckoutStepCard
|
|
264
|
+
step="contact"
|
|
265
|
+
flow={stepFlow}
|
|
266
|
+
title="Mis datos"
|
|
267
|
+
icon={User}
|
|
268
|
+
onEdit={() => openStep('contact')}
|
|
269
|
+
summary={
|
|
270
|
+
<CheckoutStepSummaryBlock>
|
|
271
|
+
<p className="text-sm">{checkout.contact.email}</p>
|
|
272
|
+
</CheckoutStepSummaryBlock>
|
|
273
|
+
}
|
|
274
|
+
>
|
|
275
|
+
<CheckoutContactSection
|
|
276
|
+
email={checkout.contact.email}
|
|
277
|
+
fieldError={fieldErrors.email}
|
|
278
|
+
sessionLabel={session?.user?.email ?? session?.user?.name}
|
|
279
|
+
isSaving={checkout.isSaving}
|
|
280
|
+
onEmailChange={(email) => patchContact({ email })}
|
|
281
|
+
onClearError={clearFieldError}
|
|
282
|
+
onContinueAsGuest={handleContinueContact}
|
|
283
|
+
/>
|
|
284
|
+
</CheckoutStepCard>
|
|
285
|
+
|
|
286
|
+
<CheckoutStepCard
|
|
287
|
+
step="delivery"
|
|
288
|
+
flow={stepFlow}
|
|
289
|
+
title="Dirección de entrega"
|
|
290
|
+
icon={Truck}
|
|
291
|
+
onEdit={() => openStep('delivery')}
|
|
292
|
+
summary={
|
|
293
|
+
<DeliveryStepSummary
|
|
294
|
+
contact={checkout.contact}
|
|
295
|
+
shippingLabel={
|
|
296
|
+
selectedShipping
|
|
297
|
+
? `${selectedShipping.rate.name}${
|
|
298
|
+
selectedShipping.rate.description
|
|
299
|
+
? ` (${selectedShipping.rate.description})`
|
|
300
|
+
: ''
|
|
301
|
+
}`
|
|
302
|
+
: null
|
|
303
|
+
}
|
|
304
|
+
shippingPrice={
|
|
305
|
+
selectedShipping
|
|
306
|
+
? selectedShipping.rate.price === 0
|
|
307
|
+
? 'GRATIS'
|
|
308
|
+
: money(selectedShipping.rate.price)
|
|
309
|
+
: null
|
|
310
|
+
}
|
|
311
|
+
/>
|
|
312
|
+
}
|
|
313
|
+
>
|
|
314
|
+
<CheckoutShippingAddressSection
|
|
315
|
+
contact={checkout.contact}
|
|
316
|
+
fieldErrors={fieldErrors}
|
|
317
|
+
countries={geo.countries}
|
|
318
|
+
departments={geo.departments}
|
|
319
|
+
provinces={geo.provinces}
|
|
320
|
+
districts={geo.districts}
|
|
321
|
+
shippingOptions={checkout.shippingFlat}
|
|
322
|
+
selectedShippingKey={checkout.selectedShippingKey}
|
|
323
|
+
shippingLoading={checkout.shippingLoading}
|
|
324
|
+
shippingError={checkout.shippingError}
|
|
325
|
+
isSaving={checkout.isSaving}
|
|
326
|
+
money={money}
|
|
327
|
+
onPatch={patchContact}
|
|
328
|
+
onSelectShipping={checkout.setSelectedShippingKey}
|
|
329
|
+
onClearError={clearFieldError}
|
|
330
|
+
onContinue={handleContinueDelivery}
|
|
331
|
+
/>
|
|
332
|
+
</CheckoutStepCard>
|
|
333
|
+
|
|
334
|
+
<CheckoutStepCard
|
|
335
|
+
step="payment"
|
|
336
|
+
flow={stepFlow}
|
|
337
|
+
title="Pago"
|
|
338
|
+
icon={CreditCard}
|
|
339
|
+
>
|
|
340
|
+
<div className="space-y-5">
|
|
341
|
+
<div className="space-y-3">
|
|
342
|
+
<label className="flex cursor-pointer items-start gap-2.5 text-sm leading-5">
|
|
343
|
+
<input
|
|
344
|
+
type="checkbox"
|
|
345
|
+
className="mt-0.5 size-4 shrink-0 rounded border-border accent-foreground"
|
|
346
|
+
checked={acceptedAge}
|
|
347
|
+
onChange={(e) => {
|
|
348
|
+
setAcceptedAge(e.target.checked)
|
|
349
|
+
setConsentError(null)
|
|
350
|
+
}}
|
|
351
|
+
/>
|
|
352
|
+
<span>Tengo 18 años o más</span>
|
|
353
|
+
</label>
|
|
354
|
+
<label className="flex cursor-pointer items-start gap-2.5 text-sm leading-5">
|
|
355
|
+
<input
|
|
356
|
+
type="checkbox"
|
|
357
|
+
className="mt-0.5 size-4 shrink-0 rounded border-border accent-foreground"
|
|
358
|
+
checked={acceptedPrivacy}
|
|
359
|
+
onChange={(e) => {
|
|
360
|
+
setAcceptedPrivacy(e.target.checked)
|
|
361
|
+
setConsentError(null)
|
|
362
|
+
}}
|
|
363
|
+
/>
|
|
364
|
+
<span>
|
|
365
|
+
Doy mi consentimiento para que mis datos personales sean tratados conforme a
|
|
366
|
+
la política de privacidad
|
|
367
|
+
</span>
|
|
368
|
+
</label>
|
|
369
|
+
{consentError ? (
|
|
370
|
+
<p className="text-xs text-destructive">{consentError}</p>
|
|
371
|
+
) : null}
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<CheckoutPaymentSection
|
|
375
|
+
methods={checkout.paymentMethods}
|
|
376
|
+
selectedCode={checkout.selectedPaymentCode}
|
|
377
|
+
fieldError={fieldErrors['checkout-payment-method']}
|
|
378
|
+
checkoutPaymentLoading={checkout.checkoutPaymentLoading}
|
|
379
|
+
checkoutPaymentError={checkout.checkoutPaymentError}
|
|
380
|
+
checkoutPaymentResponse={checkout.checkoutPaymentResponse}
|
|
381
|
+
checkoutPaymentMethodCode={checkout.checkoutPaymentMethodCode}
|
|
382
|
+
providerPaymentCompleted={checkout.providerPaymentCompleted}
|
|
383
|
+
receiptUrl={receiptUrl}
|
|
384
|
+
receiptError={receiptError}
|
|
385
|
+
onSelect={checkout.selectPaymentMethod}
|
|
386
|
+
onClearError={clearFieldError}
|
|
387
|
+
onMarkProviderCompleted={() => checkout.setProviderPaymentCompleted(true)}
|
|
388
|
+
onReceiptFileChange={(file) => {
|
|
389
|
+
setReceiptFile(file)
|
|
390
|
+
setReceiptUrl(null)
|
|
391
|
+
setReceiptError(null)
|
|
392
|
+
}}
|
|
393
|
+
/>
|
|
394
|
+
|
|
395
|
+
<CheckoutOrderBumps
|
|
396
|
+
bumps={checkout.orderBumps}
|
|
397
|
+
money={money}
|
|
398
|
+
busy={checkout.isSaving}
|
|
399
|
+
onAdd={(bump) => void checkout.handleAddOrderBump(bump)}
|
|
400
|
+
/>
|
|
401
|
+
</div>
|
|
402
|
+
</CheckoutStepCard>
|
|
212
403
|
|
|
213
404
|
<p className="flex items-start gap-2 text-xs leading-5 text-muted-foreground">
|
|
214
405
|
<Lock className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
|
|
@@ -230,6 +421,12 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
230
421
|
promotionCodes={cart.promotionCodes}
|
|
231
422
|
formError={checkout.formError}
|
|
232
423
|
isSaving={checkout.isSaving}
|
|
424
|
+
canSubmit={stepFlow.active === 'payment'}
|
|
425
|
+
submitLabel={
|
|
426
|
+
selectedMethod
|
|
427
|
+
? `Pagar con ${selectedMethod.name}`
|
|
428
|
+
: 'Pagar'
|
|
429
|
+
}
|
|
233
430
|
onCouponCodeChange={checkout.setCouponCode}
|
|
234
431
|
onApplyCoupon={() => void checkout.handleApplyCoupon()}
|
|
235
432
|
onRemoveCoupon={(code) => void checkout.handleRemoveCoupon(code)}
|
|
@@ -238,3 +435,49 @@ export function CheckoutSessionView({ cartId }: { cartId: string }) {
|
|
|
238
435
|
</div>
|
|
239
436
|
)
|
|
240
437
|
}
|
|
438
|
+
|
|
439
|
+
function DeliveryStepSummary({
|
|
440
|
+
contact,
|
|
441
|
+
shippingLabel,
|
|
442
|
+
shippingPrice,
|
|
443
|
+
}: {
|
|
444
|
+
contact: CheckoutContactForm
|
|
445
|
+
shippingLabel: string | null
|
|
446
|
+
shippingPrice: string | null
|
|
447
|
+
}) {
|
|
448
|
+
const fullName = `${contact.firstName} ${contact.lastName}`.trim()
|
|
449
|
+
const locality = [contact.districtName || contact.city, contact.provinceName || contact.province]
|
|
450
|
+
.filter(Boolean)
|
|
451
|
+
.join(', ')
|
|
452
|
+
const addressLine = [contact.address, contact.address2, locality, contact.postalCode]
|
|
453
|
+
.filter(Boolean)
|
|
454
|
+
.join(', ')
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<div className="space-y-3">
|
|
458
|
+
<CheckoutStepSummaryBlock>
|
|
459
|
+
<p className="text-sm font-medium">{fullName || 'Destinatario'}</p>
|
|
460
|
+
<p className="mt-1 text-sm text-muted-foreground">{addressLine}</p>
|
|
461
|
+
</CheckoutStepSummaryBlock>
|
|
462
|
+
{shippingLabel ? (
|
|
463
|
+
<CheckoutStepSummaryBlock>
|
|
464
|
+
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
465
|
+
Envío
|
|
466
|
+
</p>
|
|
467
|
+
<div className="mt-1 flex items-start justify-between gap-3">
|
|
468
|
+
<p className="text-sm">{shippingLabel}</p>
|
|
469
|
+
{shippingPrice ? (
|
|
470
|
+
<p className="shrink-0 text-sm font-semibold tabular-nums">{shippingPrice}</p>
|
|
471
|
+
) : null}
|
|
472
|
+
</div>
|
|
473
|
+
</CheckoutStepSummaryBlock>
|
|
474
|
+
) : null}
|
|
475
|
+
<CheckoutStepSummaryBlock>
|
|
476
|
+
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
477
|
+
Dirección de facturación
|
|
478
|
+
</p>
|
|
479
|
+
<p className="mt-1 text-sm">Misma dirección que la de entrega</p>
|
|
480
|
+
</CheckoutStepSummaryBlock>
|
|
481
|
+
</div>
|
|
482
|
+
)
|
|
483
|
+
}
|