@thorprovider/blocks 0.1.3 → 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.
Files changed (178) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +228 -68
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  7. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  8. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  9. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  10. package/src/blocks/application/verification/verify-1.tsx +2 -2
  11. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  12. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  13. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  14. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  15. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  16. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  17. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  19. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  20. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  21. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  22. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  23. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  24. package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
  25. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  27. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  29. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  30. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
  31. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
  32. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  33. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  34. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  35. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  36. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  37. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  38. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  39. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  40. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  41. package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
  42. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  43. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  44. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  45. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  46. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  47. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  48. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  49. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  50. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  51. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  52. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  53. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  54. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  55. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  56. package/src/blocks/marketing/features/feature.tsx +195 -0
  57. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  58. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  59. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  60. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  61. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  62. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  63. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  64. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  65. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  66. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  67. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  68. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  69. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  70. package/src/index.ts +156 -128
  71. package/src/lib/contracts-curated.test.ts +47 -29
  72. package/src/lib/contracts.test.ts +198 -2
  73. package/src/lib/contracts.ts +410 -16
  74. package/src/ui/button-group.tsx +1 -1
  75. package/src/ui/carousel.tsx +1 -1
  76. package/src/ui/dialog.tsx +1 -1
  77. package/src/ui/field.tsx +2 -2
  78. package/src/ui/input-group.tsx +3 -3
  79. package/src/ui/pagination.tsx +1 -1
  80. package/src/ui/sidebar.tsx +7 -7
  81. package/src/ui/toggle-group.tsx +1 -1
  82. package/tsconfig.build.json +12 -0
  83. package/tsup.config.ts +11 -7
  84. package/.turbo/turbo-build.log +0 -24
  85. package/.turbo/turbo-test.log +0 -47
  86. package/.turbo/turbo-type-check.log +0 -0
  87. package/dist/.tsbuildinfo +0 -1
  88. package/dist/contracts.d.mts +0 -1732
  89. package/dist/contracts.d.ts +0 -1732
  90. package/dist/contracts.js +0 -423
  91. package/dist/contracts.js.map +0 -1
  92. package/dist/contracts.mjs +0 -365
  93. package/dist/contracts.mjs.map +0 -1
  94. package/dist/index.d.mts +0 -3068
  95. package/dist/index.d.ts +0 -3068
  96. package/dist/index.js +0 -17834
  97. package/dist/index.js.map +0 -1
  98. package/dist/index.mjs +0 -17740
  99. package/dist/index.mjs.map +0 -1
  100. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  101. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  102. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  103. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  104. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  105. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  106. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  107. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  108. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  109. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  110. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  111. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  112. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  113. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  114. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  115. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  116. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  117. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  118. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  119. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  120. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  121. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  122. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  123. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  124. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  125. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  126. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  127. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  128. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  129. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  130. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  131. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  132. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  133. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  134. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  135. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  136. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  137. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  138. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  139. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  140. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  141. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  142. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  143. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  144. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  145. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  146. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  147. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  148. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  149. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  150. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  151. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  152. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  153. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  154. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  155. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  156. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  157. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  158. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  159. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  160. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  161. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  162. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  163. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  164. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  165. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  166. package/src/blocks/marketing/features/feature-2.tsx +0 -82
  167. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  168. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  169. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  170. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  171. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  172. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  173. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  174. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
  175. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  176. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  177. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  178. 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
- }