code cleanup

This commit is contained in:
mengstabketemaw
2026-06-05 14:38:39 +03:00
parent 78c7d08d82
commit 4a4e82f6b0
10 changed files with 75 additions and 216 deletions

View File

@@ -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()),
};
}

View File

@@ -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<string, string> }).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<typeof schema>;
@@ -34,6 +31,8 @@ export function LoginPage() {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const [loginTrigger] = useApiMutation<LoginPayload>();
const [meTrigger] = useApiMutation<AuthUser>();
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 },
});
}

View File

@@ -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<string, string> }).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<typeof schema>;
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);
}
};

View File

@@ -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<string, string> }).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);
}
};

View File

@@ -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<string>) {
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;

View File

@@ -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<string>) {
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;