mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: refactor department to organization and move certifications to configuration tab commit
This commit is contained in:
@@ -6,8 +6,8 @@ export { LoginPage } from './lib/pages/LoginPage';
|
||||
export { SignupPage } from './lib/pages/SignupPage';
|
||||
export { ForgotPasswordPage } from './lib/pages/ForgotPasswordPage';
|
||||
export { OTPVerificationPage } from './lib/pages/OTPVerificationPage';
|
||||
export { authReducer, loginSuccess, setUser, logout, hydrateAuth } from './lib/store/auth.slice';
|
||||
export { authReducer, loginSuccess, setUser, setCurrentProfile, clearCurrentProfile, logout, hydrateAuth } from './lib/store/auth.slice';
|
||||
export { signupReducer, setSignupData, setSignupStep, resetSignup } from './lib/store/signup.slice';
|
||||
export { configureAuthStorage, authStorage } from './lib/utils/auth-storage';
|
||||
export { refreshAccessToken } from './lib/utils/refresh-token';
|
||||
export type { AuthUser, AuthState, LoginPayload } from './lib/types/auth.types';
|
||||
export type { AuthUser, AuthState, LoginPayload, CurrentProfile, CurrentProfileAddress, CurrentProfileProfession } from './lib/types/auth.types';
|
||||
|
||||
@@ -26,8 +26,8 @@ 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 } from '../store/auth.slice';
|
||||
import type { LoginPayload, AuthUser } from '../types/auth.types';
|
||||
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';
|
||||
|
||||
@@ -48,7 +48,7 @@ export function LoginPage() {
|
||||
const [serverError, setServerError] = useState<string | null>(null);
|
||||
const [loginTrigger] = useApiMutation<LoginPayload>();
|
||||
const [meTrigger] = useApiMutation<AuthUser>();
|
||||
const [profileCheckTrigger] = useApiMutation<{ id: string }>();
|
||||
const [profileCheckTrigger] = useApiMutation<{ total: number; items: CurrentProfile[] }>();
|
||||
|
||||
const {
|
||||
register,
|
||||
@@ -76,12 +76,17 @@ export function LoginPage() {
|
||||
|
||||
let hasProfile = false;
|
||||
try {
|
||||
const profile = await profileCheckTrigger({
|
||||
url: `/profiles/by-user/${me.id}`,
|
||||
const q = `w=user_id:=:${me.id}&i=user,address,profession`;
|
||||
const result = await profileCheckTrigger({
|
||||
url: `/profiles?q=${encodeURIComponent(q)}`,
|
||||
method: 'GET',
|
||||
}).unwrap();
|
||||
authStorage.setProfileId(profile.id);
|
||||
hasProfile = true;
|
||||
if (result.total > 0 && result.items.length > 0) {
|
||||
const profile = result.items[0];
|
||||
authStorage.setProfileId(profile.id);
|
||||
dispatch(setCurrentProfile(profile));
|
||||
hasProfile = true;
|
||||
}
|
||||
} catch {
|
||||
// profile not found — redirect to setup
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Anchor,
|
||||
Button,
|
||||
Center,
|
||||
@@ -46,6 +47,7 @@ export function OTPVerificationPage() {
|
||||
const [verifyTrigger, { isLoading: loading }] = useApiMutation();
|
||||
const [resendTrigger, { isLoading: resending }] = useApiMutation();
|
||||
const [secondsLeft, setSecondsLeft] = useState(RESEND_SECONDS);
|
||||
const [serverError, setServerError] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -72,8 +74,11 @@ export function OTPVerificationPage() {
|
||||
|
||||
notify.success('Phone number verified successfully');
|
||||
navigate(needsProfile ? '/profile-setup' : loginRedirectPath);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Something went wrong';
|
||||
} catch (err: unknown) {
|
||||
const msg =
|
||||
(err as { data?: { message?: string } })?.data?.message ??
|
||||
(err instanceof Error ? err.message : 'Something went wrong');
|
||||
setServerError(msg);
|
||||
notify.error(msg);
|
||||
}
|
||||
};
|
||||
@@ -82,15 +87,19 @@ export function OTPVerificationPage() {
|
||||
if (secondsLeft > 0 || resending) return;
|
||||
try {
|
||||
await resendTrigger({
|
||||
url: '/auth/resend-otp',
|
||||
method: 'POST',
|
||||
body: { email },
|
||||
url: '/auth/generate-verification-code',
|
||||
method: 'PATCH',
|
||||
body: { email, phoneNumber, type: 'verify-phone-number' },
|
||||
}).unwrap();
|
||||
|
||||
notify.success('Verification code resent to your email');
|
||||
setSecondsLeft(RESEND_SECONDS);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Something went wrong';
|
||||
setServerError(null);
|
||||
} catch (err: unknown) {
|
||||
const msg =
|
||||
(err as { data?: { message?: string } })?.data?.message ??
|
||||
(err instanceof Error ? err.message : 'Something went wrong');
|
||||
setServerError(msg);
|
||||
notify.error(msg);
|
||||
}
|
||||
};
|
||||
@@ -118,6 +127,12 @@ export function OTPVerificationPage() {
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{serverError && (
|
||||
<Alert variant="light" color="red" withCloseButton onClose={() => setServerError(null)}>
|
||||
{serverError}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack gap="md">
|
||||
<Controller
|
||||
@@ -127,8 +142,8 @@ export function OTPVerificationPage() {
|
||||
<Stack gap={6} align="center">
|
||||
<PinInput
|
||||
length={CODE_LENGTH}
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
type="text"
|
||||
inputMode="text"
|
||||
oneTimeCode
|
||||
size="md"
|
||||
gap="sm"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { AuthState, AuthUser, LoginPayload } from '../types/auth.types';
|
||||
import type { AuthState, AuthUser, CurrentProfile, LoginPayload } from '../types/auth.types';
|
||||
import { authStorage } from '../utils/auth-storage';
|
||||
|
||||
const initialState: AuthState = {
|
||||
user: null,
|
||||
token: null,
|
||||
isAuthenticated: false,
|
||||
currentProfile: null,
|
||||
};
|
||||
|
||||
const authSlice = createSlice({
|
||||
@@ -22,23 +23,36 @@ const authSlice = createSlice({
|
||||
state.user = action.payload;
|
||||
authStorage.setUser(action.payload);
|
||||
},
|
||||
setCurrentProfile(state, action: PayloadAction<CurrentProfile>) {
|
||||
state.currentProfile = action.payload;
|
||||
authStorage.setProfile(action.payload);
|
||||
},
|
||||
clearCurrentProfile(state) {
|
||||
state.currentProfile = null;
|
||||
authStorage.removeProfile();
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
state.isAuthenticated = false;
|
||||
state.currentProfile = null;
|
||||
authStorage.clear();
|
||||
},
|
||||
hydrateAuth(state) {
|
||||
const token = authStorage.getToken();
|
||||
const user = authStorage.getUser<AuthUser>();
|
||||
const profile = authStorage.getProfile<CurrentProfile>();
|
||||
if (token && user) {
|
||||
state.token = token;
|
||||
state.user = user;
|
||||
state.isAuthenticated = true;
|
||||
if (profile) {
|
||||
state.currentProfile = profile;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { loginSuccess, setUser, logout, hydrateAuth } = authSlice.actions;
|
||||
export const { loginSuccess, setUser, setCurrentProfile, clearCurrentProfile, logout, hydrateAuth } = authSlice.actions;
|
||||
export const authReducer = authSlice.reducer;
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface AuthState {
|
||||
user: AuthUser | null;
|
||||
token: string | null;
|
||||
isAuthenticated: boolean;
|
||||
currentProfile: CurrentProfile | null;
|
||||
}
|
||||
|
||||
export interface LoginPayload {
|
||||
@@ -26,3 +27,57 @@ export interface LoginPayload {
|
||||
refreshToken: string;
|
||||
isPhoneNumberVerified: boolean;
|
||||
}
|
||||
|
||||
export interface CurrentProfileAddress {
|
||||
id: string;
|
||||
idType: string;
|
||||
idNumber: string;
|
||||
nationality: string;
|
||||
regionId: string | null;
|
||||
cityId: string | null;
|
||||
subCityId: string | null;
|
||||
woredaId: string | null;
|
||||
kebeleId: string | null;
|
||||
streetAddress: string | null;
|
||||
houseNumber: string | null;
|
||||
primaryPhoneNumber: string;
|
||||
secondaryPhoneNumber: string | null;
|
||||
email: string | null;
|
||||
website: string | null;
|
||||
postalAddress: string | null;
|
||||
emergencyContactName: string | null;
|
||||
emergencyContactPhone: string | null;
|
||||
emergencycontactRelation: string | null;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface CurrentProfileProfession {
|
||||
id: string;
|
||||
departmentId: string;
|
||||
name: { en: string };
|
||||
description: { en: string };
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface CurrentProfile {
|
||||
id: string;
|
||||
userId: string;
|
||||
professionId: string;
|
||||
addressId: string;
|
||||
type: string;
|
||||
firstName: string;
|
||||
middleName: string;
|
||||
lastName: string;
|
||||
gender: string;
|
||||
dob: string;
|
||||
pob: string;
|
||||
maritalStatus: string;
|
||||
isComplete: boolean;
|
||||
user: AuthUser;
|
||||
address: CurrentProfileAddress;
|
||||
profession: CurrentProfileProfession;
|
||||
}
|
||||
|
||||
export interface CurrentProfileState {
|
||||
profile: CurrentProfile | null;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,17 @@ export const authStorage = {
|
||||
setUser: <T>(u: T) => localStorage.setItem(key('auth-user'), JSON.stringify(u)),
|
||||
getProfileId: () => localStorage.getItem(key('profile-id')) ?? undefined,
|
||||
setProfileId: (id: string) => localStorage.setItem(key('profile-id'), id),
|
||||
getProfile: <T = unknown>(): T | null => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(key('current-profile')) ?? 'null') as T | null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setProfile: <T>(p: T) => localStorage.setItem(key('current-profile'), JSON.stringify(p)),
|
||||
removeProfile: () => localStorage.removeItem(key('current-profile')),
|
||||
clear: () => {
|
||||
[key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id')].forEach((k) =>
|
||||
[key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id'), key('current-profile')].forEach((k) =>
|
||||
localStorage.removeItem(k),
|
||||
);
|
||||
document.cookie =
|
||||
|
||||
Reference in New Issue
Block a user