@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,320 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { Eye, EyeOff, Lock, Mail } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { Button } from '../../../ui/button'
|
|
7
|
+
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from '../../../ui/card'
|
|
8
|
+
import { Input } from '../../../ui/input'
|
|
9
|
+
import { InputOTP, InputOTPGroup, InputOTPSlot } from '../../../ui/input-otp'
|
|
10
|
+
import { Progress } from '../../../ui/progress'
|
|
11
|
+
|
|
12
|
+
import ForgotPasswordPage1BrandMark from './forgot-password-page-1-brand-mark'
|
|
13
|
+
|
|
14
|
+
export interface ForgotPasswordPage1Props {
|
|
15
|
+
/** Callback fired with the email when the user submits the reset-code step. */
|
|
16
|
+
onSendResetCode?: (email: string) => void
|
|
17
|
+
/** Callback fired with the 6-digit code when the user verifies it. */
|
|
18
|
+
onVerifyCode?: (code: string) => void
|
|
19
|
+
/** Callback fired when the user requests a new code. */
|
|
20
|
+
onResendCode?: () => void
|
|
21
|
+
/** Callback fired with the new password when the user sets it. */
|
|
22
|
+
onSetNewPassword?: (password: string) => void
|
|
23
|
+
/** Callback fired when the user goes back to login from the success step. */
|
|
24
|
+
onBackToLogin?: () => void
|
|
25
|
+
step1TitleLabel?: string
|
|
26
|
+
step1DescriptionLabel?: string
|
|
27
|
+
emailLabel?: string
|
|
28
|
+
emailPlaceholder?: string
|
|
29
|
+
sendResetCodeLabel?: string
|
|
30
|
+
step2TitleLabel?: string
|
|
31
|
+
step2DescriptionLabel?: string
|
|
32
|
+
codeLabel?: string
|
|
33
|
+
verifyCodeLabel?: string
|
|
34
|
+
resendCodeLabel?: string
|
|
35
|
+
resendCodeCooldownLabel?: string
|
|
36
|
+
didNotReceiveLabel?: string
|
|
37
|
+
step3TitleLabel?: string
|
|
38
|
+
step3DescriptionLabel?: string
|
|
39
|
+
newPasswordLabel?: string
|
|
40
|
+
setNewPasswordLabel?: string
|
|
41
|
+
strengthWeakLabel?: string
|
|
42
|
+
strengthMediumLabel?: string
|
|
43
|
+
strengthStrongLabel?: string
|
|
44
|
+
strengthIndicatorLabel?: string
|
|
45
|
+
step4TitleLabel?: string
|
|
46
|
+
step4DescriptionLabel?: string
|
|
47
|
+
backToLoginLabel?: string
|
|
48
|
+
dontHaveAccountLabel?: string
|
|
49
|
+
signUpLabel?: string
|
|
50
|
+
showPasswordLabel?: string
|
|
51
|
+
hidePasswordLabel?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const RESEND_COOLDOWN_SECONDS = 30
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a password strength score from 0 to 100.
|
|
58
|
+
* Adds 25 points per satisfied criterion: length >= 8, contains an
|
|
59
|
+
* uppercase letter, contains a digit, and contains a non-alphanumeric
|
|
60
|
+
* character. Used to gate the "Set new password" step and drive the
|
|
61
|
+
* strength progress bar.
|
|
62
|
+
*/
|
|
63
|
+
function passwordStrength(password: string) {
|
|
64
|
+
let strength = 0
|
|
65
|
+
if (password.length >= 8) strength += 25
|
|
66
|
+
if (/[A-Z]/.test(password)) strength += 25
|
|
67
|
+
if (/[0-9]/.test(password)) strength += 25
|
|
68
|
+
if (/[^A-Za-z0-9]/.test(password)) strength += 25
|
|
69
|
+
return strength
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ForgotPasswordPage1({
|
|
73
|
+
onSendResetCode,
|
|
74
|
+
onVerifyCode,
|
|
75
|
+
onResendCode,
|
|
76
|
+
onSetNewPassword,
|
|
77
|
+
onBackToLogin,
|
|
78
|
+
step1TitleLabel = '¿Olvidaste tu contraseña?',
|
|
79
|
+
step1DescriptionLabel = 'Ingresa tu correo para restablecer tu contraseña',
|
|
80
|
+
emailLabel = 'Correo electrónico',
|
|
81
|
+
emailPlaceholder = 'tucorreo@ejemplo.com',
|
|
82
|
+
sendResetCodeLabel = 'Enviar código de restablecimiento',
|
|
83
|
+
step2TitleLabel = 'Revisa tu correo',
|
|
84
|
+
step2DescriptionLabel = 'Ingresa el código de 6 dígitos enviado a {email}',
|
|
85
|
+
codeLabel = 'Código de verificación',
|
|
86
|
+
verifyCodeLabel = 'Verificar código',
|
|
87
|
+
resendCodeLabel = 'Reenviar código',
|
|
88
|
+
resendCodeCooldownLabel = 'Reenviar en {n}s',
|
|
89
|
+
didNotReceiveLabel = '¿No lo recibiste?',
|
|
90
|
+
step3TitleLabel = 'Crea una nueva contraseña',
|
|
91
|
+
step3DescriptionLabel = 'Elige una contraseña segura para proteger tu cuenta',
|
|
92
|
+
newPasswordLabel = 'Nueva contraseña',
|
|
93
|
+
setNewPasswordLabel = 'Establecer nueva contraseña',
|
|
94
|
+
strengthWeakLabel = 'Débil',
|
|
95
|
+
strengthMediumLabel = 'Media',
|
|
96
|
+
strengthStrongLabel = 'Fuerte',
|
|
97
|
+
strengthIndicatorLabel = 'Indicador de fuerza de contraseña',
|
|
98
|
+
step4TitleLabel = '¡Felicitaciones!',
|
|
99
|
+
step4DescriptionLabel = 'Restableciste tu contraseña correctamente',
|
|
100
|
+
backToLoginLabel = 'Volver al inicio de sesión',
|
|
101
|
+
dontHaveAccountLabel = '¿Aún no tienes una cuenta?',
|
|
102
|
+
signUpLabel = 'Regístrate',
|
|
103
|
+
showPasswordLabel = 'Mostrar contraseña',
|
|
104
|
+
hidePasswordLabel = 'Ocultar contraseña',
|
|
105
|
+
}: ForgotPasswordPage1Props) {
|
|
106
|
+
const [step, setStep] = useState(1)
|
|
107
|
+
const [email, setEmail] = useState('')
|
|
108
|
+
const [code, setCode] = useState('')
|
|
109
|
+
const [password, setPassword] = useState('')
|
|
110
|
+
const [showPassword, setShowPassword] = useState(false)
|
|
111
|
+
const [resendCooldown, setResendCooldown] = useState(0)
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (resendCooldown <= 0) return
|
|
115
|
+
const timer = setInterval(() => {
|
|
116
|
+
setResendCooldown(prev => (prev <= 1 ? 0 : prev - 1))
|
|
117
|
+
}, 1000)
|
|
118
|
+
return () => clearInterval(timer)
|
|
119
|
+
}, [resendCooldown])
|
|
120
|
+
|
|
121
|
+
const handleResend = () => {
|
|
122
|
+
onResendCode?.()
|
|
123
|
+
setResendCooldown(RESEND_COOLDOWN_SECONDS)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const renderStepContent = () => {
|
|
127
|
+
switch (step) {
|
|
128
|
+
case 1:
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<CardHeader className="pt-0 text-center">
|
|
132
|
+
<CardTitle className="text-2xl">{step1TitleLabel}</CardTitle>
|
|
133
|
+
<CardDescription>{step1DescriptionLabel}</CardDescription>
|
|
134
|
+
</CardHeader>
|
|
135
|
+
|
|
136
|
+
<div className="flex flex-col gap-6">
|
|
137
|
+
<div className="relative">
|
|
138
|
+
<Input
|
|
139
|
+
id="email-forgot-password-page-1"
|
|
140
|
+
type="email"
|
|
141
|
+
placeholder={emailPlaceholder}
|
|
142
|
+
className="bg-transparent ps-10 h-9 text-sm"
|
|
143
|
+
autoComplete="email"
|
|
144
|
+
aria-label={emailLabel}
|
|
145
|
+
value={email}
|
|
146
|
+
onChange={e => setEmail(e.target.value)}
|
|
147
|
+
required
|
|
148
|
+
/>
|
|
149
|
+
<Mail className="text-muted-foreground absolute start-3 top-1/2 size-5 -translate-y-1/2" />
|
|
150
|
+
</div>
|
|
151
|
+
<Button
|
|
152
|
+
type="button"
|
|
153
|
+
className="h-9 px-4 py-2 w-full cursor-pointer"
|
|
154
|
+
onClick={() => {
|
|
155
|
+
onSendResetCode?.(email)
|
|
156
|
+
setStep(2)
|
|
157
|
+
}}
|
|
158
|
+
disabled={!email}
|
|
159
|
+
>
|
|
160
|
+
{sendResetCodeLabel}
|
|
161
|
+
</Button>
|
|
162
|
+
<p className="mt-6 flex justify-center gap-1 text-center text-sm">
|
|
163
|
+
<span>{dontHaveAccountLabel}</span>
|
|
164
|
+
<a href="#" className="underline underline-offset-4">
|
|
165
|
+
{signUpLabel}
|
|
166
|
+
</a>
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
</>
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
case 2:
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
<div className="mb-6 text-center">
|
|
176
|
+
<h1 className="mb-2 text-2xl font-bold tracking-tight text-balance">{step2TitleLabel}</h1>
|
|
177
|
+
<p className="text-muted-foreground text-sm text-balance">
|
|
178
|
+
{step2DescriptionLabel.replace('{email}', email || 'tu correo')}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="flex flex-col gap-6">
|
|
183
|
+
<InputOTP
|
|
184
|
+
maxLength={6}
|
|
185
|
+
value={code}
|
|
186
|
+
onChange={value => setCode(value)}
|
|
187
|
+
aria-label={codeLabel}
|
|
188
|
+
>
|
|
189
|
+
<InputOTPGroup className="grid w-full grid-cols-6 gap-2 sm:gap-3">
|
|
190
|
+
<InputOTPSlot index={0} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
191
|
+
<InputOTPSlot index={1} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
192
|
+
<InputOTPSlot index={2} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
193
|
+
<InputOTPSlot index={3} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
194
|
+
<InputOTPSlot index={4} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
195
|
+
<InputOTPSlot index={5} className="h-12 w-auto flex-1 rounded-md border-l text-lg" />
|
|
196
|
+
</InputOTPGroup>
|
|
197
|
+
</InputOTP>
|
|
198
|
+
<Button
|
|
199
|
+
type="button"
|
|
200
|
+
className="h-9 px-4 py-2 w-full cursor-pointer"
|
|
201
|
+
onClick={() => {
|
|
202
|
+
onVerifyCode?.(code)
|
|
203
|
+
setStep(3)
|
|
204
|
+
}}
|
|
205
|
+
disabled={code.length < 6}
|
|
206
|
+
>
|
|
207
|
+
{verifyCodeLabel}
|
|
208
|
+
</Button>
|
|
209
|
+
<p className="text-muted-foreground text-center text-sm">
|
|
210
|
+
{didNotReceiveLabel}{' '}
|
|
211
|
+
<Button
|
|
212
|
+
variant="link"
|
|
213
|
+
className="cursor-pointer px-1 font-medium underline underline-offset-4 hover:underline"
|
|
214
|
+
onClick={handleResend}
|
|
215
|
+
disabled={resendCooldown > 0}
|
|
216
|
+
>
|
|
217
|
+
{resendCooldown > 0
|
|
218
|
+
? resendCodeCooldownLabel.replace('{n}', String(resendCooldown))
|
|
219
|
+
: resendCodeLabel}
|
|
220
|
+
</Button>
|
|
221
|
+
</p>
|
|
222
|
+
</div>
|
|
223
|
+
</>
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
case 3: {
|
|
227
|
+
const strength = passwordStrength(password)
|
|
228
|
+
const strengthLabel =
|
|
229
|
+
strength === 0 ? '' : strength < 50 ? strengthWeakLabel : strength < 75 ? strengthMediumLabel : strengthStrongLabel
|
|
230
|
+
return (
|
|
231
|
+
<>
|
|
232
|
+
<div className="mb-6 text-center">
|
|
233
|
+
<h1 className="mb-2 text-2xl font-bold tracking-tight text-balance">{step3TitleLabel}</h1>
|
|
234
|
+
<p className="text-muted-foreground text-sm text-balance">{step3DescriptionLabel}</p>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div className="flex flex-col gap-6">
|
|
238
|
+
<div className="relative">
|
|
239
|
+
<Input
|
|
240
|
+
id="new-password-forgot-password-page-1"
|
|
241
|
+
type={showPassword ? 'text' : 'password'}
|
|
242
|
+
placeholder={newPasswordLabel}
|
|
243
|
+
className="bg-transparent ps-10 text-sm"
|
|
244
|
+
autoComplete="new-password"
|
|
245
|
+
aria-label={newPasswordLabel}
|
|
246
|
+
value={password}
|
|
247
|
+
onChange={e => setPassword(e.target.value)}
|
|
248
|
+
required
|
|
249
|
+
/>
|
|
250
|
+
<Lock className="text-muted-foreground absolute start-3 top-1/2 size-5 -translate-y-1/2" />
|
|
251
|
+
<Button
|
|
252
|
+
type="button"
|
|
253
|
+
variant="ghost"
|
|
254
|
+
size="icon"
|
|
255
|
+
className="size-9 text-muted-foreground absolute end-1 top-1/2 -translate-y-1/2 cursor-pointer hover:bg-transparent"
|
|
256
|
+
onClick={() => setShowPassword(prev => !prev)}
|
|
257
|
+
aria-label={showPassword ? hidePasswordLabel : showPasswordLabel}
|
|
258
|
+
>
|
|
259
|
+
{showPassword ? <EyeOff /> : <Eye />}
|
|
260
|
+
</Button>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="flex flex-col gap-1">
|
|
263
|
+
<Progress value={strength} className="h-2" aria-label={strengthIndicatorLabel} />
|
|
264
|
+
<small className="text-muted-foreground block text-end text-xs">{strengthLabel}</small>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<Button
|
|
268
|
+
type="button"
|
|
269
|
+
className="h-9 px-4 py-2 w-full cursor-pointer"
|
|
270
|
+
onClick={() => {
|
|
271
|
+
onSetNewPassword?.(password)
|
|
272
|
+
setStep(4)
|
|
273
|
+
}}
|
|
274
|
+
disabled={strength < 75}
|
|
275
|
+
>
|
|
276
|
+
{setNewPasswordLabel}
|
|
277
|
+
</Button>
|
|
278
|
+
</div>
|
|
279
|
+
</>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
case 4:
|
|
284
|
+
return (
|
|
285
|
+
<>
|
|
286
|
+
<CardHeader className="mb-4 pt-0 text-center">
|
|
287
|
+
<CardTitle className="text-2xl">{step4TitleLabel}</CardTitle>
|
|
288
|
+
<CardDescription>{step4DescriptionLabel}</CardDescription>
|
|
289
|
+
</CardHeader>
|
|
290
|
+
<CardFooter>
|
|
291
|
+
<Button
|
|
292
|
+
className="h-9 px-4 py-2 w-full cursor-pointer"
|
|
293
|
+
onClick={() => {
|
|
294
|
+
onBackToLogin?.()
|
|
295
|
+
setStep(1)
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
{backToLoginLabel}
|
|
299
|
+
</Button>
|
|
300
|
+
</CardFooter>
|
|
301
|
+
</>
|
|
302
|
+
)
|
|
303
|
+
default:
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<section className="from-background to-muted/50 relative isolate flex min-h-dvh w-full items-center justify-center overflow-hidden bg-linear-to-br">
|
|
310
|
+
<div className="relative z-10 container mx-auto flex min-h-dvh items-center justify-center px-4 py-12 sm:py-16">
|
|
311
|
+
<Card className="bg-background/80 relative w-full max-w-md ring-0 p-6 shadow-2xl backdrop-blur-md sm:p-8">
|
|
312
|
+
<ForgotPasswordPage1BrandMark />
|
|
313
|
+
{renderStepContent()}
|
|
314
|
+
</Card>
|
|
315
|
+
</div>
|
|
316
|
+
</section>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export default ForgotPasswordPage1
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { FormEvent } from 'react'
|
|
2
|
+
import { Lock, Mail, User, type LucideIcon } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../../ui/button'
|
|
5
|
+
import { Card } from '../../../ui/card'
|
|
6
|
+
import { Input } from '../../../ui/input'
|
|
7
|
+
|
|
8
|
+
export interface SignUpPage1SocialButton {
|
|
9
|
+
name: string
|
|
10
|
+
icon: LucideIcon
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SignUpPage1Data {
|
|
14
|
+
fullName: string
|
|
15
|
+
email: string
|
|
16
|
+
password: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SignUpPage1Props {
|
|
20
|
+
/** Brand text shown under the logo mark. Omit to hide it. */
|
|
21
|
+
logoText?: string
|
|
22
|
+
createAccountTitleLabel?: string
|
|
23
|
+
createAccountDescriptionLabel?: string
|
|
24
|
+
fullNameLabel?: string
|
|
25
|
+
fullNamePlaceholder?: string
|
|
26
|
+
emailLabel?: string
|
|
27
|
+
emailPlaceholder?: string
|
|
28
|
+
passwordLabel?: string
|
|
29
|
+
passwordPlaceholder?: string
|
|
30
|
+
createAccountLabel?: string
|
|
31
|
+
orSignUpWithLabel?: string
|
|
32
|
+
/** aria-label template for each social button. Use `{name}` as the button name placeholder. */
|
|
33
|
+
socialAriaLabel?: string
|
|
34
|
+
/** Social sign-up buttons rendered in a 3-column grid. Pass an empty array to hide. */
|
|
35
|
+
socialButtons?: SignUpPage1SocialButton[]
|
|
36
|
+
alreadyHaveAccountLabel?: string
|
|
37
|
+
signInLabel?: string
|
|
38
|
+
termsLabel?: string
|
|
39
|
+
termsLinkLabel?: string
|
|
40
|
+
termsHref?: string
|
|
41
|
+
termsAndLabel?: string
|
|
42
|
+
privacyLinkLabel?: string
|
|
43
|
+
privacyHref?: string
|
|
44
|
+
onSubmit?: (data: SignUpPage1Data) => void
|
|
45
|
+
onSignIn?: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function SignUpPage1({
|
|
49
|
+
logoText,
|
|
50
|
+
createAccountTitleLabel = 'Crea tu cuenta',
|
|
51
|
+
createAccountDescriptionLabel = 'Completa el formulario para comenzar',
|
|
52
|
+
fullNameLabel = 'Nombre completo',
|
|
53
|
+
fullNamePlaceholder = 'Nombre completo',
|
|
54
|
+
emailLabel = 'Correo electrónico',
|
|
55
|
+
emailPlaceholder = 'me@ejemplo.com',
|
|
56
|
+
passwordLabel = 'Contraseña',
|
|
57
|
+
passwordPlaceholder = 'Crea una contraseña',
|
|
58
|
+
createAccountLabel = 'Crear cuenta',
|
|
59
|
+
orSignUpWithLabel = 'O regístrate con',
|
|
60
|
+
socialAriaLabel = 'Continuar con {name}',
|
|
61
|
+
socialButtons = [],
|
|
62
|
+
alreadyHaveAccountLabel = '¿Ya tienes una cuenta?',
|
|
63
|
+
signInLabel = 'Inicia sesión',
|
|
64
|
+
termsLabel = 'Al crear una cuenta, aceptas los',
|
|
65
|
+
termsLinkLabel = 'Términos de servicio',
|
|
66
|
+
termsHref,
|
|
67
|
+
termsAndLabel = 'y',
|
|
68
|
+
privacyLinkLabel = 'Política de privacidad',
|
|
69
|
+
privacyHref,
|
|
70
|
+
onSubmit,
|
|
71
|
+
onSignIn,
|
|
72
|
+
}: SignUpPage1Props) {
|
|
73
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
74
|
+
event.preventDefault()
|
|
75
|
+
const formData = new FormData(event.currentTarget)
|
|
76
|
+
onSubmit?.({
|
|
77
|
+
fullName: String(formData.get('fullName') ?? ''),
|
|
78
|
+
email: String(formData.get('email') ?? ''),
|
|
79
|
+
password: String(formData.get('password') ?? ''),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<section className='from-background to-muted/50 relative isolate flex min-h-dvh w-full flex-col items-center justify-center overflow-hidden bg-linear-to-br'>
|
|
85
|
+
<div className='relative z-10 container mx-auto flex min-h-dvh flex-col items-center justify-center px-4 py-12'>
|
|
86
|
+
<Card className='relative w-full max-w-lg p-8 shadow-2xl ring-0'>
|
|
87
|
+
<div className='mb-8 flex flex-col items-center'>
|
|
88
|
+
<div className='my-4 flex justify-center'>
|
|
89
|
+
<div className='bg-secondary relative size-14 rounded-full border'>
|
|
90
|
+
<div className='flex h-full items-center justify-center'>
|
|
91
|
+
<svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
|
92
|
+
<path
|
|
93
|
+
d='M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z'
|
|
94
|
+
fill='currentColor'
|
|
95
|
+
/>
|
|
96
|
+
<path
|
|
97
|
+
d='M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z'
|
|
98
|
+
fill='currentColor'
|
|
99
|
+
/>
|
|
100
|
+
<path
|
|
101
|
+
d='M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z'
|
|
102
|
+
fill='currentColor'
|
|
103
|
+
/>
|
|
104
|
+
<path
|
|
105
|
+
d='M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z'
|
|
106
|
+
fill='currentColor'
|
|
107
|
+
/>
|
|
108
|
+
<path
|
|
109
|
+
d='M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z'
|
|
110
|
+
fill='currentColor'
|
|
111
|
+
/>
|
|
112
|
+
<path d='M21.1694 10.5806L14.5651 17.1849' stroke='currentColor' />
|
|
113
|
+
<path d='M22.1694 14.5806L18.5632 18.1868' stroke='currentColor' />
|
|
114
|
+
<circle cx='13.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
115
|
+
<circle cx='22.1' cy='26.1' r='1.7' stroke='currentColor' />
|
|
116
|
+
</svg>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
{logoText && <div className='text-base font-semibold tracking-tight'>{logoText}</div>}
|
|
121
|
+
<h1 className='mb-2 mt-4 text-center text-2xl font-bold tracking-tight'>{createAccountTitleLabel}</h1>
|
|
122
|
+
<p className='text-muted-foreground text-center text-sm'>{createAccountDescriptionLabel}</p>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<form className='flex flex-col gap-6' onSubmit={handleSubmit}>
|
|
126
|
+
<div className='relative'>
|
|
127
|
+
<Input
|
|
128
|
+
id='fullname'
|
|
129
|
+
name='fullName'
|
|
130
|
+
type='text'
|
|
131
|
+
placeholder={fullNamePlaceholder}
|
|
132
|
+
aria-label={fullNameLabel}
|
|
133
|
+
className='bg-transparent h-9 ps-10 text-sm'
|
|
134
|
+
autoComplete='name'
|
|
135
|
+
required
|
|
136
|
+
/>
|
|
137
|
+
<User className='text-muted-foreground absolute start-3 top-1/2 size-5 -translate-y-1/2' />
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className='relative'>
|
|
141
|
+
<Input
|
|
142
|
+
id='email'
|
|
143
|
+
name='email'
|
|
144
|
+
type='email'
|
|
145
|
+
placeholder={emailPlaceholder}
|
|
146
|
+
aria-label={emailLabel}
|
|
147
|
+
className='bg-transparent h-9 ps-10 text-sm'
|
|
148
|
+
autoComplete='email'
|
|
149
|
+
required
|
|
150
|
+
/>
|
|
151
|
+
<Mail className='text-muted-foreground absolute start-3 top-1/2 size-5 -translate-y-1/2' />
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div className='relative'>
|
|
155
|
+
<Input
|
|
156
|
+
id='password'
|
|
157
|
+
name='password'
|
|
158
|
+
type='password'
|
|
159
|
+
placeholder={passwordPlaceholder}
|
|
160
|
+
aria-label={passwordLabel}
|
|
161
|
+
className='bg-transparent h-9 ps-10 text-sm'
|
|
162
|
+
autoComplete='new-password'
|
|
163
|
+
required
|
|
164
|
+
/>
|
|
165
|
+
<Lock className='text-muted-foreground absolute start-3 top-1/2 size-5 -translate-y-1/2' />
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<Button className='h-9 w-full cursor-pointer px-4 py-2' type='submit'>
|
|
169
|
+
{createAccountLabel}
|
|
170
|
+
</Button>
|
|
171
|
+
|
|
172
|
+
{socialButtons.length > 0 && (
|
|
173
|
+
<>
|
|
174
|
+
<div className='relative my-6 text-center'>
|
|
175
|
+
<div className='absolute inset-0 flex items-center'>
|
|
176
|
+
<div className='w-full border-t' />
|
|
177
|
+
</div>
|
|
178
|
+
<div className='relative flex justify-center text-xs uppercase'>
|
|
179
|
+
<span className='bg-card text-muted-foreground px-2'>{orSignUpWithLabel}</span>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div className='grid grid-cols-3 gap-3'>
|
|
184
|
+
{socialButtons.map(button => (
|
|
185
|
+
<Button key={button.name} variant='outline' className='h-9 w-full cursor-pointer px-4 py-2'>
|
|
186
|
+
<button.icon />
|
|
187
|
+
<span className='sr-only'>{socialAriaLabel.replace('{name}', button.name)}</span>
|
|
188
|
+
</Button>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
193
|
+
</form>
|
|
194
|
+
|
|
195
|
+
<p className='mt-6 flex justify-center gap-1 text-center text-sm'>
|
|
196
|
+
<span>{alreadyHaveAccountLabel}</span>
|
|
197
|
+
{onSignIn ? (
|
|
198
|
+
<button type='button' onClick={onSignIn} className='underline underline-offset-4'>
|
|
199
|
+
{signInLabel}
|
|
200
|
+
</button>
|
|
201
|
+
) : (
|
|
202
|
+
<span className='underline underline-offset-4'>{signInLabel}</span>
|
|
203
|
+
)}
|
|
204
|
+
</p>
|
|
205
|
+
</Card>
|
|
206
|
+
|
|
207
|
+
<p className='text-muted-foreground relative z-10 mt-8 max-w-lg px-8 text-center text-xs text-balance'>
|
|
208
|
+
{termsLabel}{' '}
|
|
209
|
+
{termsHref ? (
|
|
210
|
+
<a href={termsHref} className='hover:text-primary underline underline-offset-4'>
|
|
211
|
+
{termsLinkLabel}
|
|
212
|
+
</a>
|
|
213
|
+
) : (
|
|
214
|
+
<span className='hover:text-primary underline underline-offset-4'>{termsLinkLabel}</span>
|
|
215
|
+
)}{' '}
|
|
216
|
+
{termsAndLabel}{' '}
|
|
217
|
+
{privacyHref ? (
|
|
218
|
+
<a href={privacyHref} className='hover:text-primary underline underline-offset-4'>
|
|
219
|
+
{privacyLinkLabel}
|
|
220
|
+
</a>
|
|
221
|
+
) : (
|
|
222
|
+
<span className='hover:text-primary underline underline-offset-4'>{privacyLinkLabel}</span>
|
|
223
|
+
)}
|
|
224
|
+
.
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default SignUpPage1
|