From 4a4e82f6b0367c8fb321d9b17fbeaf1e4c8ef389 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Fri, 5 Jun 2026 14:38:39 +0300 Subject: [PATCH] code cleanup --- apps/backoffice/src/app/router/index.tsx | 2 +- .../src/app/features/auth/hooks/useAuth.ts | 17 ------ .../src/app/features/auth/pages/LoginPage.tsx | 32 +++++----- ...sswordPage.tsx => OTPVerificationPage.tsx} | 58 +++++------------- .../app/features/auth/pages/SignupPage.tsx | 59 ++++++++----------- .../features/auth/store/set-password.slice.ts | 42 ------------- .../app/features/auth/store/signup.slice.ts | 41 ------------- apps/portal/src/app/layouts/PortalLayout.tsx | 21 +++---- apps/portal/src/app/router.tsx | 15 +++-- apps/portal/src/app/store/index.ts | 4 -- 10 files changed, 75 insertions(+), 216 deletions(-) delete mode 100644 apps/portal/src/app/features/auth/hooks/useAuth.ts rename apps/portal/src/app/features/auth/pages/{SetPasswordPage.tsx => OTPVerificationPage.tsx} (61%) delete mode 100644 apps/portal/src/app/features/auth/store/set-password.slice.ts delete mode 100644 apps/portal/src/app/features/auth/store/signup.slice.ts diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx index b91446d3e..4518adb80 100644 --- a/apps/backoffice/src/app/router/index.tsx +++ b/apps/backoffice/src/app/router/index.tsx @@ -61,7 +61,7 @@ const router = createBrowserRouter([ ), children: [ { path: '/login', element: }, - { path: '/set-password', element: }, + { path: '/otp-verify', element: }, ], }, { path: '/404', element:
Page not found
}, diff --git a/apps/portal/src/app/features/auth/hooks/useAuth.ts b/apps/portal/src/app/features/auth/hooks/useAuth.ts deleted file mode 100644 index 260f46bd0..000000000 --- a/apps/portal/src/app/features/auth/hooks/useAuth.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { loginSuccess, logout as logoutAction, hydrateAuth } from '../store/auth.slice'; -import type { LoginPayload } from '../types/auth.types'; - -export function useAuth() { - const dispatch = useAppDispatch(); - const { user, token, isAuthenticated } = useAppSelector((s) => s.auth); - - return { - user, - token, - isAuthenticated, - login: (p: LoginPayload) => dispatch(loginSuccess(p)), - logout: () => dispatch(logoutAction()), - hydrate: () => dispatch(hydrateAuth()), - }; -} diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/apps/portal/src/app/features/auth/pages/LoginPage.tsx index ae6344ab1..a9f26f342 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/apps/portal/src/app/features/auth/pages/LoginPage.tsx @@ -17,15 +17,12 @@ import { useNavigate, Link } from 'react-router-dom'; import { useAppDispatch } from '../../../store/hooks'; import { loginSuccess, setUser } from '../store/auth.slice'; import type { LoginPayload, AuthUser } from '../types/auth.types'; +import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; -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'), - password: z.string().min(0, 'Password must be at least 6 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; @@ -34,6 +31,8 @@ export function LoginPage() { const navigate = useNavigate(); const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); + const [loginTrigger] = useApiMutation(); + const [meTrigger] = useApiMutation(); const { register, @@ -46,26 +45,23 @@ export function LoginPage() { const onSubmit = async (values: FormValues) => { setIsLoading(true); try { - const res = await fetch(`${BASE_API_URL}/auth/login`, { + const data = await loginTrigger({ + url: '/auth/login', method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(values), - }); - if (!res.ok) throw new Error('Login failed'); - const data = (await res.json()) as LoginPayload; + body: values, + }).unwrap() 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; + const me = await meTrigger({ + url: '/auth/me', + method: 'GET', + }).unwrap() as AuthUser; dispatch(setUser(me)); if (me.status === 'accepted') { navigate('/dashboard'); } else { - navigate('/set-password', { + navigate('/otp-verify', { state: { email: me.email, phoneNumber: me.phoneNumber }, }); } diff --git a/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx b/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx similarity index 61% rename from apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx rename to apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx index f3c6eae87..c786d40fe 100644 --- a/apps/portal/src/app/features/auth/pages/SetPasswordPage.tsx +++ b/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx @@ -11,36 +11,26 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, useLocation } from 'react-router-dom'; -import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { - setPasswordStart, - setPasswordSuccess, - setPasswordFailure, -} from '../store/set-password.slice'; +import { useApiMutation } from '@ema-platform/api'; 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({ - verificationCode: z.string().min(1, 'Verification code is required'), + verificationCode: z.string().min(1, { message: 'Verification code is required' }), }); type FormValues = z.infer; -export function SetPasswordPage() { +export function OTPVerificationPage() { const navigate = useNavigate(); - const dispatch = useAppDispatch(); const location = useLocation(); - const { loading } = useAppSelector((s) => s.setPassword); const state = location.state as | { email?: string; phoneNumber?: string } | null; const email = state?.email ?? ''; const phoneNumber = state?.phoneNumber ?? ''; - const [resending, setResending] = useState(false); + + const [verifyTrigger, { isLoading: loading }] = useApiMutation(); + const [resendTrigger, { isLoading: resending }] = useApiMutation(); const { register, @@ -51,53 +41,33 @@ export function SetPasswordPage() { }); const onSubmit = async (values: FormValues) => { - dispatch(setPasswordStart()); try { - const res = await fetch(`${BASE_API_URL}/auth/verify-phone-number`, { + await verifyTrigger({ + url: '/auth/verify-phone-number', method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email, - phoneNumber, - verificationCode: values.verificationCode, - }), - }); + body: { email, phoneNumber, verificationCode: values.verificationCode }, + }).unwrap(); - if (!res.ok) { - const errorData = await res.json().catch(() => null); - throw new Error(errorData?.message ?? 'Verification failed'); - } - - dispatch(setPasswordSuccess()); notify.success('Phone number verified successfully'); navigate('/dashboard'); } catch (err) { const msg = err instanceof Error ? err.message : 'Something went wrong'; - dispatch(setPasswordFailure(msg)); notify.error(msg); } }; const handleResendOtp = async () => { - setResending(true); try { - const res = await fetch(`${BASE_API_URL}/auth/resend-otp`, { + await resendTrigger({ + 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'); - } + body: { email }, + }).unwrap(); 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); } }; diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/apps/portal/src/app/features/auth/pages/SignupPage.tsx index ed5716dc5..16bc78945 100644 --- a/apps/portal/src/app/features/auth/pages/SignupPage.tsx +++ b/apps/portal/src/app/features/auth/pages/SignupPage.tsx @@ -13,26 +13,21 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, Link } from 'react-router-dom'; -import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { signupStart, signupSuccess, signupFailure } from '../store/signup.slice'; -import { hydrateAuth } from '../store/auth.slice'; -import { authStorage } from '../utils/auth-storage'; +import { useAppDispatch } from '../../../store/hooks'; +import { loginSuccess } from '../store/auth.slice'; +import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; -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'), + email: z.string().email(), + username: z.string().min(3, { message: 'Username must be at least 3 characters' }), + phoneNumber: z.string().min(1, { message: 'Phone number is required' }), userType: z.literal('individual'), - nameEn: z.string().min(1, 'Name (English) is required'), + nameEn: z.string().min(1, { message: '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'), + password: z.string().min(8, { message: 'Password must be at least 8 characters' }), + confirmPassword: z.string().min(8, { message: 'Confirm your password' }), }) .refine((data) => data.password === data.confirmPassword, { message: 'Passwords do not match', @@ -57,7 +52,11 @@ interface SignupPayload { export function SignupPage() { const navigate = useNavigate(); const dispatch = useAppDispatch(); - const { loading } = useAppSelector((s) => s.signup); + const [signupTrigger, { isLoading: loading }] = useApiMutation<{ + token: string; + refreshToken: string; + isPhoneNumberVerified: boolean; + }>(); const { register, @@ -69,7 +68,6 @@ export function SignupPage() { }); const onSubmit = async (values: FormValues) => { - dispatch(signupStart()); try { const payload: SignupPayload = { email: values.email, @@ -81,30 +79,25 @@ export function SignupPage() { confirmPassword: values.confirmPassword, }; - const res = await fetch(`${BASE_API_URL}/auth/signup-with-pwd`, { + const data = await signupTrigger({ + url: '/auth/signup-with-pwd', method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); + body: payload, + }).unwrap() as { token: string; refreshToken: string; isPhoneNumberVerified: boolean }; - if (!res.ok) { - const errorData = await res.json().catch(() => null); - throw new Error(errorData?.message ?? 'Signup failed'); - } + dispatch( + loginSuccess({ + token: data.token, + refreshToken: data.refreshToken, + isPhoneNumberVerified: data.isPhoneNumberVerified, + }), + ); - 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', { + navigate('/otp-verify', { state: { email: values.email, phoneNumber: values.phoneNumber }, }); } catch (err) { const msg = err instanceof Error ? err.message : 'Something went wrong'; - dispatch(signupFailure(msg)); notify.error(msg); } }; diff --git a/apps/portal/src/app/features/auth/store/set-password.slice.ts b/apps/portal/src/app/features/auth/store/set-password.slice.ts deleted file mode 100644 index bdcf8d745..000000000 --- a/apps/portal/src/app/features/auth/store/set-password.slice.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; - -interface SetPasswordState { - loading: boolean; - success: boolean; - error: string | null; -} - -const initialState: SetPasswordState = { - loading: false, - success: false, - error: null, -}; - -const setPasswordSlice = createSlice({ - name: 'setPassword', - initialState, - reducers: { - setPasswordStart(state) { - state.loading = true; - state.error = null; - state.success = false; - }, - setPasswordSuccess(state) { - state.loading = false; - state.success = true; - }, - setPasswordFailure(state, action: PayloadAction) { - state.loading = false; - state.error = action.payload; - }, - resetSetPassword(state) { - state.loading = false; - state.success = false; - state.error = null; - }, - }, -}); - -export const { setPasswordStart, setPasswordSuccess, setPasswordFailure, resetSetPassword } = - setPasswordSlice.actions; -export const setPasswordReducer = setPasswordSlice.reducer; diff --git a/apps/portal/src/app/features/auth/store/signup.slice.ts b/apps/portal/src/app/features/auth/store/signup.slice.ts deleted file mode 100644 index 92a5f4294..000000000 --- a/apps/portal/src/app/features/auth/store/signup.slice.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; - -interface SignupState { - loading: boolean; - success: boolean; - error: string | null; -} - -const initialState: SignupState = { - loading: false, - success: false, - error: null, -}; - -const signupSlice = createSlice({ - name: 'signup', - initialState, - reducers: { - signupStart(state) { - state.loading = true; - state.error = null; - state.success = false; - }, - signupSuccess(state) { - state.loading = false; - state.success = true; - }, - signupFailure(state, action: PayloadAction) { - state.loading = false; - state.error = action.payload; - }, - resetSignup(state) { - state.loading = false; - state.success = false; - state.error = null; - }, - }, -}); - -export const { signupStart, signupSuccess, signupFailure, resetSignup } = signupSlice.actions; -export const signupReducer = signupSlice.reducer; diff --git a/apps/portal/src/app/layouts/PortalLayout.tsx b/apps/portal/src/app/layouts/PortalLayout.tsx index 4e90c93c9..50742ff64 100644 --- a/apps/portal/src/app/layouts/PortalLayout.tsx +++ b/apps/portal/src/app/layouts/PortalLayout.tsx @@ -1,24 +1,25 @@ import { AppShell, Group, Text, Button } from '@mantine/core'; import { Outlet, useNavigate } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '../store/hooks'; +import { logout } from '../features/auth/store/auth.slice'; export function PortalLayout() { const navigate = useNavigate(); - const isLoggedIn = !!localStorage.getItem('ema-portal-auth-token'); + const dispatch = useAppDispatch(); + const isAuthenticated = useAppSelector((s) => s.auth.isAuthenticated); + + const handleLogout = () => { + dispatch(logout()); + navigate('/login'); + }; return ( EMA Portal - {isLoggedIn ? ( - ) : ( diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx index 8ce217512..098111c44 100644 --- a/apps/portal/src/app/router.tsx +++ b/apps/portal/src/app/router.tsx @@ -3,9 +3,8 @@ import type { ReactNode } from 'react'; import { PortalLayout } from './layouts/PortalLayout'; import { LoginPage } from './features/auth/pages/LoginPage'; import { SignupPage } from './features/auth/pages/SignupPage'; -import { SetPasswordPage } from './features/auth/pages/SetPasswordPage'; +import { OTPVerificationPage } from './features/auth/pages/OTPVerificationPage'; import { DashboardPage } from './features/dashboard/pages/DashboardPage'; -import { UserManagementPage, UserManagementLayout } from "@tria-plc/iamui-common"; function getToken(): string | null { return localStorage.getItem('ema-portal-auth-token'); @@ -21,10 +20,14 @@ export function AppRouter() { } /> } /> - } /> - }> - } /> - + + + + } + /> }> } />