diff --git a/apps/portal/src/app/components/ProfileGuard.tsx b/apps/portal/src/app/components/ProfileGuard.tsx
new file mode 100644
index 000000000..c9328918a
--- /dev/null
+++ b/apps/portal/src/app/components/ProfileGuard.tsx
@@ -0,0 +1,12 @@
+import { Navigate, Outlet } from 'react-router-dom';
+import { authStorage } from '@ema-platform/auth';
+
+export function ProfileGuard() {
+ const profileId = authStorage.getProfileId();
+
+ if (!profileId) {
+ return ;
+ }
+
+ return ;
+}
diff --git a/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx b/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx
index 0c42d7369..5d9c5360c 100644
--- a/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx
+++ b/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx
@@ -19,6 +19,7 @@ import {
IconArrowRight,
IconCheck,
IconCircleCheck,
+ IconLogout2,
IconMapPin,
IconUser,
} from '@tabler/icons-react';
@@ -28,7 +29,7 @@ import { z } from 'zod';
import { useNavigate } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
import { notify } from '@ema-platform/ui';
-import { authStorage, setUser } from '@ema-platform/auth';
+import { authStorage, setUser, logout } from '@ema-platform/auth';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
const GENDERS = ['MALE', 'FEMALE'];
@@ -159,9 +160,12 @@ export function ProfileSetupPage() {
const [profileTrigger] = useApiMutation<{ id: string }>();
const [addressTrigger] = useApiMutation();
const [meTrigger] = useApiMutation<{ id: string }>();
+ const [profileCheckTrigger] = useApiMutation<{ count: number; items: Array<{ id: string; userId: string }> }>();
const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string } }> }>();
const fetched = useRef(false);
+ const checkedExistingProfile = useRef(false);
+
useEffect(() => {
if (fetched.current) return;
fetched.current = true;
@@ -172,6 +176,21 @@ export function ProfileSetupPage() {
.finally(() => setProfessionsLoading(false));
}, [fetchProfessions]);
+ useEffect(() => {
+ if (!user || checkedExistingProfile.current) return;
+ checkedExistingProfile.current = true;
+ profileCheckTrigger({ url: '/profiles?take=1&skip=0', method: 'GET' })
+ .unwrap()
+ .then((data) => {
+ const existing = data.items?.find((p) => p.userId === user.id);
+ if (existing) {
+ authStorage.setProfileId(existing.id);
+ navigate('/dashboard', { replace: true });
+ }
+ })
+ .catch(() => {});
+ }, [user, navigate, profileCheckTrigger]);
+
const professionOptions = useMemo(
() => professions.map((p) => ({ value: p.id, label: p.name.en })),
[professions],
@@ -185,6 +204,37 @@ export function ProfileSetupPage() {
return map;
}, [professions]);
+ const nameParts = useMemo(() => (user?.name?.en || '').trim().split(/\s+/), [user]);
+ const profileDefaults: ProfileValues = useMemo(() => ({
+ professionId: '',
+ firstName: nameParts[0] || '',
+ middleName: nameParts.length > 2 ? nameParts.slice(1, -1).join(' ') : '',
+ lastName: nameParts.length > 1 ? nameParts[nameParts.length - 1] : '',
+ gender: '',
+ dob: '',
+ pob: '',
+ maritalStatus: '',
+ }), [nameParts]);
+
+ const addressDefaults: AddressValues = useMemo(() => ({
+ idType: '',
+ idNumber: '',
+ nationality: '',
+ primaryPhoneNumber: user?.phoneNumber || '',
+ secondaryPhoneNumber: '',
+ email: user?.email || '',
+ regionId: '',
+ cityId: '',
+ subcityId: '',
+ woredaId: '',
+ kebeleId: '',
+ streetAddress: '',
+ postalAddress: '',
+ emergencyContactName: '',
+ emergencyContactPhone: '',
+ emergencyContactRelation: '',
+ }), [user]);
+
const {
register: profileRegister,
handleSubmit: profileHandleSubmit,
@@ -194,16 +244,7 @@ export function ProfileSetupPage() {
trigger: profileTriggerValidation,
} = useForm({
resolver: zodResolver(profileSchema),
- defaultValues: {
- professionId: '',
- firstName: '',
- middleName: '',
- lastName: '',
- gender: '',
- dob: '',
- pob: '',
- maritalStatus: '',
- },
+ defaultValues: profileDefaults,
});
const {
@@ -215,24 +256,7 @@ export function ProfileSetupPage() {
trigger: addressTriggerValidation,
} = useForm({
resolver: zodResolver(addressSchema),
- defaultValues: {
- idType: '',
- idNumber: '',
- nationality: '',
- primaryPhoneNumber: '',
- secondaryPhoneNumber: '',
- email: '',
- regionId: '',
- cityId: '',
- subcityId: '',
- woredaId: '',
- kebeleId: '',
- streetAddress: '',
- postalAddress: '',
- emergencyContactName: '',
- emergencyContactPhone: '',
- emergencyContactRelation: '',
- },
+ defaultValues: addressDefaults,
});
const onNext = async () => {
@@ -528,8 +552,16 @@ export function ProfileSetupPage() {
)}
-
{active > 0 && (
diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx
index 132ae8c00..361e1bf2a 100644
--- a/apps/portal/src/app/router.tsx
+++ b/apps/portal/src/app/router.tsx
@@ -3,6 +3,7 @@ import { I18nextProvider } from 'react-i18next';
import { i18n } from './i18n/config';
import { PortalLayout } from './layouts/PortalLayout';
import { ProtectedRoute } from './components/ProtectedRoute';
+import { ProfileGuard } from './components/ProfileGuard';
// Auth (standalone pages, no portal chrome)
import { LoginPage, SignupPage, OTPVerificationPage, ForgotPasswordPage } from '@ema-platform/auth';
@@ -54,9 +55,11 @@ export const router = createBrowserRouter([
{
element: (
-
-
-
+
+
+
+
+
),
children: [
diff --git a/libs/auth/src/lib/pages/LoginPage.tsx b/libs/auth/src/lib/pages/LoginPage.tsx
index 3c33b8f41..169dde10f 100644
--- a/libs/auth/src/lib/pages/LoginPage.tsx
+++ b/libs/auth/src/lib/pages/LoginPage.tsx
@@ -28,6 +28,7 @@ import { AuthShell } from '../components/AuthShell';
import { loginSuccess, setUser } from '../store/auth.slice';
import type { LoginPayload, AuthUser } from '../types/auth.types';
import { useAuthConfig } from '../AuthConfig';
+import { authStorage } from '../utils/auth-storage';
const schema = z.object({
email: z.string().email({ message: 'Enter a valid email' }),
@@ -76,12 +77,14 @@ export function LoginPage() {
url: '/profiles?take=10000&skip=0',
method: 'GET',
}).unwrap();
- const hasProfile = profiles.items?.some((p) => p.userId === me.id);
+ const userProfile = profiles.items?.find((p) => p.userId === me.id);
- if (!hasProfile) {
+ if (!userProfile) {
navigate('/profile-setup');
return;
}
+
+ authStorage.setProfileId(userProfile.id);
} catch {
// profile check failed — proceed to dashboard anyway
}
diff --git a/libs/auth/src/lib/pages/OTPVerificationPage.tsx b/libs/auth/src/lib/pages/OTPVerificationPage.tsx
index f39b93dc7..6c3172355 100644
--- a/libs/auth/src/lib/pages/OTPVerificationPage.tsx
+++ b/libs/auth/src/lib/pages/OTPVerificationPage.tsx
@@ -37,10 +37,11 @@ export function OTPVerificationPage() {
const location = useLocation();
const { loginRedirectPath } = useAuthConfig();
const state = location.state as
- | { email?: string; phoneNumber?: string }
+ | { email?: string; phoneNumber?: string; needsProfile?: boolean }
| null;
const email = state?.email ?? '';
const phoneNumber = state?.phoneNumber ?? '';
+ const needsProfile = state?.needsProfile ?? false;
const [verifyTrigger, { isLoading: loading }] = useApiMutation();
const [resendTrigger, { isLoading: resending }] = useApiMutation();
@@ -70,7 +71,7 @@ export function OTPVerificationPage() {
}).unwrap();
notify.success('Phone number verified successfully');
- navigate(loginRedirectPath);
+ navigate(needsProfile ? '/profile-setup' : loginRedirectPath);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Something went wrong';
notify.error(msg);
diff --git a/libs/auth/src/lib/pages/SignupPage.tsx b/libs/auth/src/lib/pages/SignupPage.tsx
index 676a82676..853eb1e74 100644
--- a/libs/auth/src/lib/pages/SignupPage.tsx
+++ b/libs/auth/src/lib/pages/SignupPage.tsx
@@ -27,7 +27,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 } from '../store/auth.slice';
+import { loginSuccess, setUser } from '../store/auth.slice';
+import type { AuthUser } from '../types/auth.types';
import { useAuthConfig } from '../AuthConfig';
const schema = z
@@ -71,6 +72,7 @@ export function SignupPage() {
refreshToken: string;
isPhoneNumberVerified: boolean;
}>();
+ const [meTrigger] = useApiMutation();
const {
register,
@@ -107,11 +109,18 @@ export function SignupPage() {
}),
);
+ const me = await meTrigger({ url: '/auth/me', method: 'GET' }).unwrap();
+ dispatch(setUser(me));
+
if (data.isPhoneNumberVerified) {
- navigate(loginRedirectPath);
+ navigate('/profile-setup');
} else {
navigate('/otp-verify', {
- state: { email: values.email, phoneNumber: values.phoneNumber },
+ state: {
+ email: values.email,
+ phoneNumber: values.phoneNumber,
+ needsProfile: true,
+ },
});
}
} catch (err) {
diff --git a/local-packages/tria-plc-iamui-0.0.3.tgz b/local-packages/tria-plc-iamui-0.1.1.tgz
similarity index 50%
rename from local-packages/tria-plc-iamui-0.0.3.tgz
rename to local-packages/tria-plc-iamui-0.1.1.tgz
index 5fb9f439c..6f3ffaf4e 100644
Binary files a/local-packages/tria-plc-iamui-0.0.3.tgz and b/local-packages/tria-plc-iamui-0.1.1.tgz differ
diff --git a/package-lock.json b/package-lock.json
index 52af32cb4..7c702ace9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"@reduxjs/toolkit": "^2.11.2",
"@tabler/icons-react": "^3.40.0",
"@tanstack/react-query": "^5.99.0",
- "@tria-plc/iamui": "file:local-packages/tria-plc-iamui-0.0.3.tgz",
+ "@tria-plc/iamui": "file:local-packages/tria-plc-iamui-0.1.1.tgz",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.20",
@@ -8632,9 +8632,9 @@
"license": "MIT"
},
"node_modules/@tria-plc/iamui": {
- "version": "0.0.3",
- "resolved": "file:local-packages/tria-plc-iamui-0.0.3.tgz",
- "integrity": "sha512-aZhIeNq2Uui7TUkG88G9QTBqdhaVB5kvqfd47HLgVE7qKkVyfjlm4nwlSWnHh5MO+CjgP9vRi6ILwMJx1ULO8g==",
+ "version": "0.1.1",
+ "resolved": "file:local-packages/tria-plc-iamui-0.1.1.tgz",
+ "integrity": "sha512-FTihoH0lIqKV/0s+FTJZokQw8xX3XztXIqT8eEYEZgDM2xyzVSCHY8pHCUxw0MQtiR8R97zxZJ/o192eWt+E/g==",
"license": "UNLICENSED",
"dependencies": {
"@emotion/react": "^11.14.0",
diff --git a/package.json b/package.json
index 052f93161..7d291d660 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"@reduxjs/toolkit": "^2.11.2",
"@tabler/icons-react": "^3.40.0",
"@tanstack/react-query": "^5.99.0",
- "@tria-plc/iamui": "file:local-packages/tria-plc-iamui-0.0.3.tgz",
+ "@tria-plc/iamui": "file:local-packages/tria-plc-iamui-0.1.1.tgz",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.20",