diff --git a/libs/auth/src/lib/pages/LoginPage.tsx b/libs/auth/src/lib/pages/LoginPage.tsx index 69f3e98f5..13347c548 100644 --- a/libs/auth/src/lib/pages/LoginPage.tsx +++ b/libs/auth/src/lib/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState } from 'react'; import { Alert, Anchor, @@ -11,56 +11,29 @@ import { Text, TextInput, Title, -} from "@mantine/core"; +} from '@mantine/core'; import { IconArrowRight, IconDeviceMobile, IconLock, IconMail, -} from "@tabler/icons-react"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { useNavigate, Link } from "react-router-dom"; -import { useDispatch } from "react-redux"; -import { useApiMutation } from "@ema-platform/api"; -import { notify, useErrorHandler } from "@ema-platform/ui"; -import { AuthShell } from "../components/AuthShell"; -import { loginSuccess, setUser, setCurrentProfile } from "../store/auth.slice"; -import type { - LoginPayload, - AuthUser, - CurrentProfile, -} from "../types/auth.types"; -import { useAuthConfig } from "../AuthConfig"; -import { authStorage } from "../utils/auth-storage"; -const emailOrPhone = z - .string() - .trim() - .transform((value) => { - // Convert 09xxxxxxxx -> +2519xxxxxxxx - if (/^09\d{8}$/.test(value)) { - return `+251${value.substring(1)}`; - } +} from '@tabler/icons-react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useNavigate, Link } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { useApiMutation } from '@ema-platform/api'; +import { notify } from '@ema-platform/ui'; +import { AuthShell } from '../components/AuthShell'; +import { loginSuccess, setUser, setCurrentProfile } from '../store/auth.slice'; +import type { LoginPayload, AuthUser, CurrentProfile } from '../types/auth.types'; +import { useAuthConfig } from '../AuthConfig'; +import { authStorage } from '../utils/auth-storage'; - return value; - }) - .refine( - (value) => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - const phoneRegex = /^\+2519\d{8}$/; - - return emailRegex.test(value) || phoneRegex.test(value); - }, - { - message: "Enter a valid email or phone number (+2519xxxxxxxx)", - }, - ); const schema = z.object({ - email: emailOrPhone, - password: z - .string() - .min(8, { message: "Password must be at least 8 characters" }), + email: z.string().email({ message: 'Enter a valid email' }), + password: z.string().min(5, { message: 'Password must be at least 6 characters' }), }); type FormValues = z.infer; @@ -73,7 +46,9 @@ export function LoginPage() { const [isLoading, setIsLoading] = useState(false); const [rememberMe, setRememberMe] = useState(true); const [serverError, setServerError] = useState(null); - const { handleError } = useErrorHandler(); + // MFA second step: set once /auth/login answers `mfaRequired` (US-IAM-006). + const [mfaEmail, setMfaEmail] = useState(null); + const [mfaOtp, setMfaOtp] = useState(''); const [loginTrigger] = useApiMutation(); const [meTrigger] = useApiMutation(); const [profileTrigger] = useApiMutation<{ profile: CurrentProfile | null }>(); @@ -90,8 +65,8 @@ export function LoginPage() { setIsLoading(true); try { const data = await loginTrigger({ - url: "/auth/login", - method: "POST", + url: '/auth/login', + method: 'POST', body: values, }).unwrap(); @@ -139,8 +114,8 @@ export function LoginPage() { dispatch(loginSuccess(data)); const me = await meTrigger({ - url: "/auth/me", - method: "GET", + url: '/auth/me', + method: 'GET', }).unwrap(); dispatch(setUser(me)); @@ -151,8 +126,8 @@ export function LoginPage() { // `useCurrentProfile` resolves it again on demand. try { const { profile } = await profileTrigger({ - url: "/profiles/me", - method: "GET", + url: '/profiles/me', + method: 'GET', }).unwrap(); if (profile) { authStorage.setProfileId(profile.id); @@ -162,23 +137,17 @@ export function LoginPage() { // Offline or a 5xx — the portal still works; the resolver retries. } - if (!me.isPhoneNumberVerified) { - navigate("/otp-verify", { - state: { - email: me.email, - phoneNumber: me.phoneNumber, - }, - }); - return; - } - - navigate(loginRedirectPath); - } catch (err: unknown) { - setServerError(handleError(err)); - } finally { - localStorage.setItem("rememberMe", String(rememberMe)); - setIsLoading(false); + if (!me.isPhoneNumberVerified) { + navigate('/otp-verify', { + state: { + email: me.email, + phoneNumber: me.phoneNumber, + }, + }); + return; } + + navigate(loginRedirectPath); }; return ( @@ -194,12 +163,7 @@ export function LoginPage() { {serverError && ( - setServerError(null)} - > + setServerError(null)}> {serverError} )} @@ -248,7 +212,7 @@ export function LoginPage() { size="md" leftSection={} error={errors.email?.message} - {...register("email")} + {...register('email')} /> } error={errors.password?.message} - {...register("password")} + {...register('password')} /> @@ -289,9 +253,23 @@ export function LoginPage() { + )} + + + + + {enableSignup && ( - Don't have an account?{" "} + Don't have an account?{' '} Create one @@ -300,4 +278,4 @@ export function LoginPage() { ); -} +} \ No newline at end of file