@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,190 +0,0 @@
1
- import { Truck } from 'lucide-react'
2
-
3
- import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
4
- import { CheckboxCheck } from './checkbox-check'
5
- import { Field, FieldGroup, FieldLabel } from '../../../ui/field'
6
- import { Input } from '../../../ui/input'
7
- import {
8
- Select,
9
- SelectContent,
10
- SelectGroup,
11
- SelectItem,
12
- SelectTrigger,
13
- SelectValue,
14
- } from '../../../ui/select'
15
- import { CheckoutFieldFeedback, checkoutFieldAria } from './checkout-field-feedback'
16
- import type {
17
- CheckoutForm2CountryOption,
18
- CheckoutForm2Data,
19
- CheckoutForm2FieldDescriptions,
20
- CheckoutForm2FieldErrors,
21
- } from './checkout-form-2'
22
-
23
- interface CheckoutForm2ShippingCardProps {
24
- data: CheckoutForm2Data
25
- onChange: (field: keyof CheckoutForm2Data, value: string | boolean) => void
26
- countryOptions: CheckoutForm2CountryOption[]
27
- shippingAddressLabel: string
28
- firstNameLabel: string
29
- lastNameLabel: string
30
- addressLabel: string
31
- cityLabel: string
32
- zipCodeLabel: string
33
- countryLabel: string
34
- saveInfoLabel: string
35
- fieldErrors?: CheckoutForm2FieldErrors
36
- fieldDescriptions?: CheckoutForm2FieldDescriptions
37
- }
38
-
39
- const countryLabelFor = (value: string, options: CheckoutForm2CountryOption[]) =>
40
- options.find(option => option.value === value)?.label ?? value
41
-
42
- export function CheckoutForm2ShippingCard({
43
- data,
44
- onChange,
45
- countryOptions,
46
- shippingAddressLabel,
47
- firstNameLabel,
48
- lastNameLabel,
49
- addressLabel,
50
- cityLabel,
51
- zipCodeLabel,
52
- countryLabel,
53
- saveInfoLabel,
54
- fieldErrors,
55
- fieldDescriptions,
56
- }: CheckoutForm2ShippingCardProps) {
57
- return (
58
- <Card>
59
- <CardHeader>
60
- <CardTitle className='flex items-center gap-2'>
61
- <Truck className='size-5' aria-hidden='true' />
62
- {shippingAddressLabel}
63
- </CardTitle>
64
- </CardHeader>
65
- <CardContent>
66
- <FieldGroup>
67
- <div className='grid gap-4 sm:grid-cols-2'>
68
- <Field data-invalid={!!fieldErrors?.firstName?.length}>
69
- <FieldLabel htmlFor='firstName'>{firstNameLabel}</FieldLabel>
70
- <Input
71
- id='firstName'
72
- placeholder='Juan'
73
- value={data.firstName}
74
- onChange={event => onChange('firstName', event.target.value)}
75
- {...checkoutFieldAria('firstName', fieldErrors?.firstName)}
76
- className='h-9'
77
- />
78
- <CheckoutFieldFeedback
79
- id='firstName'
80
- errors={fieldErrors?.firstName}
81
- description={fieldDescriptions?.firstName}
82
- />
83
- </Field>
84
- <Field data-invalid={!!fieldErrors?.lastName?.length}>
85
- <FieldLabel htmlFor='lastName'>{lastNameLabel}</FieldLabel>
86
- <Input
87
- id='lastName'
88
- placeholder='Pérez'
89
- value={data.lastName}
90
- onChange={event => onChange('lastName', event.target.value)}
91
- {...checkoutFieldAria('lastName', fieldErrors?.lastName)}
92
- className='h-9'
93
- />
94
- <CheckoutFieldFeedback
95
- id='lastName'
96
- errors={fieldErrors?.lastName}
97
- description={fieldDescriptions?.lastName}
98
- />
99
- </Field>
100
- </div>
101
-
102
- <Field data-invalid={!!fieldErrors?.address?.length}>
103
- <FieldLabel htmlFor='address'>{addressLabel}</FieldLabel>
104
- <Input
105
- id='address'
106
- placeholder='Calle Principal 123'
107
- value={data.address}
108
- onChange={event => onChange('address', event.target.value)}
109
- {...checkoutFieldAria('address', fieldErrors?.address)}
110
- className='h-9'
111
- />
112
- <CheckoutFieldFeedback
113
- id='address'
114
- errors={fieldErrors?.address}
115
- description={fieldDescriptions?.address}
116
- />
117
- </Field>
118
-
119
- <div className='grid gap-4 sm:grid-cols-2 md:grid-cols-3'>
120
- <Field data-invalid={!!fieldErrors?.city?.length}>
121
- <FieldLabel htmlFor='city'>{cityLabel}</FieldLabel>
122
- <Input
123
- id='city'
124
- placeholder='Madrid'
125
- value={data.city}
126
- onChange={event => onChange('city', event.target.value)}
127
- {...checkoutFieldAria('city', fieldErrors?.city)}
128
- className='h-9'
129
- />
130
- <CheckoutFieldFeedback
131
- id='city'
132
- errors={fieldErrors?.city}
133
- description={fieldDescriptions?.city}
134
- />
135
- </Field>
136
- <Field data-invalid={!!fieldErrors?.zipCode?.length}>
137
- <FieldLabel htmlFor='zipCode'>{zipCodeLabel}</FieldLabel>
138
- <Input
139
- id='zipCode'
140
- placeholder='28001'
141
- value={data.zipCode}
142
- onChange={event => onChange('zipCode', event.target.value)}
143
- {...checkoutFieldAria('zipCode', fieldErrors?.zipCode)}
144
- className='h-9'
145
- />
146
- <CheckoutFieldFeedback
147
- id='zipCode'
148
- errors={fieldErrors?.zipCode}
149
- description={fieldDescriptions?.zipCode}
150
- />
151
- </Field>
152
- <Field data-invalid={!!fieldErrors?.country?.length}>
153
- <FieldLabel htmlFor='country'>{countryLabel}</FieldLabel>
154
- <Select value={data.country} onValueChange={value => value && onChange('country', value)}>
155
- <SelectTrigger id='country' {...checkoutFieldAria('country', fieldErrors?.country)} className='w-full'>
156
- <SelectValue>{countryLabelFor(data.country, countryOptions)}</SelectValue>
157
- </SelectTrigger>
158
- <SelectContent>
159
- <SelectGroup>
160
- {countryOptions.map(country => (
161
- <SelectItem key={country.value} value={country.value}>
162
- {country.label}
163
- </SelectItem>
164
- ))}
165
- </SelectGroup>
166
- </SelectContent>
167
- </Select>
168
- <CheckoutFieldFeedback
169
- id='country'
170
- errors={fieldErrors?.country}
171
- description={fieldDescriptions?.country}
172
- />
173
- </Field>
174
- </div>
175
-
176
- <Field orientation='horizontal'>
177
- <CheckboxCheck
178
- id='saveInfo'
179
- checked={data.saveInfo}
180
- onCheckedChange={checked => onChange('saveInfo', checked as boolean)}
181
- />
182
- <FieldLabel htmlFor='saveInfo' className='text-sm font-normal'>
183
- {saveInfoLabel}
184
- </FieldLabel>
185
- </Field>
186
- </FieldGroup>
187
- </CardContent>
188
- </Card>
189
- )
190
- }
@@ -1,312 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, type FormEvent } from 'react'
4
- import { ArrowLeft, Lock, Package } from 'lucide-react'
5
-
6
- import { Button } from '../../../ui/button'
7
- import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
8
- import { CheckboxCheck } from './checkbox-check'
9
- import { Field, FieldGroup, FieldLabel } from '../../../ui/field'
10
- import { Input } from '../../../ui/input'
11
- import { CheckoutFieldFeedback, checkoutFieldAria } from './checkout-field-feedback'
12
- import { formatCardNumber, formatExpiryDate } from './card-formatting'
13
- import type { PaymentMethod } from '@thorprovider/types'
14
- import type { CheckoutFieldErrors } from './checkout-form-types'
15
-
16
- import { CheckoutForm2OrderSummary } from './checkout-form-2-order-summary'
17
- import { CheckoutForm2PaymentCard } from './checkout-form-2-payment-card'
18
- import { CheckoutForm2ShippingCard } from './checkout-form-2-shipping-card'
19
-
20
- export interface CheckoutForm2Item {
21
- id: string
22
- name: string
23
- price: number
24
- quantity: number
25
- image: string
26
- }
27
-
28
- export interface CheckoutForm2CountryOption {
29
- value: string
30
- label: string
31
- }
32
-
33
- export interface CheckoutForm2Data {
34
- email: string
35
- firstName: string
36
- lastName: string
37
- address: string
38
- city: string
39
- zipCode: string
40
- country: string
41
- saveInfo: boolean
42
- newsletter: boolean
43
- cardNumber: string
44
- expiryDate: string
45
- cvv: string
46
- cardName: string
47
- }
48
-
49
- export type CheckoutForm2FieldErrors = CheckoutFieldErrors<keyof CheckoutForm2Data>
50
-
51
- export type CheckoutForm2FieldDescriptions = Partial<
52
- Record<keyof CheckoutForm2Data, string>
53
- >
54
-
55
- export interface CheckoutForm2Totals {
56
- subtotal: number
57
- shipping: number
58
- tax: number
59
- total: number
60
- }
61
-
62
- export interface CheckoutForm2Props {
63
- items: CheckoutForm2Item[]
64
- countryOptions?: CheckoutForm2CountryOption[]
65
- paymentMethods?: PaymentMethod[]
66
- noMethodsLabel?: string
67
- taxRate?: number
68
- shippingCost?: number
69
- freeShippingThreshold?: number
70
- currencySymbol?: string
71
- titleLabel?: string
72
- descriptionLabel?: string
73
- backToCartLabel?: string
74
- completeOrderLabel?: string
75
- contactLabel?: string
76
- emailLabel?: string
77
- emailPlaceholder?: string
78
- newsletterLabel?: string
79
- shippingAddressLabel?: string
80
- firstNameLabel?: string
81
- lastNameLabel?: string
82
- addressLabel?: string
83
- cityLabel?: string
84
- zipCodeLabel?: string
85
- countryLabel?: string
86
- saveInfoLabel?: string
87
- paymentLabel?: string
88
- cardNumberLabel?: string
89
- expiryLabel?: string
90
- cvvLabel?: string
91
- cardNameLabel?: string
92
- encryptedNoteLabel?: string
93
- orderSummaryLabel?: string
94
- subtotalLabel?: string
95
- shippingLineLabel?: string
96
- freeShippingLineLabel?: string
97
- taxLabel?: string
98
- totalLabel?: string
99
- freeShippingNoteLabel?: string
100
- estimatedDeliveryLabel?: string
101
- values: CheckoutForm2Data
102
- onChange: (field: keyof CheckoutForm2Data, value: string | boolean) => void
103
- fieldErrors?: CheckoutForm2FieldErrors
104
- fieldDescriptions?: CheckoutForm2FieldDescriptions
105
- onBackToCart?: () => void
106
- onCompleteOrder?: (data: CheckoutForm2Data, totals: CheckoutForm2Totals) => void
107
- }
108
-
109
- const DEFAULT_COUNTRY_OPTIONS: CheckoutForm2CountryOption[] = [
110
- { value: 'US', label: 'Estados Unidos' },
111
- { value: 'CA', label: 'Canadá' },
112
- { value: 'UK', label: 'Reino Unido' },
113
- { value: 'AU', label: 'Australia' },
114
- ]
115
-
116
- export function CheckoutForm2({
117
- items,
118
- countryOptions = DEFAULT_COUNTRY_OPTIONS,
119
- paymentMethods = [],
120
- noMethodsLabel,
121
- taxRate = 0.08,
122
- shippingCost = 9.99,
123
- freeShippingThreshold = 75,
124
- currencySymbol = '$',
125
- titleLabel = 'Finalizar compra',
126
- descriptionLabel = 'Completa tu pedido de forma segura',
127
- backToCartLabel = 'Volver al carrito',
128
- completeOrderLabel = 'Completar pedido',
129
- contactLabel = 'Información de contacto',
130
- emailLabel = 'Correo electrónico',
131
- emailPlaceholder = 'tucorreo@ejemplo.com',
132
- newsletterLabel = 'Quiero recibir novedades y ofertas',
133
- shippingAddressLabel = 'Dirección de envío',
134
- firstNameLabel = 'Nombre',
135
- lastNameLabel = 'Apellido',
136
- addressLabel = 'Dirección',
137
- cityLabel = 'Ciudad',
138
- zipCodeLabel = 'Código postal',
139
- countryLabel = 'País',
140
- saveInfoLabel = 'Guardar esta información para la próxima vez',
141
- paymentLabel = 'Método de pago',
142
- cardNumberLabel = 'Número de tarjeta',
143
- expiryLabel = 'Fecha de vencimiento',
144
- cvvLabel = 'CVV',
145
- cardNameLabel = 'Nombre en la tarjeta',
146
- encryptedNoteLabel = 'Tu información de pago está cifrada y segura',
147
- orderSummaryLabel = 'Resumen del pedido',
148
- subtotalLabel = 'Subtotal',
149
- shippingLineLabel = 'Envío',
150
- freeShippingLineLabel = 'Gratis',
151
- taxLabel = 'Impuestos',
152
- totalLabel = 'Total',
153
- freeShippingNoteLabel = 'Envío gratis en pedidos superiores a',
154
- estimatedDeliveryLabel = 'Entrega estimada: 3-5 días hábiles',
155
- values,
156
- onChange,
157
- fieldErrors,
158
- fieldDescriptions,
159
- onBackToCart,
160
- onCompleteOrder,
161
- }: CheckoutForm2Props) {
162
- const [paymentMethod, setPaymentMethod] = useState(paymentMethods[0]?.id ?? '')
163
-
164
- const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
165
- const shipping = subtotal >= freeShippingThreshold ? 0 : shippingCost
166
- const tax = subtotal * taxRate
167
- const total = subtotal + shipping + tax
168
-
169
- const handleCardNumberChange = (value: string) => {
170
- onChange('cardNumber', formatCardNumber(value))
171
- }
172
-
173
- const handleExpiryChange = (value: string) => {
174
- onChange('expiryDate', formatExpiryDate(value))
175
- }
176
-
177
- const handleSubmit = (event: FormEvent) => {
178
- event.preventDefault()
179
- onCompleteOrder?.(values, { subtotal, shipping, tax, total })
180
- }
181
-
182
- return (
183
- <section className='py-8'>
184
- <div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
185
- <div className='mb-8'>
186
- <Button
187
- type='button'
188
- variant='secondary'
189
- size='sm'
190
- className='mb-4 h-8 px-3 text-xs cursor-pointer'
191
- onClick={onBackToCart}
192
- >
193
- <ArrowLeft />
194
- {backToCartLabel}
195
- </Button>
196
- <h1 className='text-3xl font-bold tracking-tight text-balance'>{titleLabel}</h1>
197
- <p className='text-muted-foreground mt-2'>{descriptionLabel}</p>
198
- </div>
199
-
200
- <div className='grid gap-8 lg:grid-cols-3'>
201
- <div className='order-2 lg:order-1 lg:col-span-2'>
202
- <form onSubmit={handleSubmit} className='flex flex-col gap-8'>
203
- <Card>
204
- <CardHeader>
205
- <CardTitle className='flex items-center gap-2'>
206
- <Package className='size-5' aria-hidden='true' />
207
- {contactLabel}
208
- </CardTitle>
209
- </CardHeader>
210
- <CardContent>
211
- <FieldGroup>
212
- <Field data-invalid={!!fieldErrors?.email?.length}>
213
- <FieldLabel htmlFor='email'>{emailLabel}</FieldLabel>
214
- <Input
215
- id='email'
216
- type='text'
217
- inputMode='email'
218
- placeholder={emailPlaceholder}
219
- value={values.email}
220
- onChange={event => onChange('email', event.target.value)}
221
- {...checkoutFieldAria('email', fieldErrors?.email)}
222
- className='h-9'
223
- />
224
- <CheckoutFieldFeedback
225
- id='email'
226
- errors={fieldErrors?.email}
227
- description={fieldDescriptions?.email}
228
- />
229
- </Field>
230
- <Field orientation='horizontal'>
231
- <CheckboxCheck
232
- id='newsletter'
233
- checked={values.newsletter}
234
- onCheckedChange={checked => onChange('newsletter', checked as boolean)}
235
- />
236
- <FieldLabel htmlFor='newsletter' className='text-sm font-normal'>
237
- {newsletterLabel}
238
- </FieldLabel>
239
- </Field>
240
- </FieldGroup>
241
- </CardContent>
242
- </Card>
243
-
244
- <CheckoutForm2ShippingCard
245
- data={values}
246
- onChange={onChange}
247
- countryOptions={countryOptions}
248
- shippingAddressLabel={shippingAddressLabel}
249
- firstNameLabel={firstNameLabel}
250
- lastNameLabel={lastNameLabel}
251
- addressLabel={addressLabel}
252
- cityLabel={cityLabel}
253
- zipCodeLabel={zipCodeLabel}
254
- countryLabel={countryLabel}
255
- saveInfoLabel={saveInfoLabel}
256
- fieldErrors={fieldErrors}
257
- fieldDescriptions={fieldDescriptions}
258
- />
259
-
260
- <CheckoutForm2PaymentCard
261
- paymentMethod={paymentMethod}
262
- onPaymentMethodChange={setPaymentMethod}
263
- data={values}
264
- onChange={onChange}
265
- onCardNumberChange={handleCardNumberChange}
266
- onExpiryChange={handleExpiryChange}
267
- paymentMethods={paymentMethods}
268
- noMethodsLabel={noMethodsLabel}
269
- paymentLabel={paymentLabel}
270
- cardNumberLabel={cardNumberLabel}
271
- expiryLabel={expiryLabel}
272
- cvvLabel={cvvLabel}
273
- cardNameLabel={cardNameLabel}
274
- encryptedNoteLabel={encryptedNoteLabel}
275
- fieldErrors={fieldErrors}
276
- fieldDescriptions={fieldDescriptions}
277
- />
278
-
279
- <Button type='submit' size='lg' className='h-10 px-8 w-full cursor-pointer'>
280
- <Lock />
281
- {completeOrderLabel} • {currencySymbol}
282
- {total.toFixed(2)}
283
- </Button>
284
- </form>
285
- </div>
286
-
287
- <div className='order-1 lg:order-2 lg:col-span-1'>
288
- <CheckoutForm2OrderSummary
289
- items={items}
290
- subtotal={subtotal}
291
- shipping={shipping}
292
- tax={tax}
293
- total={total}
294
- currencySymbol={currencySymbol}
295
- orderSummaryLabel={orderSummaryLabel}
296
- subtotalLabel={subtotalLabel}
297
- shippingLabel={shippingLineLabel}
298
- freeShippingLabel={freeShippingLineLabel}
299
- taxLabel={taxLabel}
300
- totalLabel={totalLabel}
301
- freeShippingNoteLabel={freeShippingNoteLabel}
302
- estimatedDeliveryLabel={estimatedDeliveryLabel}
303
- freeShippingThreshold={freeShippingThreshold}
304
- />
305
- </div>
306
- </div>
307
- </div>
308
- </section>
309
- )
310
- }
311
-
312
- export default CheckoutForm2
@@ -1,91 +0,0 @@
1
- 'use client'
2
-
3
- import { CheckboxCheck } from './checkbox-check'
4
- import { Field, FieldGroup, FieldLabel } from '../../../ui/field'
5
- import { Input } from '../../../ui/input'
6
-
7
- import type { CheckoutForm3ChangeHandler, CheckoutForm3FormData } from './checkout-form-3'
8
-
9
- interface CheckoutForm3InformationStepProps {
10
- formData: CheckoutForm3FormData
11
- onChange: CheckoutForm3ChangeHandler
12
- emailLabel: string
13
- firstNameLabel: string
14
- lastNameLabel: string
15
- phoneLabel: string
16
- newsletterLabel: string
17
- }
18
-
19
- export function CheckoutForm3InformationStep({
20
- formData,
21
- onChange,
22
- emailLabel,
23
- firstNameLabel,
24
- lastNameLabel,
25
- phoneLabel,
26
- newsletterLabel,
27
- }: CheckoutForm3InformationStepProps) {
28
- return (
29
- <FieldGroup>
30
- <Field>
31
- <FieldLabel htmlFor='email'>{emailLabel} *</FieldLabel>
32
- <Input
33
- id='email'
34
- type='email'
35
- placeholder='tucorreo@ejemplo.com'
36
- value={formData.email}
37
- onChange={event => onChange('email', event.target.value)}
38
- required
39
- className='h-9'
40
- />
41
- </Field>
42
-
43
- <div className='grid gap-4 sm:grid-cols-2'>
44
- <Field>
45
- <FieldLabel htmlFor='firstName'>{firstNameLabel} *</FieldLabel>
46
- <Input
47
- id='firstName'
48
- placeholder='Juan'
49
- value={formData.firstName}
50
- onChange={event => onChange('firstName', event.target.value)}
51
- required
52
- className='h-9'
53
- />
54
- </Field>
55
- <Field>
56
- <FieldLabel htmlFor='lastName'>{lastNameLabel} *</FieldLabel>
57
- <Input
58
- id='lastName'
59
- placeholder='Pérez'
60
- value={formData.lastName}
61
- onChange={event => onChange('lastName', event.target.value)}
62
- required
63
- className='h-9'
64
- />
65
- </Field>
66
- </div>
67
-
68
- <Field>
69
- <FieldLabel htmlFor='phone'>{phoneLabel}</FieldLabel>
70
- <Input
71
- id='phone'
72
- placeholder='+34 600 123 456'
73
- value={formData.phone}
74
- onChange={event => onChange('phone', event.target.value)}
75
- className='h-9'
76
- />
77
- </Field>
78
-
79
- <Field orientation='horizontal'>
80
- <CheckboxCheck
81
- id='newsletterCheck'
82
- checked={formData.newsletter}
83
- onCheckedChange={checked => onChange('newsletter', checked as boolean)}
84
- />
85
- <FieldLabel htmlFor='newsletterCheck' className='text-sm font-normal'>
86
- {newsletterLabel}
87
- </FieldLabel>
88
- </Field>
89
- </FieldGroup>
90
- )
91
- }
@@ -1,110 +0,0 @@
1
- import { Badge } from '../../../ui/badge'
2
- import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
3
- import { Separator } from '../../../ui/separator'
4
-
5
- import type { CheckoutForm3CartItem } from './checkout-form-3'
6
-
7
- interface CheckoutForm3OrderSummaryProps {
8
- items: CheckoutForm3CartItem[]
9
- subtotal: number
10
- shipping: number
11
- tax: number
12
- total: number
13
- currencySymbol: string
14
- orderSummaryLabel: string
15
- subtotalLabel: string
16
- shippingLabel: string
17
- taxLabel: string
18
- totalLabel: string
19
- freeLabel: string
20
- }
21
-
22
- export function CheckoutForm3OrderSummary({
23
- items,
24
- subtotal,
25
- shipping,
26
- tax,
27
- total,
28
- currencySymbol,
29
- orderSummaryLabel,
30
- subtotalLabel,
31
- shippingLabel,
32
- taxLabel,
33
- totalLabel,
34
- freeLabel,
35
- }: CheckoutForm3OrderSummaryProps) {
36
- return (
37
- <Card className='sticky top-6'>
38
- <CardHeader>
39
- <CardTitle>{orderSummaryLabel}</CardTitle>
40
- </CardHeader>
41
- <CardContent className='flex flex-col gap-4'>
42
- <div className='flex flex-col gap-3'>
43
- {items.map(item => (
44
- <div key={item.id} className='flex items-center gap-3'>
45
- <div className='relative'>
46
- <img
47
- src={item.image || undefined}
48
- alt={item.name}
49
- width={48}
50
- height={48}
51
- className='size-12 rounded-md object-cover'
52
- />
53
- <Badge
54
- variant='secondary'
55
- className='px-2.5 py-0.5 font-semibold absolute -top-2 -end-2 size-5 rounded-full p-0 text-xs'
56
- >
57
- {item.quantity}
58
- </Badge>
59
- </div>
60
- <div className='flex-1'>
61
- <h4 className='text-sm font-medium'>{item.name}</h4>
62
- <p className='text-muted-foreground text-sm'>
63
- {currencySymbol}
64
- {item.price}
65
- </p>
66
- </div>
67
- <div className='text-sm font-medium'>
68
- {currencySymbol}
69
- {(item.price * item.quantity).toFixed(2)}
70
- </div>
71
- </div>
72
- ))}
73
- </div>
74
-
75
- <Separator />
76
-
77
- <div className='flex flex-col gap-2 text-sm'>
78
- <div className='flex justify-between'>
79
- <span>{subtotalLabel}</span>
80
- <span>
81
- {currencySymbol}
82
- {subtotal.toFixed(2)}
83
- </span>
84
- </div>
85
- <div className='flex justify-between'>
86
- <span>{shippingLabel}</span>
87
- <span>{shipping === 0 ? freeLabel : `${currencySymbol}${shipping.toFixed(2)}`}</span>
88
- </div>
89
- <div className='flex justify-between'>
90
- <span>{taxLabel}</span>
91
- <span>
92
- {currencySymbol}
93
- {tax.toFixed(2)}
94
- </span>
95
- </div>
96
- </div>
97
-
98
- <Separator />
99
-
100
- <div className='flex justify-between text-lg font-semibold'>
101
- <span>{totalLabel}</span>
102
- <span>
103
- {currencySymbol}
104
- {total.toFixed(2)}
105
- </span>
106
- </div>
107
- </CardContent>
108
- </Card>
109
- )
110
- }