@thorprovider/blocks 0.1.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/.turbo/turbo-build.log +24 -0
- package/.turbo/turbo-test.log +10 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -0
- package/README.md +161 -0
- package/components.json +33 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/contracts.d.mts +563 -0
- package/dist/contracts.d.ts +563 -0
- package/dist/contracts.js +207 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contracts.mjs +168 -0
- package/dist/contracts.mjs.map +1 -0
- package/dist/index.d.mts +2408 -0
- package/dist/index.d.ts +2408 -0
- package/dist/index.js +16422 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +16352 -0
- package/dist/index.mjs.map +1 -0
- package/docs/standards/data-contracts.md +70 -0
- package/docs/standards/theming.md +85 -0
- package/package.json +62 -0
- package/src/blocks/application/app-shells/app-shell-1-sidebar.tsx +198 -0
- package/src/blocks/application/app-shells/app-shell-1.tsx +196 -0
- package/src/blocks/application/login-forms/login-page-1.tsx +246 -0
- package/src/blocks/application/login-forms/login-page-2.tsx +207 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1-brand-mark.tsx +38 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +320 -0
- package/src/blocks/application/signup-forms/signup-page-1.tsx +231 -0
- package/src/blocks/application/verification/verify-1.tsx +307 -0
- package/src/blocks/application/verification/verify-confetti.tsx +192 -0
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +260 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +179 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +341 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +124 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +170 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +52 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +68 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +298 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +47 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +9 -0
- package/src/blocks/e-commerce/checkout-forms/checkbox-check.tsx +74 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +189 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +283 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-field-feedback.tsx +29 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +96 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-country-field.tsx +51 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +139 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +184 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +36 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +107 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +235 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +130 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +175 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +190 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +312 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +91 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +110 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +137 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +68 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +79 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +172 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +392 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-types.ts +7 -0
- package/src/blocks/e-commerce/navigation/category-icon.test.tsx +54 -0
- package/src/blocks/e-commerce/navigation/category-icon.tsx +94 -0
- package/src/blocks/e-commerce/navigation/mega-menu.test.tsx +139 -0
- package/src/blocks/e-commerce/navigation/mega-menu.tsx +271 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +152 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +276 -0
- package/src/blocks/e-commerce/order-history/order-history-1.tsx +159 -0
- package/src/blocks/e-commerce/order-history/order-history-2.tsx +222 -0
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +86 -0
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +126 -0
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +100 -0
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +129 -0
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +218 -0
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +131 -0
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +71 -0
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +166 -0
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +215 -0
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +98 -0
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +171 -0
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +367 -0
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +201 -0
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +272 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +59 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-lightbox-tab.tsx +104 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-reviews-tab.tsx +82 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-tabs.tsx +69 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +274 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-card.tsx +135 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-dialog.tsx +393 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view.tsx +136 -0
- package/src/blocks/e-commerce/reviews/review-rating-1-review-form.tsx +133 -0
- package/src/blocks/e-commerce/reviews/review-rating-1.tsx +178 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +246 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +291 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +110 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +51 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +295 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +342 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +391 -0
- package/src/blocks/e-commerce/store-containers/store-container-1.tsx +29 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +244 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +283 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +318 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +205 -0
- package/src/blocks/marketing/banners/avatar-group.tsx +69 -0
- package/src/blocks/marketing/banners/banner-1.tsx +188 -0
- package/src/blocks/marketing/cta/cta-section-1.tsx +112 -0
- package/src/blocks/marketing/faq/faq-block-1.tsx +45 -0
- package/src/blocks/marketing/features/feature-1.tsx +97 -0
- package/src/blocks/marketing/features/feature-2.tsx +82 -0
- package/src/blocks/marketing/footers/footer-section-1.tsx +141 -0
- package/src/blocks/marketing/footers/footer-section-3.tsx +192 -0
- package/src/blocks/marketing/footers/footer-section-4.tsx +355 -0
- package/src/blocks/marketing/hero-sections/avatar-group.tsx +86 -0
- package/src/blocks/marketing/hero-sections/hero-section-1.tsx +135 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1-logo-icon.tsx +52 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1.tsx +56 -0
- package/src/blocks/marketing/navbars/navbar-1.tsx +267 -0
- package/src/blocks/marketing/navbars/navbar-2.tsx +351 -0
- package/src/blocks/marketing/navbars/navbar-3.tsx +303 -0
- package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +118 -0
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +248 -0
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +177 -0
- package/src/blocks/marketing/navbars/navbar-4-mobile-nav.tsx +104 -0
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +219 -0
- package/src/blocks/marketing/navbars/navbar-4-search-bar.tsx +52 -0
- package/src/blocks/marketing/navbars/navbar-4.tsx +411 -0
- package/src/blocks/marketing/pricing/pricing-section-1.tsx +94 -0
- package/src/blocks/marketing/statistics/statistic-1.tsx +93 -0
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +141 -0
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +108 -0
- package/src/blocks/shared/reaction-toggle.tsx +145 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +200 -0
- package/src/lib/contracts.test.ts +32 -0
- package/src/lib/contracts.ts +235 -0
- package/src/lib/types/menu.ts +43 -0
- package/src/lib/types.ts +4 -0
- package/src/lib/utils.ts +10 -0
- package/src/ui/accordion.tsx +65 -0
- package/src/ui/avatar.tsx +108 -0
- package/src/ui/badge.tsx +47 -0
- package/src/ui/breadcrumb.tsx +108 -0
- package/src/ui/button-group.tsx +83 -0
- package/src/ui/button.tsx +63 -0
- package/src/ui/card.tsx +91 -0
- package/src/ui/carousel.tsx +241 -0
- package/src/ui/dialog.tsx +158 -0
- package/src/ui/drawer.tsx +134 -0
- package/src/ui/dropdown-menu.tsx +256 -0
- package/src/ui/empty.tsx +103 -0
- package/src/ui/field.tsx +248 -0
- package/src/ui/input-group.tsx +170 -0
- package/src/ui/input-otp.tsx +76 -0
- package/src/ui/input.tsx +20 -0
- package/src/ui/label.tsx +23 -0
- package/src/ui/navigation-menu.tsx +167 -0
- package/src/ui/pagination.tsx +127 -0
- package/src/ui/progress.tsx +30 -0
- package/src/ui/radio-group.tsx +44 -0
- package/src/ui/select.tsx +189 -0
- package/src/ui/separator.tsx +27 -0
- package/src/ui/sheet.tsx +142 -0
- package/src/ui/sidebar.tsx +726 -0
- package/src/ui/skeleton.tsx +13 -0
- package/src/ui/table.tsx +115 -0
- package/src/ui/tabs.tsx +90 -0
- package/src/ui/textarea.tsx +17 -0
- package/src/ui/toggle-group.tsx +83 -0
- package/src/ui/toggle.tsx +46 -0
- package/src/ui/tooltip.tsx +56 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +16 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { ChevronLeft, ChevronRight, Lock } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import type { PaymentMethod } from '@thorprovider/types'
|
|
7
|
+
|
|
8
|
+
import { Button } from '../../../ui/button'
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
|
|
10
|
+
|
|
11
|
+
import { CheckoutForm3InformationStep } from './checkout-form-3-information-step'
|
|
12
|
+
import { CheckoutForm3OrderSummary } from './checkout-form-3-order-summary'
|
|
13
|
+
import { CheckoutForm3PaymentStep } from './checkout-form-3-payment-step'
|
|
14
|
+
import { CheckoutForm3Progress, CHECKOUT_FORM_3_STEP_ICONS } from './checkout-form-3-progress'
|
|
15
|
+
import { CheckoutForm3ReviewStep } from './checkout-form-3-review-step'
|
|
16
|
+
import { CheckoutForm3ShippingStep } from './checkout-form-3-shipping-step'
|
|
17
|
+
|
|
18
|
+
export interface CheckoutForm3CartItem {
|
|
19
|
+
id: number
|
|
20
|
+
name: string
|
|
21
|
+
price: number
|
|
22
|
+
quantity: number
|
|
23
|
+
image: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CheckoutForm3ShippingMethod {
|
|
27
|
+
id: string
|
|
28
|
+
name: string
|
|
29
|
+
description: string
|
|
30
|
+
cost: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CheckoutForm3CountryOption {
|
|
34
|
+
value: string
|
|
35
|
+
label: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CheckoutForm3Step {
|
|
39
|
+
id: number
|
|
40
|
+
name: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CheckoutForm3FormData {
|
|
44
|
+
email: string
|
|
45
|
+
firstName: string
|
|
46
|
+
lastName: string
|
|
47
|
+
phone: string
|
|
48
|
+
address: string
|
|
49
|
+
apartment: string
|
|
50
|
+
city: string
|
|
51
|
+
state: string
|
|
52
|
+
zipCode: string
|
|
53
|
+
country: string
|
|
54
|
+
shippingMethod: string
|
|
55
|
+
paymentMethod: string
|
|
56
|
+
cardNumber: string
|
|
57
|
+
expiryDate: string
|
|
58
|
+
cvv: string
|
|
59
|
+
cardName: string
|
|
60
|
+
billingAddress: string
|
|
61
|
+
newsletter: boolean
|
|
62
|
+
saveInfo: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type CheckoutForm3ChangeHandler = (
|
|
66
|
+
field: keyof CheckoutForm3FormData,
|
|
67
|
+
value: string | boolean
|
|
68
|
+
) => void
|
|
69
|
+
|
|
70
|
+
export interface CheckoutForm3Props {
|
|
71
|
+
cartItems: CheckoutForm3CartItem[]
|
|
72
|
+
shippingMethods: CheckoutForm3ShippingMethod[]
|
|
73
|
+
paymentMethods?: PaymentMethod[]
|
|
74
|
+
taxRate?: number
|
|
75
|
+
countryOptions?: CheckoutForm3CountryOption[]
|
|
76
|
+
steps?: CheckoutForm3Step[]
|
|
77
|
+
currencySymbol?: string
|
|
78
|
+
titleLabel?: string
|
|
79
|
+
subtitleLabel?: string
|
|
80
|
+
backLabel?: string
|
|
81
|
+
continueLabel?: string
|
|
82
|
+
placeOrderLabel?: string
|
|
83
|
+
shippingAddressLabel?: string
|
|
84
|
+
shippingMethodLabel?: string
|
|
85
|
+
paymentLabel?: string
|
|
86
|
+
noMethodsLabel?: string
|
|
87
|
+
securePaymentLabel?: string
|
|
88
|
+
emailLabel?: string
|
|
89
|
+
firstNameLabel?: string
|
|
90
|
+
lastNameLabel?: string
|
|
91
|
+
phoneLabel?: string
|
|
92
|
+
addressLabel?: string
|
|
93
|
+
apartmentLabel?: string
|
|
94
|
+
cityLabel?: string
|
|
95
|
+
stateLabel?: string
|
|
96
|
+
zipCodeLabel?: string
|
|
97
|
+
countryLabel?: string
|
|
98
|
+
cardNumberLabel?: string
|
|
99
|
+
expiryLabel?: string
|
|
100
|
+
cvvLabel?: string
|
|
101
|
+
cardNameLabel?: string
|
|
102
|
+
newsletterLabel?: string
|
|
103
|
+
saveInfoLabel?: string
|
|
104
|
+
orderSummaryLabel?: string
|
|
105
|
+
subtotalLabel?: string
|
|
106
|
+
shippingLabel?: string
|
|
107
|
+
taxLabel?: string
|
|
108
|
+
totalLabel?: string
|
|
109
|
+
freeLabel?: string
|
|
110
|
+
orderReviewLabel?: string
|
|
111
|
+
contactReviewLabel?: string
|
|
112
|
+
shipToReviewLabel?: string
|
|
113
|
+
paymentReviewLabel?: string
|
|
114
|
+
shippingReviewLabel?: string
|
|
115
|
+
onComplete?: (data: CheckoutForm3FormData) => void
|
|
116
|
+
onNextStep?: (step: number) => void
|
|
117
|
+
onPreviousStep?: (step: number) => void
|
|
118
|
+
onFieldChange?: CheckoutForm3ChangeHandler
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const DEFAULT_COUNTRY_OPTIONS: CheckoutForm3CountryOption[] = [
|
|
122
|
+
{ value: 'US', label: 'Estados Unidos' },
|
|
123
|
+
{ value: 'CA', label: 'Canadá' },
|
|
124
|
+
{ value: 'UK', label: 'Reino Unido' },
|
|
125
|
+
{ value: 'AU', label: 'Australia' },
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
const DEFAULT_STEPS: CheckoutForm3Step[] = [
|
|
129
|
+
{ id: 1, name: 'Información' },
|
|
130
|
+
{ id: 2, name: 'Envío' },
|
|
131
|
+
{ id: 3, name: 'Pago' },
|
|
132
|
+
{ id: 4, name: 'Revisión' },
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
export default function CheckoutForm3({
|
|
136
|
+
cartItems,
|
|
137
|
+
shippingMethods,
|
|
138
|
+
paymentMethods = [],
|
|
139
|
+
taxRate = 0,
|
|
140
|
+
countryOptions = DEFAULT_COUNTRY_OPTIONS,
|
|
141
|
+
steps = DEFAULT_STEPS,
|
|
142
|
+
currencySymbol = '$',
|
|
143
|
+
titleLabel = 'Checkout seguro',
|
|
144
|
+
subtitleLabel,
|
|
145
|
+
backLabel = 'Anterior',
|
|
146
|
+
continueLabel = 'Continuar',
|
|
147
|
+
placeOrderLabel = 'Realizar pedido',
|
|
148
|
+
shippingAddressLabel = 'Dirección de envío',
|
|
149
|
+
shippingMethodLabel = 'Método de envío',
|
|
150
|
+
paymentLabel = 'Método de pago',
|
|
151
|
+
noMethodsLabel = 'No hay métodos de pago disponibles para esta región.',
|
|
152
|
+
securePaymentLabel = 'Tu información de pago está cifrada y es segura',
|
|
153
|
+
emailLabel = 'Correo electrónico',
|
|
154
|
+
firstNameLabel = 'Nombre',
|
|
155
|
+
lastNameLabel = 'Apellidos',
|
|
156
|
+
phoneLabel = 'Teléfono',
|
|
157
|
+
addressLabel = 'Dirección',
|
|
158
|
+
apartmentLabel = 'Apartamento, suite, etc.',
|
|
159
|
+
cityLabel = 'Ciudad',
|
|
160
|
+
stateLabel = 'Estado/Provincia',
|
|
161
|
+
zipCodeLabel = 'Código postal',
|
|
162
|
+
countryLabel = 'País',
|
|
163
|
+
cardNumberLabel = 'Número de tarjeta',
|
|
164
|
+
expiryLabel = 'Fecha de vencimiento',
|
|
165
|
+
cvvLabel = 'CVV',
|
|
166
|
+
cardNameLabel = 'Nombre en la tarjeta',
|
|
167
|
+
newsletterLabel = 'Quiero recibir novedades y ofertas',
|
|
168
|
+
saveInfoLabel = 'Guardar esta información para la próxima vez',
|
|
169
|
+
orderSummaryLabel = 'Resumen del pedido',
|
|
170
|
+
subtotalLabel = 'Subtotal',
|
|
171
|
+
shippingLabel = 'Envío',
|
|
172
|
+
taxLabel = 'Impuestos',
|
|
173
|
+
totalLabel = 'Total',
|
|
174
|
+
freeLabel = 'Gratis',
|
|
175
|
+
orderReviewLabel = 'Revisión del pedido',
|
|
176
|
+
contactReviewLabel = 'Contacto',
|
|
177
|
+
shipToReviewLabel = 'Enviar a',
|
|
178
|
+
paymentReviewLabel = 'Pago',
|
|
179
|
+
shippingReviewLabel = 'Envío',
|
|
180
|
+
onComplete,
|
|
181
|
+
onNextStep,
|
|
182
|
+
onPreviousStep,
|
|
183
|
+
onFieldChange,
|
|
184
|
+
}: CheckoutForm3Props) {
|
|
185
|
+
const [currentStep, setCurrentStep] = useState(1)
|
|
186
|
+
const [formData, setFormData] = useState<CheckoutForm3FormData>(() => ({
|
|
187
|
+
email: '',
|
|
188
|
+
firstName: '',
|
|
189
|
+
lastName: '',
|
|
190
|
+
phone: '',
|
|
191
|
+
address: '',
|
|
192
|
+
apartment: '',
|
|
193
|
+
city: '',
|
|
194
|
+
state: '',
|
|
195
|
+
zipCode: '',
|
|
196
|
+
country: countryOptions[0]?.value ?? '',
|
|
197
|
+
shippingMethod: shippingMethods[0]?.id ?? '',
|
|
198
|
+
paymentMethod: paymentMethods[0]?.id ?? '',
|
|
199
|
+
cardNumber: '',
|
|
200
|
+
expiryDate: '',
|
|
201
|
+
cvv: '',
|
|
202
|
+
cardName: '',
|
|
203
|
+
billingAddress: 'same',
|
|
204
|
+
newsletter: false,
|
|
205
|
+
saveInfo: false,
|
|
206
|
+
}))
|
|
207
|
+
|
|
208
|
+
const subtotal = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
209
|
+
const shippingCosts = Object.fromEntries(
|
|
210
|
+
shippingMethods.map(method => [method.id, method.cost]),
|
|
211
|
+
) as Record<string, number>
|
|
212
|
+
const shipping = shippingCosts[formData.shippingMethod] ?? 0
|
|
213
|
+
const tax = subtotal * taxRate
|
|
214
|
+
const total = subtotal + shipping + tax
|
|
215
|
+
|
|
216
|
+
const handleInputChange: CheckoutForm3ChangeHandler = (field, value) => {
|
|
217
|
+
setFormData(prev => ({ ...prev, [field]: value }))
|
|
218
|
+
onFieldChange?.(field, value)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const handleNextStep = () => {
|
|
222
|
+
const next = Math.min(currentStep + 1, steps.length)
|
|
223
|
+
setCurrentStep(next)
|
|
224
|
+
onNextStep?.(next)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const handlePreviousStep = () => {
|
|
228
|
+
const next = Math.max(currentStep - 1, 1)
|
|
229
|
+
setCurrentStep(next)
|
|
230
|
+
onPreviousStep?.(next)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Continue stays disabled until the fields this step is responsible for are filled in. */
|
|
234
|
+
const isStepComplete = (stepNumber: number) => {
|
|
235
|
+
if (stepNumber < currentStep) return true
|
|
236
|
+
if (stepNumber === 1) return Boolean(formData.email && formData.firstName && formData.lastName)
|
|
237
|
+
if (stepNumber === 2) return Boolean(formData.address && formData.city && formData.zipCode)
|
|
238
|
+
if (stepNumber === 3) {
|
|
239
|
+
const selectedMethod = paymentMethods.find(method => method.id === formData.paymentMethod)
|
|
240
|
+
if (!formData.paymentMethod) return false
|
|
241
|
+
return selectedMethod?.type === 'card' ? Boolean(formData.cardNumber) : true
|
|
242
|
+
}
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const CurrentStepIcon = CHECKOUT_FORM_3_STEP_ICONS[currentStep - 1]
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<section className='py-8'>
|
|
250
|
+
<div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
|
|
251
|
+
<div className='mb-8'>
|
|
252
|
+
<h1 className='text-3xl font-bold tracking-tight text-balance'>{titleLabel}</h1>
|
|
253
|
+
<p className='text-muted-foreground mt-2'>
|
|
254
|
+
{subtitleLabel ?? `Completa tu compra en ${steps.length} sencillos pasos`}
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div className='grid gap-8 lg:grid-cols-3'>
|
|
259
|
+
<div className='lg:col-span-2'>
|
|
260
|
+
<CheckoutForm3Progress
|
|
261
|
+
steps={steps}
|
|
262
|
+
currentStep={currentStep}
|
|
263
|
+
isStepComplete={isStepComplete}
|
|
264
|
+
/>
|
|
265
|
+
|
|
266
|
+
<Card>
|
|
267
|
+
<CardHeader>
|
|
268
|
+
<CardTitle className='flex items-center gap-2'>
|
|
269
|
+
{CurrentStepIcon ? <CurrentStepIcon className='size-5' aria-hidden='true' /> : null}
|
|
270
|
+
{steps[currentStep - 1].name}
|
|
271
|
+
</CardTitle>
|
|
272
|
+
</CardHeader>
|
|
273
|
+
<CardContent className='flex flex-col gap-6'>
|
|
274
|
+
{currentStep === 1 ? (
|
|
275
|
+
<CheckoutForm3InformationStep
|
|
276
|
+
formData={formData}
|
|
277
|
+
onChange={handleInputChange}
|
|
278
|
+
emailLabel={emailLabel}
|
|
279
|
+
firstNameLabel={firstNameLabel}
|
|
280
|
+
lastNameLabel={lastNameLabel}
|
|
281
|
+
phoneLabel={phoneLabel}
|
|
282
|
+
newsletterLabel={newsletterLabel}
|
|
283
|
+
/>
|
|
284
|
+
) : null}
|
|
285
|
+
|
|
286
|
+
{currentStep === 2 ? (
|
|
287
|
+
<CheckoutForm3ShippingStep
|
|
288
|
+
formData={formData}
|
|
289
|
+
onChange={handleInputChange}
|
|
290
|
+
countryOptions={countryOptions}
|
|
291
|
+
shippingMethods={shippingMethods}
|
|
292
|
+
shippingAddressLabel={shippingAddressLabel}
|
|
293
|
+
shippingMethodLabel={shippingMethodLabel}
|
|
294
|
+
addressLabel={addressLabel}
|
|
295
|
+
apartmentLabel={apartmentLabel}
|
|
296
|
+
cityLabel={cityLabel}
|
|
297
|
+
stateLabel={stateLabel}
|
|
298
|
+
zipCodeLabel={zipCodeLabel}
|
|
299
|
+
countryLabel={countryLabel}
|
|
300
|
+
freeLabel={freeLabel}
|
|
301
|
+
currencySymbol={currencySymbol}
|
|
302
|
+
/>
|
|
303
|
+
) : null}
|
|
304
|
+
|
|
305
|
+
{currentStep === 3 ? (
|
|
306
|
+
<CheckoutForm3PaymentStep
|
|
307
|
+
formData={formData}
|
|
308
|
+
onChange={handleInputChange}
|
|
309
|
+
paymentMethods={paymentMethods}
|
|
310
|
+
paymentLabel={paymentLabel}
|
|
311
|
+
noMethodsLabel={noMethodsLabel}
|
|
312
|
+
cardNumberLabel={cardNumberLabel}
|
|
313
|
+
expiryLabel={expiryLabel}
|
|
314
|
+
cvvLabel={cvvLabel}
|
|
315
|
+
cardNameLabel={cardNameLabel}
|
|
316
|
+
securePaymentLabel={securePaymentLabel}
|
|
317
|
+
/>
|
|
318
|
+
) : null}
|
|
319
|
+
|
|
320
|
+
{currentStep === 4 ? (
|
|
321
|
+
<CheckoutForm3ReviewStep
|
|
322
|
+
formData={formData}
|
|
323
|
+
onChange={handleInputChange}
|
|
324
|
+
shippingMethods={shippingMethods}
|
|
325
|
+
paymentMethods={paymentMethods}
|
|
326
|
+
orderReviewLabel={orderReviewLabel}
|
|
327
|
+
contactReviewLabel={contactReviewLabel}
|
|
328
|
+
shipToReviewLabel={shipToReviewLabel}
|
|
329
|
+
paymentReviewLabel={paymentReviewLabel}
|
|
330
|
+
shippingReviewLabel={shippingReviewLabel}
|
|
331
|
+
saveInfoLabel={saveInfoLabel}
|
|
332
|
+
/>
|
|
333
|
+
) : null}
|
|
334
|
+
|
|
335
|
+
<div className='flex justify-between pt-6'>
|
|
336
|
+
<Button
|
|
337
|
+
type='button'
|
|
338
|
+
variant='outline'
|
|
339
|
+
className='h-9 px-4 py-2 cursor-pointer'
|
|
340
|
+
onClick={handlePreviousStep}
|
|
341
|
+
disabled={currentStep === 1}
|
|
342
|
+
>
|
|
343
|
+
<ChevronLeft data-icon='inline-start' />
|
|
344
|
+
{backLabel}
|
|
345
|
+
</Button>
|
|
346
|
+
|
|
347
|
+
{currentStep < steps.length ? (
|
|
348
|
+
<Button
|
|
349
|
+
type='button'
|
|
350
|
+
onClick={handleNextStep}
|
|
351
|
+
className='h-9 px-4 py-2 cursor-pointer'
|
|
352
|
+
disabled={!isStepComplete(currentStep)}
|
|
353
|
+
>
|
|
354
|
+
{continueLabel}
|
|
355
|
+
<ChevronRight data-icon='inline-end' />
|
|
356
|
+
</Button>
|
|
357
|
+
) : (
|
|
358
|
+
<Button
|
|
359
|
+
type='button'
|
|
360
|
+
className='h-9 px-4 py-2 min-w-32 cursor-pointer'
|
|
361
|
+
onClick={() => onComplete?.(formData)}
|
|
362
|
+
>
|
|
363
|
+
<Lock data-icon='inline-start' />
|
|
364
|
+
{placeOrderLabel}
|
|
365
|
+
</Button>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
</CardContent>
|
|
369
|
+
</Card>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div className='lg:col-span-1'>
|
|
373
|
+
<CheckoutForm3OrderSummary
|
|
374
|
+
items={cartItems}
|
|
375
|
+
subtotal={subtotal}
|
|
376
|
+
shipping={shipping}
|
|
377
|
+
tax={tax}
|
|
378
|
+
total={total}
|
|
379
|
+
currencySymbol={currencySymbol}
|
|
380
|
+
orderSummaryLabel={orderSummaryLabel}
|
|
381
|
+
subtotalLabel={subtotalLabel}
|
|
382
|
+
shippingLabel={shippingLabel}
|
|
383
|
+
taxLabel={taxLabel}
|
|
384
|
+
totalLabel={totalLabel}
|
|
385
|
+
freeLabel={freeLabel}
|
|
386
|
+
/>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</section>
|
|
391
|
+
)
|
|
392
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CategoryIcon,
|
|
6
|
+
categoryIconNameSchema,
|
|
7
|
+
categoryIconPropsSchema,
|
|
8
|
+
type CategoryIconName,
|
|
9
|
+
} from './category-icon'
|
|
10
|
+
|
|
11
|
+
describe('CategoryIcon', () => {
|
|
12
|
+
it('parses a valid props object', () => {
|
|
13
|
+
const result = categoryIconPropsSchema.parse({ name: 'wrench' })
|
|
14
|
+
expect(result.name).toBe('wrench')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('accepts every known icon name', () => {
|
|
18
|
+
const names = categoryIconNameSchema.options
|
|
19
|
+
expect(names).toHaveLength(18)
|
|
20
|
+
for (const name of names) {
|
|
21
|
+
expect(() => categoryIconPropsSchema.parse({ name })).not.toThrow()
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('rejects an unknown name at the schema boundary', () => {
|
|
26
|
+
expect(() => categoryIconNameSchema.parse('nope')).toThrow()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('renders an svg with aria-hidden for a known name', () => {
|
|
30
|
+
const html = renderToStaticMarkup(<CategoryIcon name="wrench" />)
|
|
31
|
+
expect(html).toContain('<svg')
|
|
32
|
+
expect(html).toContain('aria-hidden="true"')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders null for an unknown name', () => {
|
|
36
|
+
const html = renderToStaticMarkup(
|
|
37
|
+
<CategoryIcon name={'not-a-category' as CategoryIconName} />,
|
|
38
|
+
)
|
|
39
|
+
expect(html).toBe('')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders null when no name is given', () => {
|
|
43
|
+
const html = renderToStaticMarkup(<CategoryIcon />)
|
|
44
|
+
expect(html).toBe('')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('merges className', () => {
|
|
48
|
+
const html = renderToStaticMarkup(
|
|
49
|
+
<CategoryIcon name="home" className="size-8" />,
|
|
50
|
+
)
|
|
51
|
+
expect(html).toContain('size-8')
|
|
52
|
+
expect(html).not.toContain('size-4')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Bolt,
|
|
3
|
+
Boxes,
|
|
4
|
+
Car,
|
|
5
|
+
ChefHat,
|
|
6
|
+
Droplet,
|
|
7
|
+
HardHat,
|
|
8
|
+
Home,
|
|
9
|
+
Refrigerator,
|
|
10
|
+
Shield,
|
|
11
|
+
Sofa,
|
|
12
|
+
Store,
|
|
13
|
+
Sun,
|
|
14
|
+
Tractor,
|
|
15
|
+
Trees,
|
|
16
|
+
Utensils,
|
|
17
|
+
Wind,
|
|
18
|
+
Wrench,
|
|
19
|
+
Zap,
|
|
20
|
+
type LucideIcon,
|
|
21
|
+
} from 'lucide-react'
|
|
22
|
+
import { z } from 'zod'
|
|
23
|
+
|
|
24
|
+
import { cn } from '../../../lib/utils'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The explicit vocabulary of known category icon names. Kept as a Zod enum so
|
|
28
|
+
* the union type derives from the schema and never drifts.
|
|
29
|
+
*/
|
|
30
|
+
export const categoryIconNameSchema = z.enum([
|
|
31
|
+
'wrench',
|
|
32
|
+
'bolt',
|
|
33
|
+
'tree',
|
|
34
|
+
'refrigerator',
|
|
35
|
+
'wind',
|
|
36
|
+
'droplet',
|
|
37
|
+
'zap',
|
|
38
|
+
'hard-hat',
|
|
39
|
+
'car',
|
|
40
|
+
'utensils',
|
|
41
|
+
'boxes',
|
|
42
|
+
'sofa',
|
|
43
|
+
'shield',
|
|
44
|
+
'sun',
|
|
45
|
+
'chef-hat',
|
|
46
|
+
'tractor',
|
|
47
|
+
'store',
|
|
48
|
+
'home',
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
export type CategoryIconName = z.infer<typeof categoryIconNameSchema>
|
|
52
|
+
|
|
53
|
+
export const categoryIconPropsSchema = z.object({
|
|
54
|
+
name: categoryIconNameSchema.optional(),
|
|
55
|
+
className: z.string().optional(),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export type CategoryIconProps = z.infer<typeof categoryIconPropsSchema>
|
|
59
|
+
|
|
60
|
+
const iconMap: Record<CategoryIconName, LucideIcon> = {
|
|
61
|
+
wrench: Wrench,
|
|
62
|
+
bolt: Bolt,
|
|
63
|
+
tree: Trees,
|
|
64
|
+
refrigerator: Refrigerator,
|
|
65
|
+
wind: Wind,
|
|
66
|
+
droplet: Droplet,
|
|
67
|
+
zap: Zap,
|
|
68
|
+
'hard-hat': HardHat,
|
|
69
|
+
car: Car,
|
|
70
|
+
utensils: Utensils,
|
|
71
|
+
boxes: Boxes,
|
|
72
|
+
sofa: Sofa,
|
|
73
|
+
shield: Shield,
|
|
74
|
+
sun: Sun,
|
|
75
|
+
'chef-hat': ChefHat,
|
|
76
|
+
tractor: Tractor,
|
|
77
|
+
store: Store,
|
|
78
|
+
home: Home,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Category icon by name: maps a string icon-name to a Lucide icon.
|
|
83
|
+
*
|
|
84
|
+
* Framework-agnostic and prop-driven. Unknown or absent names render null
|
|
85
|
+
* (graceful fallback); the schema validates names at the boundary, so a
|
|
86
|
+
* non-vocabulary value fails `parse` but the component still renders null
|
|
87
|
+
* instead of throwing.
|
|
88
|
+
*/
|
|
89
|
+
export function CategoryIcon({ name, className }: CategoryIconProps) {
|
|
90
|
+
if (!name) return null
|
|
91
|
+
const Icon = iconMap[name]
|
|
92
|
+
if (!Icon) return null
|
|
93
|
+
return <Icon className={cn('size-4', className)} aria-hidden="true" />
|
|
94
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import type { CollectionItem, MenuItem } from '../../../lib/types/menu'
|
|
5
|
+
import { MegaMenu, megaMenuPropsSchema } from './mega-menu'
|
|
6
|
+
|
|
7
|
+
const categories: MenuItem[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 'hogar',
|
|
10
|
+
label: 'Hogar',
|
|
11
|
+
icon: 'home',
|
|
12
|
+
href: '/hogar',
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
id: 'hogar-decoracion',
|
|
16
|
+
label: 'Decoración',
|
|
17
|
+
href: '/hogar/decoracion',
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
id: 'hogar-decoracion-cortinas',
|
|
21
|
+
label: 'Cortinas',
|
|
22
|
+
href: '/hogar/decoracion/cortinas',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{ id: 'cocina', label: 'Cocina', icon: 'utensils', href: '/cocina' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const collections: CollectionItem[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'ofertas',
|
|
34
|
+
label: 'Ofertas',
|
|
35
|
+
description: 'Precios bajos',
|
|
36
|
+
image: '/img/ofertas.jpg',
|
|
37
|
+
href: '/colecciones/ofertas',
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
describe('MegaMenu', () => {
|
|
42
|
+
it('parses a valid props object', () => {
|
|
43
|
+
const result = megaMenuPropsSchema.parse({ categories, collections })
|
|
44
|
+
expect(result.categories).toHaveLength(2)
|
|
45
|
+
expect(result.collections).toHaveLength(1)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('rejects missing categories', () => {
|
|
49
|
+
expect(() => megaMenuPropsSchema.parse({ collections })).toThrow()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('rejects missing collections', () => {
|
|
53
|
+
expect(() => megaMenuPropsSchema.parse({ categories })).toThrow()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('renders category columns from the categories prop', () => {
|
|
57
|
+
const html = renderToStaticMarkup(
|
|
58
|
+
<MegaMenu categories={categories} collections={collections} />,
|
|
59
|
+
)
|
|
60
|
+
expect(html).toContain('Comprar por categorías')
|
|
61
|
+
expect(html).toContain('Hogar')
|
|
62
|
+
expect(html).toContain('href="/hogar"')
|
|
63
|
+
expect(html).toContain('Decoración')
|
|
64
|
+
expect(html).toContain('href="/hogar/decoracion"')
|
|
65
|
+
expect(html).toContain('Cortinas')
|
|
66
|
+
expect(html).toContain('href="/hogar/decoracion/cortinas"')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('renders the collections grid', () => {
|
|
70
|
+
const html = renderToStaticMarkup(
|
|
71
|
+
<MegaMenu categories={categories} collections={collections} />,
|
|
72
|
+
)
|
|
73
|
+
expect(html).toContain('Colecciones')
|
|
74
|
+
expect(html).toContain('Ofertas')
|
|
75
|
+
expect(html).toContain('Precios bajos')
|
|
76
|
+
expect(html).toContain('href="/colecciones/ofertas"')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('uses CategoryIcon for icon items', () => {
|
|
80
|
+
const html = renderToStaticMarkup(
|
|
81
|
+
<MegaMenu categories={categories} collections={collections} />,
|
|
82
|
+
)
|
|
83
|
+
expect(html).toContain('<svg')
|
|
84
|
+
expect(html).toContain('aria-hidden="true"')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('renders the view-all link only when collectionsHref is set', () => {
|
|
88
|
+
const withHref = renderToStaticMarkup(
|
|
89
|
+
<MegaMenu
|
|
90
|
+
categories={categories}
|
|
91
|
+
collections={collections}
|
|
92
|
+
collectionsHref="/tienda/colecciones"
|
|
93
|
+
viewAllLabel="Ver todas"
|
|
94
|
+
/>,
|
|
95
|
+
)
|
|
96
|
+
expect(withHref).toContain('href="/tienda/colecciones"')
|
|
97
|
+
expect(withHref).toContain('Ver todas')
|
|
98
|
+
|
|
99
|
+
const withoutHref = renderToStaticMarkup(
|
|
100
|
+
<MegaMenu
|
|
101
|
+
categories={categories}
|
|
102
|
+
collections={collections}
|
|
103
|
+
collectionsHref=""
|
|
104
|
+
/>,
|
|
105
|
+
)
|
|
106
|
+
expect(withoutHref).not.toContain('Ver todas')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('uses ES defaults for copy, hrefs and aria-label', () => {
|
|
110
|
+
const html = renderToStaticMarkup(
|
|
111
|
+
<MegaMenu categories={categories} collections={collections} />,
|
|
112
|
+
)
|
|
113
|
+
expect(html).toContain('Comprar por categorías')
|
|
114
|
+
expect(html).toContain('Colecciones')
|
|
115
|
+
expect(html).toContain('Ver todas las colecciones')
|
|
116
|
+
expect(html).toContain('href="/colecciones"')
|
|
117
|
+
expect(html).toContain('aria-label="Menú de categorías"')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('applies copy overrides', () => {
|
|
121
|
+
const html = renderToStaticMarkup(
|
|
122
|
+
<MegaMenu
|
|
123
|
+
categories={categories}
|
|
124
|
+
collections={collections}
|
|
125
|
+
categoryColumnTitle="Shop by category"
|
|
126
|
+
collectionsTitle="Collections"
|
|
127
|
+
viewAllLabel="View all collections"
|
|
128
|
+
collectionsHref="/collections"
|
|
129
|
+
ariaLabel="Category menu"
|
|
130
|
+
/>,
|
|
131
|
+
)
|
|
132
|
+
expect(html).toContain('Shop by category')
|
|
133
|
+
expect(html).toContain('Collections')
|
|
134
|
+
expect(html).toContain('View all collections')
|
|
135
|
+
expect(html).toContain('href="/collections"')
|
|
136
|
+
expect(html).toContain('aria-label="Category menu"')
|
|
137
|
+
expect(html).not.toContain('Comprar por categorías')
|
|
138
|
+
})
|
|
139
|
+
})
|