diff --git a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx index 14f709b8b..28a38695a 100644 --- a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx @@ -50,7 +50,6 @@ import { useTranslation } from 'react-i18next'; import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements, getCountryCode } from '@ema-platform/ui'; import { useApiMutation } from '@ema-platform/api'; import { setUser, useCurrentProfile } from '@ema-platform/auth'; -import type { CurrentProfile } from '@ema-platform/auth'; import { SUPPORTED_LANGUAGES, type AppLanguage } from '../../../i18n/config'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import type { AuthUser } from '@ema-platform/auth'; @@ -106,7 +105,6 @@ export function ProfilePage() { const { handleError } = useErrorHandler(); const [updateTrigger] = useApiMutation(); - const [meTrigger] = useApiMutation(); const [passwordTrigger] = useApiMutation(); const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string } }> }>(); @@ -137,12 +135,6 @@ export function ProfilePage() { [professions], ); - const professionNameMap = useMemo(() => { - const map: Record = {}; - professions.forEach((p) => { map[p.id] = p.name.en; }); - return map; - }, [professions]); - // ---- Profile data ---- // Resolved through `useCurrentProfile`, which provisions a profile if the // user has none. The page used to read an id out of local storage that only @@ -227,21 +219,6 @@ export function ProfilePage() { } }, [resolvedProfile, storedProfile, profileResolving, user]); - // Load the latest user from the server on mount - useEffect(() => { - let active = true; - meTrigger({ url: '/auth/me', method: 'GET' }) - .unwrap() - .then((me) => { - if (active) dispatch(setUser(me)); - }) - .catch(() => { - // Best-effort refresh; the store already holds the user from sign-in. - }); - return () => { active = false; }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - // ---- Personal form (auth user data) ---- const personalSchema = z.object({ nameEn: z.string().min(1, { message: t('profile.validation.nameRequired') }), diff --git a/libs/auth/src/lib/components/AuthBootstrap.tsx b/libs/auth/src/lib/components/AuthBootstrap.tsx index 03eef5a7a..769864f6f 100644 --- a/libs/auth/src/lib/components/AuthBootstrap.tsx +++ b/libs/auth/src/lib/components/AuthBootstrap.tsx @@ -30,6 +30,7 @@ export function AuthBootstrap({ children }: { children: ReactNode }) { dispatch(hydrateAuth()); const token = authStorage.getToken(); + const cachedUser = authStorage.getUser(); if (!token) { if (!cancelled) setReady(true); return; @@ -42,7 +43,12 @@ export function AuthBootstrap({ children }: { children: ReactNode }) { if (response.ok) { const user = (await response.json()) as AuthUser; - if (!cancelled) dispatch(setUser(user)); + // Keep the persisted session as the source of truth when it is + // available. A successful profile update writes it immediately, + // while `/auth/me` can briefly return a stale read and otherwise + // undo that update on every page refresh. We still make this call + // to validate the token and clear invalid sessions below. + if (!cancelled && !cachedUser) dispatch(setUser(user)); } else if (response.status === 401 || response.status === 403) { // Expired or revoked — drop it so the user gets a login screen // instead of a dead end.