@thorprovider/blocks 1.0.0 → 2.0.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/CHANGELOG.md +48 -0
- package/README.md +156 -40
- package/package.json +1 -1
- package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
- package/src/blocks/application/login-forms/login-page.tsx +240 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
- package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
- package/src/blocks/application/verification/verify-1.tsx +2 -2
- package/src/blocks/application/viewport-ownership.test.tsx +41 -0
- package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
- package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
- package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
- package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
- package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
- package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
- package/src/blocks/{marketing/navbars/navbar-4-account-menu.test.tsx → e-commerce/navigation/storefront-navbar-account-menu.test.tsx} +8 -8
- package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
- package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +40 -40
- package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +17 -17
- package/src/blocks/e-commerce/product-list/product-card.tsx +63 -40
- package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +2 -2
- package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
- package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
- package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
- package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
- package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
- package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
- package/src/blocks/marketing/footers/footer-section.tsx +532 -0
- package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
- package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
- package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
- package/src/blocks/marketing/navbars/navbar.tsx +735 -0
- package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
- package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
- package/src/index.ts +80 -81
- package/src/lib/contracts-curated.test.ts +5 -5
- package/src/lib/contracts.test.ts +55 -0
- package/src/lib/contracts.ts +64 -2
- package/tsup.config.ts +1 -1
- package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
- package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
- package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
- package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
- package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
- package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
- package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
- package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
- package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react'
|
|
4
|
-
import { Lock } from 'lucide-react'
|
|
5
|
-
import { Button } from '../../../ui/button'
|
|
6
|
-
import { Card, CardContent } from '../../../ui/card'
|
|
7
|
-
import { cn } from '../../../lib/utils'
|
|
8
|
-
import { CheckoutForm1ContactStep } from './checkout-form-1-contact-step'
|
|
9
|
-
import { CheckoutForm1ShippingStep } from './checkout-form-1-shipping-step'
|
|
10
|
-
import { CheckoutForm1PaymentStep } from './checkout-form-1-payment-step'
|
|
11
|
-
import { CheckoutForm1OrderSummary } from './checkout-form-1-order-summary'
|
|
12
|
-
import { CheckoutForm1Progress } from './checkout-form-1-progress'
|
|
13
|
-
import { formatCardNumber, formatExpiryDate } from './card-formatting'
|
|
14
|
-
import type { Money, PaymentMethod } from '@thorprovider/types'
|
|
15
|
-
import type { CheckoutFieldErrors } from './checkout-form-types'
|
|
16
|
-
|
|
17
|
-
export interface CheckoutForm1FormData {
|
|
18
|
-
email: string
|
|
19
|
-
firstName: string
|
|
20
|
-
lastName: string
|
|
21
|
-
phone: string
|
|
22
|
-
address: string
|
|
23
|
-
city: string
|
|
24
|
-
state: string
|
|
25
|
-
zipCode: string
|
|
26
|
-
country: string
|
|
27
|
-
paymentMethod: string
|
|
28
|
-
cardNumber: string
|
|
29
|
-
expiryDate: string
|
|
30
|
-
cvv: string
|
|
31
|
-
cardName: string
|
|
32
|
-
saveInfo: boolean
|
|
33
|
-
sameAsBilling: boolean
|
|
34
|
-
newsletter: boolean
|
|
35
|
-
promoCode: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type CheckoutForm1FieldErrors = CheckoutFieldErrors<keyof CheckoutForm1FormData>
|
|
39
|
-
|
|
40
|
-
export type CheckoutForm1FieldDescriptions = Partial<
|
|
41
|
-
Record<keyof CheckoutForm1FormData, string>
|
|
42
|
-
>
|
|
43
|
-
|
|
44
|
-
export interface CheckoutForm1OrderSummaryItem {
|
|
45
|
-
id: string | number
|
|
46
|
-
name: string
|
|
47
|
-
variant?: string
|
|
48
|
-
quantity: number
|
|
49
|
-
/** Canonical backend line total; preferred over `price * quantity`. */
|
|
50
|
-
lineTotal?: Money
|
|
51
|
-
/** @deprecated legacy numeric price, used only when `lineTotal` is absent. */
|
|
52
|
-
price?: number
|
|
53
|
-
/** Optional; an absent image is omitted rather than serialized as `""`. */
|
|
54
|
-
image?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface CheckoutForm1Totals {
|
|
58
|
-
subtotal: Money
|
|
59
|
-
discount?: Money
|
|
60
|
-
tax?: Money
|
|
61
|
-
shipping?: Money
|
|
62
|
-
total: Money
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type CheckoutForm1TotalsState = 'idle' | 'pending' | 'error'
|
|
66
|
-
|
|
67
|
-
export interface CheckoutForm1OrderSummary {
|
|
68
|
-
items: CheckoutForm1OrderSummaryItem[]
|
|
69
|
-
/** Backend-authoritative totals; rendered verbatim when present. */
|
|
70
|
-
totals?: CheckoutForm1Totals
|
|
71
|
-
/** @deprecated legacy numeric amounts, used only when `totals` is absent. */
|
|
72
|
-
shipping?: number
|
|
73
|
-
/** @deprecated */
|
|
74
|
-
tax?: number
|
|
75
|
-
/** @deprecated */
|
|
76
|
-
discount?: number
|
|
77
|
-
/** @deprecated */
|
|
78
|
-
promoDiscount?: number
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface CheckoutForm1CountryOption {
|
|
82
|
-
value: string
|
|
83
|
-
label: string
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface CheckoutForm1ShippingOption {
|
|
87
|
-
/** Backend shipping method id — the controlled selection identity. */
|
|
88
|
-
id: string
|
|
89
|
-
name: string
|
|
90
|
-
price?: Money
|
|
91
|
-
description?: string
|
|
92
|
-
estimatedDays?: string
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export type CheckoutForm1ChangeHandler = (
|
|
96
|
-
field: keyof CheckoutForm1FormData,
|
|
97
|
-
value: string | boolean
|
|
98
|
-
) => void
|
|
99
|
-
|
|
100
|
-
const STEP_COPY = [
|
|
101
|
-
{ title: 'Información de contacto', description: 'La usaremos para enviarte novedades de tu pedido.' },
|
|
102
|
-
{ title: 'Dirección de envío', description: 'Enviaremos tu pedido a esta dirección.' },
|
|
103
|
-
{ title: 'Datos de pago', description: 'Pago seguro para tu pedido.' },
|
|
104
|
-
] as const
|
|
105
|
-
|
|
106
|
-
export interface CheckoutForm1Props {
|
|
107
|
-
title?: string
|
|
108
|
-
description?: string
|
|
109
|
-
orderSummary?: CheckoutForm1OrderSummary
|
|
110
|
-
/** Controlled state of the totals fetch/mutation. */
|
|
111
|
-
totalsState?: CheckoutForm1TotalsState
|
|
112
|
-
totalsErrorLabel?: string
|
|
113
|
-
retryLabel?: string
|
|
114
|
-
onRetryTotals?: () => void
|
|
115
|
-
countries?: CheckoutForm1CountryOption[]
|
|
116
|
-
/** Eligible shipping options for the cart's region/address. */
|
|
117
|
-
shippingOptions?: CheckoutForm1ShippingOption[]
|
|
118
|
-
/** Controlled selection of a shipping option id. */
|
|
119
|
-
selectedShippingOptionId?: string
|
|
120
|
-
onSelectShippingOption?: (id: string) => void
|
|
121
|
-
paymentMethods?: PaymentMethod[]
|
|
122
|
-
noPaymentMethodsLabel?: string
|
|
123
|
-
/** Override money formatting (locale, currency). Defaults to `Money.currencyCode`. */
|
|
124
|
-
formatMoney?: (money: Money) => string
|
|
125
|
-
/**
|
|
126
|
-
* Raw card fields are legacy-only and never collected in the launch flow.
|
|
127
|
-
* Defaults to `false`: the curated surface renders no card inputs.
|
|
128
|
-
*/
|
|
129
|
-
collectCardFields?: boolean
|
|
130
|
-
onApplyPromo?: (code: string) => void
|
|
131
|
-
promoPending?: boolean
|
|
132
|
-
promoApplyLabel?: string
|
|
133
|
-
promoPlaceholder?: string
|
|
134
|
-
/** Optional trust copy; rendered only when supplied (never fabricated). */
|
|
135
|
-
trustNotes?: string[]
|
|
136
|
-
backLabel?: string
|
|
137
|
-
completeOrderLabel?: string
|
|
138
|
-
processingLabel?: string
|
|
139
|
-
continueLabel?: string
|
|
140
|
-
values: CheckoutForm1FormData
|
|
141
|
-
onChange: CheckoutForm1ChangeHandler
|
|
142
|
-
fieldErrors?: CheckoutForm1FieldErrors
|
|
143
|
-
fieldDescriptions?: CheckoutForm1FieldDescriptions
|
|
144
|
-
validateStep?: (step: number) => boolean
|
|
145
|
-
onSubmit?: (data: CheckoutForm1FormData) => void
|
|
146
|
-
/** Disables the submit button while the order is being placed. */
|
|
147
|
-
submitting?: boolean
|
|
148
|
-
/** Order-level error surfaced above the footer actions. */
|
|
149
|
-
submitError?: string
|
|
150
|
-
/** Optional recovery action for a failed submission. */
|
|
151
|
-
onRetrySubmit?: () => void
|
|
152
|
-
submitErrorActionLabel?: string
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export default function CheckoutForm1({
|
|
156
|
-
title,
|
|
157
|
-
description,
|
|
158
|
-
orderSummary,
|
|
159
|
-
totalsState = 'idle',
|
|
160
|
-
totalsErrorLabel,
|
|
161
|
-
retryLabel,
|
|
162
|
-
onRetryTotals,
|
|
163
|
-
countries,
|
|
164
|
-
shippingOptions,
|
|
165
|
-
selectedShippingOptionId,
|
|
166
|
-
onSelectShippingOption,
|
|
167
|
-
paymentMethods,
|
|
168
|
-
noPaymentMethodsLabel,
|
|
169
|
-
formatMoney,
|
|
170
|
-
collectCardFields = false,
|
|
171
|
-
onApplyPromo,
|
|
172
|
-
promoPending = false,
|
|
173
|
-
promoApplyLabel,
|
|
174
|
-
promoPlaceholder,
|
|
175
|
-
trustNotes,
|
|
176
|
-
backLabel = 'Atrás',
|
|
177
|
-
completeOrderLabel = 'Completar pedido',
|
|
178
|
-
processingLabel = 'Procesando pedido…',
|
|
179
|
-
continueLabel = 'Continuar',
|
|
180
|
-
values,
|
|
181
|
-
onChange,
|
|
182
|
-
fieldErrors,
|
|
183
|
-
fieldDescriptions,
|
|
184
|
-
validateStep,
|
|
185
|
-
onSubmit,
|
|
186
|
-
submitting = false,
|
|
187
|
-
submitError,
|
|
188
|
-
onRetrySubmit,
|
|
189
|
-
submitErrorActionLabel = 'Reintentar',
|
|
190
|
-
}: CheckoutForm1Props) {
|
|
191
|
-
const [step, setStep] = useState(1)
|
|
192
|
-
|
|
193
|
-
const handleCardNumberChange = (value: string) => {
|
|
194
|
-
onChange('cardNumber', formatCardNumber(value))
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const handleExpiryChange = (value: string) => {
|
|
198
|
-
onChange('expiryDate', formatExpiryDate(value))
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const handleContinue = () => {
|
|
202
|
-
if (validateStep && !validateStep(step)) return
|
|
203
|
-
setStep((s) => Math.min(STEP_COPY.length, s + 1))
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const handleSubmit = () => {
|
|
207
|
-
if (validateStep) {
|
|
208
|
-
const allValid = Array.from({ length: STEP_COPY.length }, (_, index) => index + 1).every(
|
|
209
|
-
(currentStep) => validateStep(currentStep)
|
|
210
|
-
)
|
|
211
|
-
if (!allValid) return
|
|
212
|
-
}
|
|
213
|
-
onSubmit?.(values)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const stepCount = STEP_COPY.length
|
|
217
|
-
const isLastStep = step === stepCount
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6 lg:px-8">
|
|
221
|
-
{(title || description) && (
|
|
222
|
-
<header className="mb-8 text-center">
|
|
223
|
-
{title && <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">{title}</h1>}
|
|
224
|
-
{description && <p className="mt-2 text-muted-foreground">{description}</p>}
|
|
225
|
-
</header>
|
|
226
|
-
)}
|
|
227
|
-
|
|
228
|
-
<CheckoutForm1Progress step={step} />
|
|
229
|
-
|
|
230
|
-
<div className="mt-8 grid gap-6 lg:grid-cols-3">
|
|
231
|
-
<Card className={cn('lg:col-span-2', !orderSummary && 'lg:col-span-3')}>
|
|
232
|
-
<CardContent className="p-6">
|
|
233
|
-
<div className="mb-6">
|
|
234
|
-
<h2 className="text-lg font-semibold">{STEP_COPY[step - 1].title}</h2>
|
|
235
|
-
<p className="text-sm text-muted-foreground">{STEP_COPY[step - 1].description}</p>
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
{step === 1 && (
|
|
239
|
-
<CheckoutForm1ContactStep
|
|
240
|
-
formData={values}
|
|
241
|
-
onChange={onChange}
|
|
242
|
-
fieldErrors={fieldErrors}
|
|
243
|
-
fieldDescriptions={fieldDescriptions}
|
|
244
|
-
/>
|
|
245
|
-
)}
|
|
246
|
-
{step === 2 && (
|
|
247
|
-
<CheckoutForm1ShippingStep
|
|
248
|
-
formData={values}
|
|
249
|
-
onChange={onChange}
|
|
250
|
-
countries={countries}
|
|
251
|
-
shippingOptions={shippingOptions}
|
|
252
|
-
selectedShippingOptionId={selectedShippingOptionId}
|
|
253
|
-
onSelectShippingOption={onSelectShippingOption}
|
|
254
|
-
formatMoney={formatMoney}
|
|
255
|
-
fieldErrors={fieldErrors}
|
|
256
|
-
fieldDescriptions={fieldDescriptions}
|
|
257
|
-
/>
|
|
258
|
-
)}
|
|
259
|
-
{step === 3 && (
|
|
260
|
-
<CheckoutForm1PaymentStep
|
|
261
|
-
formData={values}
|
|
262
|
-
onChange={onChange}
|
|
263
|
-
onCardNumberChange={handleCardNumberChange}
|
|
264
|
-
onExpiryChange={handleExpiryChange}
|
|
265
|
-
paymentMethods={paymentMethods}
|
|
266
|
-
noPaymentMethodsLabel={noPaymentMethodsLabel}
|
|
267
|
-
collectCardFields={collectCardFields}
|
|
268
|
-
fieldErrors={fieldErrors}
|
|
269
|
-
fieldDescriptions={fieldDescriptions}
|
|
270
|
-
/>
|
|
271
|
-
)}
|
|
272
|
-
|
|
273
|
-
{submitError && (
|
|
274
|
-
<div
|
|
275
|
-
role="alert"
|
|
276
|
-
className="mt-6 flex flex-wrap items-center justify-between gap-3 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive"
|
|
277
|
-
>
|
|
278
|
-
<span>{submitError}</span>
|
|
279
|
-
{onRetrySubmit ? (
|
|
280
|
-
<Button
|
|
281
|
-
type="button"
|
|
282
|
-
variant="outline"
|
|
283
|
-
size="sm"
|
|
284
|
-
onClick={onRetrySubmit}
|
|
285
|
-
disabled={submitting}
|
|
286
|
-
>
|
|
287
|
-
{submitErrorActionLabel}
|
|
288
|
-
</Button>
|
|
289
|
-
) : null}
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
|
|
293
|
-
<div className="mt-6 flex items-center justify-between gap-4">
|
|
294
|
-
<Button
|
|
295
|
-
type="button"
|
|
296
|
-
variant="outline"
|
|
297
|
-
onClick={() => setStep((s) => Math.max(1, s - 1))}
|
|
298
|
-
disabled={step === 1 || submitting}
|
|
299
|
-
>
|
|
300
|
-
{backLabel}
|
|
301
|
-
</Button>
|
|
302
|
-
{isLastStep ? (
|
|
303
|
-
<Button type="button" onClick={handleSubmit} disabled={submitting}>
|
|
304
|
-
<Lock className="size-4" />
|
|
305
|
-
{submitting ? processingLabel : completeOrderLabel}
|
|
306
|
-
</Button>
|
|
307
|
-
) : (
|
|
308
|
-
<Button type="button" onClick={handleContinue} disabled={submitting}>
|
|
309
|
-
{continueLabel}
|
|
310
|
-
</Button>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
</CardContent>
|
|
314
|
-
</Card>
|
|
315
|
-
|
|
316
|
-
{orderSummary && (
|
|
317
|
-
<div className="lg:col-span-1">
|
|
318
|
-
<CheckoutForm1OrderSummary
|
|
319
|
-
orderSummary={orderSummary}
|
|
320
|
-
totalsState={totalsState}
|
|
321
|
-
totalsErrorLabel={totalsErrorLabel}
|
|
322
|
-
retryLabel={retryLabel}
|
|
323
|
-
onRetryTotals={onRetryTotals}
|
|
324
|
-
promoCode={values.promoCode}
|
|
325
|
-
onPromoCodeChange={(value) => onChange('promoCode', value)}
|
|
326
|
-
onApplyPromo={onApplyPromo}
|
|
327
|
-
promoPending={promoPending}
|
|
328
|
-
promoApplyLabel={promoApplyLabel}
|
|
329
|
-
promoPlaceholder={promoPlaceholder}
|
|
330
|
-
trustNotes={trustNotes}
|
|
331
|
-
formatMoney={formatMoney}
|
|
332
|
-
/>
|
|
333
|
-
</div>
|
|
334
|
-
)}
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
)
|
|
338
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { Truck } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
import { Badge } from '../../../ui/badge'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
5
|
-
import { Separator } from '../../../ui/separator'
|
|
6
|
-
import type { CheckoutForm2Item } from './checkout-form-2'
|
|
7
|
-
|
|
8
|
-
interface CheckoutForm2OrderSummaryProps {
|
|
9
|
-
items: CheckoutForm2Item[]
|
|
10
|
-
subtotal: number
|
|
11
|
-
shipping: number
|
|
12
|
-
tax: number
|
|
13
|
-
total: number
|
|
14
|
-
currencySymbol: string
|
|
15
|
-
orderSummaryLabel: string
|
|
16
|
-
subtotalLabel: string
|
|
17
|
-
shippingLabel: string
|
|
18
|
-
freeShippingLabel: string
|
|
19
|
-
taxLabel: string
|
|
20
|
-
totalLabel: string
|
|
21
|
-
freeShippingNoteLabel: string
|
|
22
|
-
estimatedDeliveryLabel: string
|
|
23
|
-
freeShippingThreshold: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function CheckoutForm2OrderSummary({
|
|
27
|
-
items,
|
|
28
|
-
subtotal,
|
|
29
|
-
shipping,
|
|
30
|
-
tax,
|
|
31
|
-
total,
|
|
32
|
-
currencySymbol,
|
|
33
|
-
orderSummaryLabel,
|
|
34
|
-
subtotalLabel,
|
|
35
|
-
shippingLabel,
|
|
36
|
-
freeShippingLabel,
|
|
37
|
-
taxLabel,
|
|
38
|
-
totalLabel,
|
|
39
|
-
freeShippingNoteLabel,
|
|
40
|
-
estimatedDeliveryLabel,
|
|
41
|
-
freeShippingThreshold,
|
|
42
|
-
}: CheckoutForm2OrderSummaryProps) {
|
|
43
|
-
return (
|
|
44
|
-
<Card className='sticky top-6'>
|
|
45
|
-
<CardHeader>
|
|
46
|
-
<CardTitle>{orderSummaryLabel}</CardTitle>
|
|
47
|
-
</CardHeader>
|
|
48
|
-
<CardContent className='flex flex-col gap-4'>
|
|
49
|
-
<div className='flex flex-col gap-3'>
|
|
50
|
-
{items.map(item => (
|
|
51
|
-
<div key={item.id} className='flex items-center gap-3'>
|
|
52
|
-
<div className='relative'>
|
|
53
|
-
<img
|
|
54
|
-
src={item.image || undefined}
|
|
55
|
-
alt={item.name}
|
|
56
|
-
width={48}
|
|
57
|
-
height={48}
|
|
58
|
-
className='size-12 rounded-md object-cover'
|
|
59
|
-
/>
|
|
60
|
-
<Badge
|
|
61
|
-
variant='secondary'
|
|
62
|
-
className='absolute -top-2 -end-2 size-5 rounded-full p-0 text-xs font-semibold'
|
|
63
|
-
>
|
|
64
|
-
{item.quantity}
|
|
65
|
-
</Badge>
|
|
66
|
-
</div>
|
|
67
|
-
<div className='flex-1'>
|
|
68
|
-
<h4 className='text-sm font-medium'>{item.name}</h4>
|
|
69
|
-
<p className='text-muted-foreground text-sm'>
|
|
70
|
-
{currencySymbol}
|
|
71
|
-
{item.price} × {item.quantity}
|
|
72
|
-
</p>
|
|
73
|
-
</div>
|
|
74
|
-
<div className='text-sm font-medium'>
|
|
75
|
-
{currencySymbol}
|
|
76
|
-
{(item.price * item.quantity).toFixed(2)}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<Separator />
|
|
83
|
-
|
|
84
|
-
<div className='flex flex-col gap-2 text-sm'>
|
|
85
|
-
<div className='flex justify-between'>
|
|
86
|
-
<span>{subtotalLabel}</span>
|
|
87
|
-
<span>
|
|
88
|
-
{currencySymbol}
|
|
89
|
-
{subtotal.toFixed(2)}
|
|
90
|
-
</span>
|
|
91
|
-
</div>
|
|
92
|
-
<div className='flex justify-between'>
|
|
93
|
-
<span>{shippingLabel}</span>
|
|
94
|
-
<span>{shipping === 0 ? freeShippingLabel : `${currencySymbol}${shipping.toFixed(2)}`}</span>
|
|
95
|
-
</div>
|
|
96
|
-
<div className='flex justify-between'>
|
|
97
|
-
<span>{taxLabel}</span>
|
|
98
|
-
<span>
|
|
99
|
-
{currencySymbol}
|
|
100
|
-
{tax.toFixed(2)}
|
|
101
|
-
</span>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<Separator />
|
|
106
|
-
|
|
107
|
-
<div className='flex justify-between text-lg font-semibold'>
|
|
108
|
-
<span>{totalLabel}</span>
|
|
109
|
-
<span>
|
|
110
|
-
{currencySymbol}
|
|
111
|
-
{total.toFixed(2)}
|
|
112
|
-
</span>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
{shipping === 0 ? (
|
|
116
|
-
<div className='bg-muted/50 rounded-lg p-3 text-sm'>
|
|
117
|
-
<div className='flex items-center gap-2 font-medium'>
|
|
118
|
-
<Truck className='size-4' aria-hidden='true' />
|
|
119
|
-
<span>
|
|
120
|
-
{freeShippingNoteLabel} {currencySymbol}
|
|
121
|
-
{freeShippingThreshold}
|
|
122
|
-
</span>
|
|
123
|
-
</div>
|
|
124
|
-
<p className='text-muted-foreground mt-1'>{estimatedDeliveryLabel}</p>
|
|
125
|
-
</div>
|
|
126
|
-
) : null}
|
|
127
|
-
</CardContent>
|
|
128
|
-
</Card>
|
|
129
|
-
)
|
|
130
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { CreditCard, Shield } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
4
|
-
import { Field, FieldGroup, FieldLabel } from '../../../ui/field'
|
|
5
|
-
import { Input } from '../../../ui/input'
|
|
6
|
-
import { RadioGroup, RadioGroupItem } from '../../../ui/radio-group'
|
|
7
|
-
import { CheckoutFieldFeedback, checkoutFieldAria } from './checkout-field-feedback'
|
|
8
|
-
import type { PaymentMethod } from '@thorprovider/types'
|
|
9
|
-
import type {
|
|
10
|
-
CheckoutForm2Data,
|
|
11
|
-
CheckoutForm2FieldDescriptions,
|
|
12
|
-
CheckoutForm2FieldErrors,
|
|
13
|
-
} from './checkout-form-2'
|
|
14
|
-
|
|
15
|
-
interface CheckoutForm2PaymentCardProps {
|
|
16
|
-
paymentMethod: string
|
|
17
|
-
onPaymentMethodChange: (method: string) => void
|
|
18
|
-
data: CheckoutForm2Data
|
|
19
|
-
onChange: (field: keyof CheckoutForm2Data, value: string | boolean) => void
|
|
20
|
-
onCardNumberChange: (value: string) => void
|
|
21
|
-
onExpiryChange: (value: string) => void
|
|
22
|
-
paymentMethods?: PaymentMethod[]
|
|
23
|
-
noMethodsLabel?: string
|
|
24
|
-
paymentLabel: string
|
|
25
|
-
cardNumberLabel: string
|
|
26
|
-
expiryLabel: string
|
|
27
|
-
cvvLabel: string
|
|
28
|
-
cardNameLabel: string
|
|
29
|
-
encryptedNoteLabel: string
|
|
30
|
-
fieldErrors?: CheckoutForm2FieldErrors
|
|
31
|
-
fieldDescriptions?: CheckoutForm2FieldDescriptions
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function CheckoutForm2PaymentCard({
|
|
35
|
-
paymentMethod,
|
|
36
|
-
onPaymentMethodChange,
|
|
37
|
-
data,
|
|
38
|
-
onChange,
|
|
39
|
-
onCardNumberChange,
|
|
40
|
-
onExpiryChange,
|
|
41
|
-
paymentMethods = [],
|
|
42
|
-
noMethodsLabel = 'No hay métodos de pago disponibles para esta región.',
|
|
43
|
-
paymentLabel,
|
|
44
|
-
cardNumberLabel,
|
|
45
|
-
expiryLabel,
|
|
46
|
-
cvvLabel,
|
|
47
|
-
cardNameLabel,
|
|
48
|
-
encryptedNoteLabel,
|
|
49
|
-
fieldErrors,
|
|
50
|
-
fieldDescriptions,
|
|
51
|
-
}: CheckoutForm2PaymentCardProps) {
|
|
52
|
-
const selectedMethod = paymentMethods.find(method => method.id === paymentMethod)
|
|
53
|
-
const isCard = selectedMethod?.type === 'card'
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<Card>
|
|
57
|
-
<CardHeader>
|
|
58
|
-
<CardTitle className='flex items-center gap-2'>
|
|
59
|
-
<CreditCard className='size-5' aria-hidden='true' />
|
|
60
|
-
{paymentLabel}
|
|
61
|
-
</CardTitle>
|
|
62
|
-
</CardHeader>
|
|
63
|
-
<CardContent className='flex flex-col gap-4'>
|
|
64
|
-
{paymentMethods.length === 0 ? (
|
|
65
|
-
<p className='text-muted-foreground text-sm'>{noMethodsLabel}</p>
|
|
66
|
-
) : (
|
|
67
|
-
<RadioGroup value={paymentMethod} onValueChange={value => value && onPaymentMethodChange(value)}>
|
|
68
|
-
{paymentMethods.map(method => (
|
|
69
|
-
<Field key={method.id} orientation='horizontal'>
|
|
70
|
-
<RadioGroupItem value={method.id} id={method.id} />
|
|
71
|
-
<FieldLabel htmlFor={method.id} className='flex flex-col gap-0.5 font-normal'>
|
|
72
|
-
<span className='flex items-center gap-2'>
|
|
73
|
-
<CreditCard className='size-4' aria-hidden='true' />
|
|
74
|
-
{method.name}
|
|
75
|
-
</span>
|
|
76
|
-
{method.description ? (
|
|
77
|
-
<span className='text-muted-foreground text-xs'>{method.description}</span>
|
|
78
|
-
) : null}
|
|
79
|
-
</FieldLabel>
|
|
80
|
-
</Field>
|
|
81
|
-
))}
|
|
82
|
-
</RadioGroup>
|
|
83
|
-
)}
|
|
84
|
-
|
|
85
|
-
{selectedMethod && !isCard ? (
|
|
86
|
-
<div className='bg-muted/50 rounded-lg p-4 text-sm'>
|
|
87
|
-
<p className='font-medium'>{selectedMethod.name}</p>
|
|
88
|
-
{selectedMethod.description ? <p className='text-muted-foreground mt-1'>{selectedMethod.description}</p> : null}
|
|
89
|
-
</div>
|
|
90
|
-
) : null}
|
|
91
|
-
|
|
92
|
-
{isCard ? (
|
|
93
|
-
<div className='border-muted border-l-2 ps-6'>
|
|
94
|
-
<FieldGroup>
|
|
95
|
-
<Field data-invalid={!!fieldErrors?.cardNumber?.length}>
|
|
96
|
-
<FieldLabel htmlFor='cardNumber'>{cardNumberLabel}</FieldLabel>
|
|
97
|
-
<Input
|
|
98
|
-
id='cardNumber'
|
|
99
|
-
placeholder='1234 5678 9012 3456'
|
|
100
|
-
inputMode='numeric'
|
|
101
|
-
value={data.cardNumber}
|
|
102
|
-
onChange={event => onCardNumberChange(event.target.value)}
|
|
103
|
-
{...checkoutFieldAria('cardNumber', fieldErrors?.cardNumber)}
|
|
104
|
-
className='h-9 font-mono'
|
|
105
|
-
/>
|
|
106
|
-
<CheckoutFieldFeedback
|
|
107
|
-
id='cardNumber'
|
|
108
|
-
errors={fieldErrors?.cardNumber}
|
|
109
|
-
description={fieldDescriptions?.cardNumber}
|
|
110
|
-
/>
|
|
111
|
-
</Field>
|
|
112
|
-
<div className='grid gap-4 sm:grid-cols-2 md:grid-cols-3'>
|
|
113
|
-
<Field data-invalid={!!fieldErrors?.expiryDate?.length} className='sm:col-span-2'>
|
|
114
|
-
<FieldLabel htmlFor='expiryDate'>{expiryLabel}</FieldLabel>
|
|
115
|
-
<Input
|
|
116
|
-
id='expiryDate'
|
|
117
|
-
placeholder='MM/AA'
|
|
118
|
-
inputMode='numeric'
|
|
119
|
-
value={data.expiryDate}
|
|
120
|
-
onChange={event => onExpiryChange(event.target.value)}
|
|
121
|
-
{...checkoutFieldAria('expiryDate', fieldErrors?.expiryDate)}
|
|
122
|
-
className='h-9 font-mono'
|
|
123
|
-
/>
|
|
124
|
-
<CheckoutFieldFeedback
|
|
125
|
-
id='expiryDate'
|
|
126
|
-
errors={fieldErrors?.expiryDate}
|
|
127
|
-
description={fieldDescriptions?.expiryDate}
|
|
128
|
-
/>
|
|
129
|
-
</Field>
|
|
130
|
-
<Field data-invalid={!!fieldErrors?.cvv?.length}>
|
|
131
|
-
<FieldLabel htmlFor='cvv'>{cvvLabel}</FieldLabel>
|
|
132
|
-
<Input
|
|
133
|
-
id='cvv'
|
|
134
|
-
placeholder='123'
|
|
135
|
-
inputMode='numeric'
|
|
136
|
-
value={data.cvv}
|
|
137
|
-
onChange={event => onChange('cvv', event.target.value)}
|
|
138
|
-
{...checkoutFieldAria('cvv', fieldErrors?.cvv)}
|
|
139
|
-
className='h-9 font-mono'
|
|
140
|
-
/>
|
|
141
|
-
<CheckoutFieldFeedback
|
|
142
|
-
id='cvv'
|
|
143
|
-
errors={fieldErrors?.cvv}
|
|
144
|
-
description={fieldDescriptions?.cvv}
|
|
145
|
-
/>
|
|
146
|
-
</Field>
|
|
147
|
-
</div>
|
|
148
|
-
<Field data-invalid={!!fieldErrors?.cardName?.length}>
|
|
149
|
-
<FieldLabel htmlFor='cardName'>{cardNameLabel}</FieldLabel>
|
|
150
|
-
<Input
|
|
151
|
-
id='cardName'
|
|
152
|
-
placeholder='Juan Pérez'
|
|
153
|
-
value={data.cardName}
|
|
154
|
-
onChange={event => onChange('cardName', event.target.value)}
|
|
155
|
-
{...checkoutFieldAria('cardName', fieldErrors?.cardName)}
|
|
156
|
-
className='h-9'
|
|
157
|
-
/>
|
|
158
|
-
<CheckoutFieldFeedback
|
|
159
|
-
id='cardName'
|
|
160
|
-
errors={fieldErrors?.cardName}
|
|
161
|
-
description={fieldDescriptions?.cardName}
|
|
162
|
-
/>
|
|
163
|
-
</Field>
|
|
164
|
-
</FieldGroup>
|
|
165
|
-
</div>
|
|
166
|
-
) : null}
|
|
167
|
-
|
|
168
|
-
<div className='bg-muted/50 flex items-center gap-2 rounded-lg p-3 text-sm'>
|
|
169
|
-
<Shield className='size-4 text-success' aria-hidden='true' />
|
|
170
|
-
<span>{encryptedNoteLabel}</span>
|
|
171
|
-
</div>
|
|
172
|
-
</CardContent>
|
|
173
|
-
</Card>
|
|
174
|
-
)
|
|
175
|
-
}
|