From 78c7d08d823d27b0168693ce642ce908558b3a25 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Fri, 5 Jun 2026 12:19:48 +0300 Subject: [PATCH] protal signup and login --- apps/portal/src/app/app.tsx | 15 +-- .../src/app/features/auth/pages/LoginPage.tsx | 26 ++++- .../features/auth/pages/SetPasswordPage.tsx | 101 +++++++++--------- .../app/features/auth/pages/SignupPage.tsx | 71 ++++++++---- .../src/app/features/auth/store/auth.slice.ts | 10 +- .../src/app/features/auth/types/auth.types.ts | 14 ++- .../portal/src/app/providers/AppProviders.tsx | 5 +- apps/portal/src/app/router.tsx | 62 +++++------ apps/portal/src/main.tsx | 2 - 9 files changed, 173 insertions(+), 133 deletions(-) diff --git a/apps/portal/src/app/app.tsx b/apps/portal/src/app/app.tsx index 8a0b8df61..58451087d 100644 --- a/apps/portal/src/app/app.tsx +++ b/apps/portal/src/app/app.tsx @@ -1,16 +1,17 @@ -// src/main.tsx or App.tsx -import { AppProviders, configureIam } from "@tria-plc/iamui-common"; +import { configureIam } from "@tria-plc/iamui-common"; +import { BrowserRouter } from 'react-router-dom'; import "@tria-plc/iamui-common/styles.css"; -// import { AppProviders } from './providers/AppProviders'; +import { AppProviders } from './providers/AppProviders'; import { AppRouter } from './router'; -configureIam({ apiUrl: 'http://localhost:3001/api'}); -// import './styles.css'; + +configureIam({ apiUrl: 'http://localhost:3001/api' }); export function App() { return ( - // 2. Wrap with Providers so hooks like useAuthUser() work - + + + ); } diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/apps/portal/src/app/features/auth/pages/LoginPage.tsx index f0153f2ef..ae6344ab1 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/apps/portal/src/app/features/auth/pages/LoginPage.tsx @@ -14,7 +14,9 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, Link } from 'react-router-dom'; -import { useAuth } from '../hooks/useAuth'; +import { useAppDispatch } from '../../../store/hooks'; +import { loginSuccess, setUser } from '../store/auth.slice'; +import type { LoginPayload, AuthUser } from '../types/auth.types'; import { notify } from '@ema-platform/ui'; const BASE_API_URL = @@ -30,7 +32,7 @@ type FormValues = z.infer; export function LoginPage() { const navigate = useNavigate(); - const { login } = useAuth(); + const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); const { @@ -50,9 +52,23 @@ export function LoginPage() { body: JSON.stringify(values), }); if (!res.ok) throw new Error('Login failed'); - const data = (await res.json()) as Parameters[0]; - login(data); - navigate('/dashboard'); + const data = (await res.json()) as LoginPayload; + dispatch(loginSuccess(data)); + + const meRes = await fetch(`${BASE_API_URL}/auth/me`, { + headers: { Authorization: `Bearer ${data.token}` }, + }); + if (!meRes.ok) throw new Error('Failed to fetch user'); + const me = (await meRes.json()) as AuthUser; + dispatch(setUser(me)); + + if (me.status === 'accepted') { + navigate('/dashboard'); + } else { + navigate('/set-password', { + state: { email: me.email, phoneNumber: me.phoneNumber }, + }); + } } catch { notify.error('Invalid email or password'); } finally { diff --git a/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx b/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx index eb2837420..f3c6eae87 100644 --- a/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx +++ b/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx @@ -1,7 +1,6 @@ import { Paper, TextInput, - PasswordInput, Button, Stack, Title, @@ -19,23 +18,15 @@ import { setPasswordFailure, } from '../store/set-password.slice'; import { notify } from '@ema-platform/ui'; +import { useState } from 'react'; const BASE_API_URL = (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? 'http://localhost:3001/api'; -const schema = z - .object({ - userId: z.string().min(1, 'User ID is required'), - email: z.string().email('Enter a valid email'), - verificationCode: z.string().min(1, 'Verification code is required'), - newPassword: z.string().min(6, 'Password must be at least 6 characters'), - confirmPassword: z.string().min(6, 'Confirm your password'), - }) - .refine((data) => data.newPassword === data.confirmPassword, { - message: 'Passwords do not match', - path: ['confirmPassword'], - }); +const schema = z.object({ + verificationCode: z.string().min(1, 'Verification code is required'), +}); type FormValues = z.infer; @@ -44,7 +35,12 @@ export function SetPasswordPage() { const dispatch = useAppDispatch(); const location = useLocation(); const { loading } = useAppSelector((s) => s.setPassword); - const signupEmail = (location.state as { email?: string } | null)?.email ?? ''; + const state = location.state as + | { email?: string; phoneNumber?: string } + | null; + const email = state?.email ?? ''; + const phoneNumber = state?.phoneNumber ?? ''; + const [resending, setResending] = useState(false); const { register, @@ -52,31 +48,28 @@ export function SetPasswordPage() { formState: { errors }, } = useForm({ resolver: zodResolver(schema), - defaultValues: { email: signupEmail }, }); const onSubmit = async (values: FormValues) => { dispatch(setPasswordStart()); try { - const res = await fetch(`${BASE_API_URL}/auth/set-password`, { + const res = await fetch(`${BASE_API_URL}/auth/verify-phone-number`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - userId: values.userId, - email: values.email, + email, + phoneNumber, verificationCode: values.verificationCode, - newPassword: values.newPassword, - confirmPassword: values.confirmPassword, }), }); if (!res.ok) { const errorData = await res.json().catch(() => null); - throw new Error(errorData?.message ?? 'Failed to set password'); + throw new Error(errorData?.message ?? 'Verification failed'); } dispatch(setPasswordSuccess()); - notify.success('Password set successfully'); + notify.success('Phone number verified successfully'); navigate('/dashboard'); } catch (err) { const msg = err instanceof Error ? err.message : 'Something went wrong'; @@ -85,52 +78,60 @@ export function SetPasswordPage() { } }; + const handleResendOtp = async () => { + setResending(true); + try { + const res = await fetch(`${BASE_API_URL}/auth/resend-otp`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => null); + throw new Error(errorData?.message ?? 'Failed to resend OTP'); + } + + notify.success('Verification code resent to your email'); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Something went wrong'; + notify.error(msg); + } finally { + setResending(false); + } + }; + return (
- Set your password + Verify your email - A verification code has been sent to your email. Enter it below along - with your new password to complete registration. + A verification code has been sent to {email || 'your email'}. Enter it + below to complete your registration.
- - - -
+
diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/apps/portal/src/app/features/auth/pages/SignupPage.tsx index 2a277e457..ed5716dc5 100644 --- a/apps/portal/src/app/features/auth/pages/SignupPage.tsx +++ b/apps/portal/src/app/features/auth/pages/SignupPage.tsx @@ -1,6 +1,7 @@ import { Paper, TextInput, + PasswordInput, Button, Stack, Title, @@ -22,13 +23,21 @@ const BASE_API_URL = (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? 'http://localhost:3001/api'; -const schema = z.object({ - email: z.string().email('Enter a valid email'), - username: z.string().min(3, 'Username must be at least 3 characters'), - phoneNumber: z.string().min(1, 'Phone number is required'), - userType: z.string().min(1, 'User type is required'), - name: z.string().min(1, 'Name is required'), -}); +const schema = z + .object({ + email: z.string().email('Enter a valid email'), + username: z.string().min(3, 'Username must be at least 3 characters'), + phoneNumber: z.string().min(1, 'Phone number is required'), + userType: z.literal('individual'), + nameEn: z.string().min(1, 'Name (English) is required'), + nameAm: z.string().optional(), + password: z.string().min(8, 'Password must be at least 8 characters'), + confirmPassword: z.string().min(8, 'Confirm your password'), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords do not match', + path: ['confirmPassword'], + }); type FormValues = z.infer; @@ -41,6 +50,8 @@ interface SignupPayload { am: string; en: string; }; + password: string; + confirmPassword: string; } export function SignupPage() { @@ -54,6 +65,7 @@ export function SignupPage() { formState: { errors }, } = useForm({ resolver: zodResolver(schema), + defaultValues: { userType: 'individual' }, }); const onSubmit = async (values: FormValues) => { @@ -64,13 +76,12 @@ export function SignupPage() { username: values.username, phoneNumber: values.phoneNumber, userType: values.userType, - name: { - am: '', - en: values.name, - }, + name: { en: values.nameEn, am: values.nameAm ?? '' }, + password: values.password, + confirmPassword: values.confirmPassword, }; - const res = await fetch(`${BASE_API_URL}/auth/signup`, { + const res = await fetch(`${BASE_API_URL}/auth/signup-with-pwd`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), @@ -81,14 +92,16 @@ export function SignupPage() { throw new Error(errorData?.message ?? 'Signup failed'); } - const data = (await res.json()) as { token: string; refreshToken: string }; + const data = (await res.json()) as { token: string; refreshToken: string; isPhoneNumberVerified: boolean }; authStorage.setToken(data.token); authStorage.setRefreshToken(data.refreshToken); dispatch(hydrateAuth()); dispatch(signupSuccess()); - navigate('/set-password', { state: { email: values.email } }); + navigate('/set-password', { + state: { email: values.email, phoneNumber: values.phoneNumber }, + }); } catch (err) { const msg = err instanceof Error ? err.message : 'Something went wrong'; dispatch(signupFailure(msg)); @@ -104,10 +117,16 @@ export function SignupPage() {
+ - +