1+ import {
2+ IonButton ,
3+ useIonRouter ,
4+ IonText ,
5+ } from '@ionic/react' ;
6+ import { useState } from 'react' ;
7+ import classNames from 'classnames' ;
8+ import { Form , Formik } from 'formik' ;
9+ import { object , string } from 'yup' ;
10+ import { useTranslation } from 'react-i18next' ;
11+
12+ import './VerificationForm.scss' ;
13+ import { BaseComponentProps } from 'common/components/types' ;
14+ import { useAuth } from 'common/hooks/useAuth' ;
15+ import { useProgress } from 'common/hooks/useProgress' ;
16+ import Input from 'common/components/Input/Input' ;
17+ import ErrorCard from 'common/components/Card/ErrorCard' ;
18+ import HeaderRow from 'common/components/Text/HeaderRow' ;
19+ import { getAuthErrorMessage } from 'common/utils/auth-errors' ;
20+
21+ /**
22+ * Properties for the `VerificationForm` component.
23+ */
24+ interface VerificationFormProps extends BaseComponentProps {
25+ email : string ;
26+ }
27+
28+ /**
29+ * Email verification form values.
30+ */
31+ interface VerificationFormValues {
32+ code : string ;
33+ }
34+
35+ /**
36+ * The `VerificationForm` component renders a form for verifying a user's email with a code.
37+ * @param {VerificationFormProps } props - Component properties.
38+ * @returns {JSX.Element } JSX
39+ */
40+ const VerificationForm = ( { className, email, testid = 'form-verification' } : VerificationFormProps ) : JSX . Element => {
41+ const [ error , setError ] = useState < string > ( '' ) ;
42+ const [ successMessage , setSuccessMessage ] = useState < string > ( '' ) ;
43+ const { setIsActive : setShowProgress } = useProgress ( ) ;
44+ const router = useIonRouter ( ) ;
45+ const { confirmSignUp, resendConfirmationCode } = useAuth ( ) ;
46+ const { t } = useTranslation ( ) ;
47+
48+ /**
49+ * Verification form validation schema.
50+ */
51+ const validationSchema = object ( {
52+ code : string ( )
53+ . matches ( / ^ \d + $ / , t ( 'validation.numeric' ) )
54+ . length ( 6 , t ( 'validation.exact-length' , { length : 6 } ) )
55+ . required ( t ( 'validation.required' ) ) ,
56+ } ) ;
57+
58+ /**
59+ * Handle resend code button click
60+ */
61+ const handleResendCode = async ( ) => {
62+ if ( ! email ) {
63+ setError ( t ( 'error.no-email' , { ns : 'auth' } ) ) ;
64+ return ;
65+ }
66+
67+ try {
68+ setError ( '' ) ;
69+ setSuccessMessage ( '' ) ;
70+ setShowProgress ( true ) ;
71+ await resendConfirmationCode ( email ) ;
72+ setSuccessMessage ( t ( 'email-verification.code-resent' , { ns : 'auth' } ) ) ;
73+ } catch ( err ) {
74+ setError ( getAuthErrorMessage ( err ) ) ;
75+ } finally {
76+ setShowProgress ( false ) ;
77+ }
78+ } ;
79+
80+ return (
81+ < div className = { classNames ( 'ls-verification-form' , className ) } data-testid = { testid } >
82+ { error && (
83+ < ErrorCard
84+ content = { error }
85+ className = "ion-margin-bottom"
86+ testid = { `${ testid } -error` }
87+ />
88+ ) }
89+
90+ { successMessage && (
91+ < div className = "ls-verification-form__success" data-testid = { `${ testid } -success` } >
92+ < IonText color = "success" > { successMessage } </ IonText >
93+ </ div >
94+ ) }
95+
96+ < Formik < VerificationFormValues >
97+ initialValues = { {
98+ code : '' ,
99+ } }
100+ onSubmit = { async ( values , { setSubmitting } ) => {
101+ if ( ! email ) {
102+ setError ( t ( 'error.no-email' , { ns : 'auth' } ) ) ;
103+ return ;
104+ }
105+
106+ try {
107+ setError ( '' ) ;
108+ setSuccessMessage ( '' ) ;
109+ setShowProgress ( true ) ;
110+ await confirmSignUp ( email , values . code ) ;
111+
112+ // Show success message briefly before redirecting
113+ setSuccessMessage ( t ( 'email-verification.success' , { ns : 'auth' } ) ) ;
114+ setTimeout ( ( ) => {
115+ router . push ( '/auth/signin' , 'forward' , 'replace' ) ;
116+ } , 1500 ) ;
117+ } catch ( err ) {
118+ setError ( getAuthErrorMessage ( err ) ) ;
119+ } finally {
120+ setShowProgress ( false ) ;
121+ setSubmitting ( false ) ;
122+ }
123+ } }
124+ validationSchema = { validationSchema }
125+ >
126+ { ( { dirty, isSubmitting } ) => (
127+ < Form data-testid = { `${ testid } -form` } >
128+ < HeaderRow border >
129+ < div > { t ( 'email-verification.title' , { ns : 'auth' } ) } </ div >
130+ </ HeaderRow >
131+
132+ < div className = "ls-verification-form__message" >
133+ < IonText >
134+ { t ( 'email-verification.message' , { ns : 'auth' } ) }
135+ </ IonText >
136+ { email && (
137+ < IonText className = "ls-verification-form__email" >
138+ < strong > { email } </ strong >
139+ </ IonText >
140+ ) }
141+ </ div >
142+
143+ < Input
144+ name = "code"
145+ label = { t ( 'label.verification-code' , { ns : 'auth' } ) }
146+ labelPlacement = "stacked"
147+ maxlength = { 6 }
148+ className = "ls-verification-form__input"
149+ data-testid = { `${ testid } -field-code` }
150+ type = "text"
151+ inputmode = "numeric"
152+ />
153+
154+ < IonButton
155+ type = "submit"
156+ color = "primary"
157+ className = "ls-verification-form__button"
158+ expand = "block"
159+ disabled = { isSubmitting || ! dirty }
160+ data-testid = { `${ testid } -button-submit` }
161+ >
162+ { t ( 'confirm' , { ns : 'auth' } ) }
163+ </ IonButton >
164+
165+ < div className = "ls-verification-form__resend" >
166+ < IonButton
167+ fill = "clear"
168+ color = "medium"
169+ onClick = { handleResendCode }
170+ disabled = { isSubmitting }
171+ data-testid = { `${ testid } -button-resend` }
172+ >
173+ { t ( 'resend-code' , { ns : 'auth' } ) }
174+ </ IonButton >
175+ </ div >
176+ </ Form >
177+ ) }
178+ </ Formik >
179+ </ div >
180+ ) ;
181+ } ;
182+
183+ export default VerificationForm ;
0 commit comments