feat: implement password requirements validation and UI component

This commit is contained in:
estifanos
2026-08-01 06:27:59 +00:00
parent 18f18b71dc
commit af4ef8b3a7
6 changed files with 110 additions and 41 deletions

View File

@@ -42,7 +42,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useTranslation } from 'react-i18next';
import { notify, PageHeader, useErrorHandler } from '@ema-platform/ui';
import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements } from '@ema-platform/ui';
import { useApiMutation } from '@ema-platform/api';
import { setUser } from '@ema-platform/auth';
import type { AuthUser } from '@ema-platform/auth';
@@ -170,12 +170,10 @@ export function ProfilePage() {
oldPassword: z
.string()
.min(1, { message: t('profile.validation.passwordMin') }),
newPassword: z
.string()
.min(8, { message: t('profile.validation.passwordMin') }),
newPassword: strongPasswordSchema(12),
confirmPassword: z
.string()
.min(8, { message: t('profile.validation.passwordMin') }),
.min(1, { message: t('profile.validation.passwordMin') }),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: t('profile.validation.passwordMismatch'),
@@ -414,12 +412,15 @@ export function ProfilePage() {
{...registerPassword('oldPassword')}
/>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<PasswordInput
label={t('profile.fields.newPassword')}
leftSection={<IconLock size={18} />}
error={passwordErrors.newPassword?.message}
{...registerPassword('newPassword')}
/>
<div>
<PasswordInput
label={t('profile.fields.newPassword')}
leftSection={<IconLock size={18} />}
error={passwordErrors.newPassword?.message}
{...registerPassword('newPassword')}
/>
<PasswordRequirements password={watchPassword('newPassword')} minLength={12} />
</div>
<PasswordInput
label={t('profile.fields.confirmPassword')}
leftSection={<IconLock size={18} />}

View File

@@ -43,7 +43,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useTranslation } from 'react-i18next';
import { notify, PageHeader, useErrorHandler } from '@ema-platform/ui';
import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements } from '@ema-platform/ui';
import { useApiMutation } from '@ema-platform/api';
import { authStorage, setUser, setCurrentProfile } from '@ema-platform/auth';
import type { CurrentProfile } from '@ema-platform/auth';
@@ -331,8 +331,8 @@ export function ProfilePage() {
const passwordSchema = z
.object({
oldPassword: z.string().min(1, { message: t('profile.validation.passwordMin') }),
newPassword: z.string().min(8, { message: t('profile.validation.passwordMin') }),
confirmPassword: z.string().min(8, { message: t('profile.validation.passwordMin') }),
newPassword: strongPasswordSchema(8),
confirmPassword: z.string().min(1, { message: t('profile.validation.passwordMin') }),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: t('profile.validation.passwordMismatch'),
@@ -654,12 +654,15 @@ export function ProfilePage() {
{...registerPassword('oldPassword')}
/>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<PasswordInput
label={t('profile.fields.newPassword')}
leftSection={<IconLock size={18} />}
error={passwordErrors.newPassword?.message}
{...registerPassword('newPassword')}
/>
<div>
<PasswordInput
label={t('profile.fields.newPassword')}
leftSection={<IconLock size={18} />}
error={passwordErrors.newPassword?.message}
{...registerPassword('newPassword')}
/>
<PasswordRequirements password={watchPassword('newPassword')} minLength={8} />
</div>
<PasswordInput
label={t('profile.fields.confirmPassword')}
leftSection={<IconLock size={18} />}

View File

@@ -28,7 +28,7 @@ import {
IconUser,
} from '@tabler/icons-react';
import { useApiMutation } from '@ema-platform/api';
import { notify } from '@ema-platform/ui';
import { notify, passwordMeetsAll, PasswordRequirements } from '@ema-platform/ui';
const OWNER_TYPES = [
'Individual (Private Owner)',
@@ -52,11 +52,11 @@ export function VesselOwnerRegisterPage() {
const [success, setSuccess] = useState(false);
const [registerTrigger] = useApiMutation<{ id: string }>();
const canSubmit = !!fullName.trim() && !!email.trim() && !!phone.trim() && !!ownerType && !!nationalIdOrTin.trim() && !!password && password === confirmPassword;
const canSubmit = !!fullName.trim() && !!email.trim() && !!phone.trim() && !!ownerType && !!nationalIdOrTin.trim() && passwordMeetsAll(password, 8) && password === confirmPassword;
const handleRegister = async () => {
if (!canSubmit) {
setError('Please fill in all required fields. Passwords must match.');
setError('Please fill in all required fields. Password must meet all requirements and match.');
return;
}
setError('');
@@ -164,14 +164,17 @@ export function VesselOwnerRegisterPage() {
<Divider label="Set Password" labelPosition="center" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<PasswordInput
label="Password"
placeholder="Min. 8 characters"
leftSection={<IconLock size={16} />}
required
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
<div>
<PasswordInput
label="Password"
placeholder="Min. 8 characters"
leftSection={<IconLock size={16} />}
required
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
<PasswordRequirements password={password} minLength={8} />
</div>
<PasswordInput
label="Confirm Password"
placeholder="Repeat password"