Skip to content

Commit 7ef2119

Browse files
Add password requirements and guidelines to SignUpForm
- Introduced password requirements in auth.json for better user guidance - Added PasswordGuidelines component to SignUpForm for real-time validation feedback - Updated validation schema to enforce password complexity rules (min length, uppercase, number, special character) - Adjusted styles for password guidelines to improve layout and visibility
1 parent c58e578 commit 7ef2119

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

frontend/src/common/utils/i18n/resources/en/auth.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"submit": "Submit",
4040
"confirm": "Confirm",
4141
"resend-code": "Resend Code",
42+
"password-requirements": "Password Requirements:",
4243
"email-verification": {
4344
"title": "Email Verification",
4445
"message": "We've sent a verification code to your email. Please enter it below to verify your account.",
@@ -59,6 +60,9 @@
5960
"email": "Please enter a valid email address",
6061
"required": "This field is required",
6162
"min-length": "Must be at least {{length}} characters",
62-
"max-length": "Must be at most {{length}} characters"
63+
"max-length": "Must be at most {{length}} characters",
64+
"uppercase": "Must contain at least one uppercase letter",
65+
"number": "Must contain at least one number",
66+
"special-char": "Must contain at least one special character"
6367
}
6468
}

frontend/src/pages/Auth/SignUp/components/SignUpForm.scss

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,39 @@
22
padding: 1rem;
33

44
.ls-signup-form__input {
5-
margin-bottom: 1rem;
5+
margin-bottom: 16px;
66
}
77

88
.ls-signup-form__button {
9-
margin-top: 1.5rem;
9+
margin-top: 16px;
10+
}
11+
12+
&__password-guidelines {
13+
margin-top: -8px;
14+
margin-bottom: 16px;
15+
font-size: 0.85rem;
16+
17+
&-header {
18+
margin-bottom: 4px;
19+
font-weight: 500;
20+
}
21+
22+
&-item {
23+
display: flex;
24+
align-items: center;
25+
margin-bottom: 2px;
26+
27+
&-icon {
28+
margin-right: 6px;
29+
}
30+
31+
&-valid {
32+
color: var(--ion-color-success);
33+
}
34+
35+
&-invalid {
36+
color: var(--ion-color-medium);
37+
}
38+
}
1039
}
1140
}

frontend/src/pages/Auth/SignUp/components/SignUpForm.tsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import {
66
IonText,
77
IonRow,
88
IonCol,
9+
IonIcon,
910
} from '@ionic/react';
1011
import { useRef, useState } from 'react';
1112
import classNames from 'classnames';
12-
import { Form, Formik } from 'formik';
13+
import { Form, Formik, useFormikContext } from 'formik';
1314
import { object, string, ref } from 'yup';
1415
import { useTranslation } from 'react-i18next';
16+
import { checkmarkCircle, closeCircle } from 'ionicons/icons';
1517

1618
import './SignUpForm.scss';
1719
import { BaseComponentProps } from 'common/components/types';
@@ -40,6 +42,44 @@ interface SignUpFormValues {
4042
confirmPassword: string;
4143
}
4244

45+
/**
46+
* Password guidelines component
47+
*/
48+
const PasswordGuidelines = () => {
49+
const { values } = useFormikContext<SignUpFormValues>();
50+
const password = values.password || '';
51+
const { t } = useTranslation();
52+
53+
const hasMinLength = password.length >= 8;
54+
const hasUppercase = /[A-Z]/.test(password);
55+
const hasNumber = /[0-9]/.test(password);
56+
const hasSpecialChar = /[^A-Za-z0-9]/.test(password);
57+
58+
const renderGuideline = (isValid: boolean, text: string) => {
59+
return (
60+
<div className={`ls-signup-form__password-guidelines-item ls-signup-form__password-guidelines-item-${isValid ? 'valid' : 'invalid'}`}>
61+
<IonIcon
62+
icon={isValid ? checkmarkCircle : closeCircle}
63+
className="ls-signup-form__password-guidelines-item-icon"
64+
/>
65+
<span>{text}</span>
66+
</div>
67+
);
68+
};
69+
70+
return (
71+
<div className="ls-signup-form__password-guidelines">
72+
<div className="ls-signup-form__password-guidelines-header">
73+
{t('password-requirements', { ns: 'auth' })}
74+
</div>
75+
{renderGuideline(hasMinLength, t('validation.min-length', { length: 8, ns: 'auth' }))}
76+
{renderGuideline(hasUppercase, t('validation.uppercase', { ns: 'auth' }))}
77+
{renderGuideline(hasNumber, t('validation.number', { ns: 'auth' }))}
78+
{renderGuideline(hasSpecialChar, t('validation.special-char', { ns: 'auth' }))}
79+
</div>
80+
);
81+
};
82+
4383
/**
4484
* The `SignUpForm` component renders a form for user registration.
4585
* @param {SignUpFormProps} props - Component properties.
@@ -71,6 +111,9 @@ const SignUpForm = ({ className, testid = 'form-signup' }: SignUpFormProps): JSX
71111
.required(t('validation.required', { ns: 'auth' })),
72112
password: string()
73113
.min(8, t('validation.min-length', { length: 8, ns: 'auth' }))
114+
.matches(/[A-Z]/, t('validation.uppercase', { ns: 'auth' }))
115+
.matches(/[0-9]/, t('validation.number', { ns: 'auth' }))
116+
.matches(/[^A-Za-z0-9]/, t('validation.special-char', { ns: 'auth' }))
74117
.required(t('validation.required', { ns: 'auth' })),
75118
confirmPassword: string()
76119
.oneOf([ref('password')], t('validation.passwords-match', { ns: 'auth' }))
@@ -180,6 +223,8 @@ const SignUpForm = ({ className, testid = 'form-signup' }: SignUpFormProps): JSX
180223
<IonInputPasswordToggle slot="end"></IonInputPasswordToggle>
181224
</Input>
182225

226+
<PasswordGuidelines />
227+
183228
<Input
184229
type="password"
185230
name="confirmPassword"

0 commit comments

Comments
 (0)