From c106ac68d4acb1bdbc23db4f8ae5e7c93b9b362c Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Wed, 8 Jul 2026 13:36:52 +0300 Subject: [PATCH] fix: change the me endpoint to useQuery api --- .../features/profile/pages/ProfilePage.tsx | 31 +++-------------- .../user-management/UserManagementPage.tsx | 4 +-- apps/backoffice/src/app/i18n/config.ts | 2 +- apps/backoffice/src/app/store/index.ts | 3 -- .../src/app/store/preferences.slice.ts | 2 +- .../features/profile/pages/ProfilePage.tsx | 26 ++++---------- apps/portal/src/app/i18n/config.ts | 2 +- apps/portal/src/app/store/index.ts | 3 -- libs/api/src/lib/base-api/index.ts | 2 +- libs/api/src/lib/query-and-mutation/index.ts | 6 +++- libs/api/src/lib/session/index.ts | 6 +--- libs/auth/src/index.ts | 2 +- libs/auth/src/lib/utils/auth-storage.ts | 34 +++++++------------ 13 files changed, 36 insertions(+), 87 deletions(-) diff --git a/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx b/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx index e3f4ef131..98fc4289c 100644 --- a/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx @@ -43,8 +43,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { notify, PageHeader } from '@ema-platform/ui'; -import { useApiMutation } from '@ema-platform/api'; -import { setUser } from '@ema-platform/auth'; +import { useApiMutation, useGetMeQuery, baseApi } from '@ema-platform/api'; import type { AuthUser } from '@ema-platform/auth'; import { SUPPORTED_LANGUAGES, type AppLanguage } from '../../../i18n/config'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; @@ -74,12 +73,13 @@ function passwordScore(pw: string) { export function ProfilePage() { const { t, i18n } = useTranslation(); const dispatch = useAppDispatch(); - const user = useAppSelector((state) => state.auth.user); const { colorScheme, setColorScheme } = useMantineColorScheme(); const layoutMode = useAppSelector((state) => state.preferences.layoutMode); + const { data: me } = useGetMeQuery(); + const storeUser = useAppSelector((state) => state.auth.user); + const user = (me as AuthUser | undefined) ?? storeUser; const [updateTrigger] = useApiMutation(); - const [meTrigger] = useApiMutation(); const [passwordTrigger] = useApiMutation(); const [isSavingProfile, setIsSavingProfile] = useState(false); @@ -89,25 +89,6 @@ export function ProfilePage() { const [twoStepEnabled, setTwoStepEnabled] = useState(false); const [emailNotifications, setEmailNotifications] = useState(true); - // Load the latest profile from the server on mount so the form always - // reflects the current account information (the cached user may be stale). - useEffect(() => { - let active = true; - meTrigger({ url: '/auth/me', method: 'GET' }) - .unwrap() - .then((me) => { - if (active) dispatch(setUser(me)); - }) - .catch(() => { - /* fall back to the cached user already in the store */ - }); - return () => { - active = false; - }; - // meTrigger/dispatch are stable; run once on mount. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - // ---- Profile form ---- const profileSchema = z.object({ nameEn: z.string().min(1, { message: t('profile.validation.nameRequired') }), @@ -152,9 +133,7 @@ export function ProfilePage() { }, }).unwrap(); - const me = await meTrigger({ url: '/auth/me', method: 'GET' }).unwrap(); - dispatch(setUser(me)); - + dispatch(baseApi.util.invalidateTags(['Me'])); notify.success(t('profile.profileUpdated')); } catch { notify.error(t('profile.updateFailed')); diff --git a/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx b/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx index d6d89c47d..24f68ca88 100644 --- a/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx +++ b/apps/backoffice/src/app/features/user-management/UserManagementPage.tsx @@ -138,8 +138,8 @@ export default function UserManagementPage() { style.textContent = UM_OVERRIDES; document.head.appendChild(style); - const token = localStorage.getItem('ema-backoffice-auth-token') ?? ''; - const refreshToken = localStorage.getItem('ema-backoffice-refresh-token') ?? undefined; + const token = localStorage.getItem('auth-token') ?? ''; + const refreshToken = localStorage.getItem('refresh-token') ?? undefined; const session: UserManagementSessionOptions = { initialSession: token diff --git a/apps/backoffice/src/app/i18n/config.ts b/apps/backoffice/src/app/i18n/config.ts index b506b2ce0..b16d34633 100644 --- a/apps/backoffice/src/app/i18n/config.ts +++ b/apps/backoffice/src/app/i18n/config.ts @@ -6,7 +6,7 @@ import { am } from './locales/am'; export const SUPPORTED_LANGUAGES = ['en', 'am'] as const; export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]; -const STORAGE_KEY = 'ema-backoffice-lang'; +const STORAGE_KEY = 'app-lang'; function getInitialLanguage(): AppLanguage { const stored = diff --git a/apps/backoffice/src/app/store/index.ts b/apps/backoffice/src/app/store/index.ts index bb3ca733e..cb75f33ce 100644 --- a/apps/backoffice/src/app/store/index.ts +++ b/apps/backoffice/src/app/store/index.ts @@ -3,7 +3,6 @@ import { baseApi, configureTokenRefresh } from '@ema-platform/api'; import { authReducer, signupReducer, - configureAuthStorage, authStorage, refreshAccessToken, logout, @@ -11,8 +10,6 @@ import { import type { AuthUser, CurrentProfile } from '@ema-platform/auth'; import { preferencesReducer } from './preferences.slice'; -configureAuthStorage('ema-backoffice'); - const preloadedAuth = (() => { const token = authStorage.getToken(); const user = authStorage.getUser(); diff --git a/apps/backoffice/src/app/store/preferences.slice.ts b/apps/backoffice/src/app/store/preferences.slice.ts index 3b25d4b87..7201419d6 100644 --- a/apps/backoffice/src/app/store/preferences.slice.ts +++ b/apps/backoffice/src/app/store/preferences.slice.ts @@ -6,7 +6,7 @@ interface PreferencesState { layoutMode: LayoutMode; } -const PREFERENCES_KEY = 'ema-backoffice-preferences'; +const PREFERENCES_KEY = 'app-preferences'; const loadPreferences = (): PreferencesState => { try { diff --git a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx index 97ba640ce..e5c21809e 100644 --- a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx @@ -44,8 +44,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { notify, PageHeader } from '@ema-platform/ui'; -import { useApiMutation } from '@ema-platform/api'; -import { authStorage, setUser, setCurrentProfile } from '@ema-platform/auth'; +import { useApiMutation, useGetMeQuery, baseApi } from '@ema-platform/api'; +import { authStorage, setCurrentProfile } 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'; @@ -83,12 +83,13 @@ function passwordScore(pw: string) { export function ProfilePage() { const { t, i18n } = useTranslation(); const dispatch = useAppDispatch(); - const user = useAppSelector((state) => state.auth.user); const currentProfile = useAppSelector((state) => state.auth.currentProfile); const { colorScheme, setColorScheme } = useMantineColorScheme(); + const { data: me } = useGetMeQuery(); + const storeUser = useAppSelector((state) => state.auth.user); + const user = (me as AuthUser | undefined) ?? storeUser; const [updateTrigger] = useApiMutation(); - const [meTrigger] = useApiMutation(); const [passwordTrigger] = useApiMutation(); const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string } }> }>(); @@ -198,19 +199,6 @@ export function ProfilePage() { } }, [currentProfile, user, fetchProfile, dispatch]); - // 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(() => {}); - 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') }), @@ -251,9 +239,7 @@ export function ProfilePage() { }, }).unwrap(); - const me = await meTrigger({ url: '/auth/me', method: 'GET' }).unwrap(); - dispatch(setUser(me)); - + dispatch(baseApi.util.invalidateTags(['Me'])); notify.success(t('profile.profileUpdated')); } catch { notify.error(t('profile.updateFailed')); diff --git a/apps/portal/src/app/i18n/config.ts b/apps/portal/src/app/i18n/config.ts index 97e49cb8c..a6572c81a 100644 --- a/apps/portal/src/app/i18n/config.ts +++ b/apps/portal/src/app/i18n/config.ts @@ -6,7 +6,7 @@ import { am } from './locales/am'; export const SUPPORTED_LANGUAGES = ['en', 'am'] as const; export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]; -const STORAGE_KEY = 'ema-portal-lang'; +const STORAGE_KEY = 'app-lang'; function getInitialLanguage(): AppLanguage { const stored = diff --git a/apps/portal/src/app/store/index.ts b/apps/portal/src/app/store/index.ts index a48e46406..2fc47fe89 100644 --- a/apps/portal/src/app/store/index.ts +++ b/apps/portal/src/app/store/index.ts @@ -3,15 +3,12 @@ import { baseApi, configureTokenRefresh } from '@ema-platform/api'; import { authReducer, signupReducer, - configureAuthStorage, authStorage, refreshAccessToken, logout, } from '@ema-platform/auth'; import type { AuthUser, CurrentProfile } from '@ema-platform/auth'; -configureAuthStorage('ema-portal'); - const preloadedAuth = (() => { const token = authStorage.getToken(); const user = authStorage.getUser(); diff --git a/libs/api/src/lib/base-api/index.ts b/libs/api/src/lib/base-api/index.ts index 0570a2d88..624526140 100644 --- a/libs/api/src/lib/base-api/index.ts +++ b/libs/api/src/lib/base-api/index.ts @@ -4,6 +4,6 @@ import { baseQueryWithReauth } from './base-query-with-reauth'; export const baseApi = createApi({ reducerPath: 'baseApi', baseQuery: baseQueryWithReauth, - tagTypes: ['Api'], + tagTypes: ['Api', 'Me'], endpoints: () => ({}), }); diff --git a/libs/api/src/lib/query-and-mutation/index.ts b/libs/api/src/lib/query-and-mutation/index.ts index 10b736e11..d1e3c2baa 100644 --- a/libs/api/src/lib/query-and-mutation/index.ts +++ b/libs/api/src/lib/query-and-mutation/index.ts @@ -22,11 +22,15 @@ const queryApi = baseApi.injectEndpoints({ headers, }), }), + getMe: builder.query({ + query: () => '/auth/me', + providesTags: ['Me'], + }), }), overrideExisting: false, }); -export const { useApiQueryQuery, useApiMutationMutation } = queryApi; +export const { useApiQueryQuery, useApiMutationMutation, useGetMeQuery } = queryApi; export function useApiQuery( args: ApiQueryArgs, diff --git a/libs/api/src/lib/session/index.ts b/libs/api/src/lib/session/index.ts index 7f7a25284..bd95d1d73 100644 --- a/libs/api/src/lib/session/index.ts +++ b/libs/api/src/lib/session/index.ts @@ -5,11 +5,7 @@ export const SESSION_HEADER_KEYS = { currentProjectId: 'x-current-project-id', } as const; -const TOKEN_STORAGE_KEYS = [ - 'ema-backoffice-auth-token', - 'ema-portal-auth-token', - 'auth-token', -] as const; +const TOKEN_STORAGE_KEYS = ['auth-token'] as const; export function resolveTokenFromStorage(): string | undefined { for (const key of TOKEN_STORAGE_KEYS) { diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts index f1919dd50..7c178a615 100644 --- a/libs/auth/src/index.ts +++ b/libs/auth/src/index.ts @@ -8,6 +8,6 @@ export { ForgotPasswordPage } from './lib/pages/ForgotPasswordPage'; export { OTPVerificationPage } from './lib/pages/OTPVerificationPage'; 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 { authStorage } from './lib/utils/auth-storage'; export { refreshAccessToken } from './lib/utils/refresh-token'; export type { AuthUser, AuthState, LoginPayload, CurrentProfile, CurrentProfileAddress, CurrentProfileProfession } from './lib/types/auth.types'; diff --git a/libs/auth/src/lib/utils/auth-storage.ts b/libs/auth/src/lib/utils/auth-storage.ts index 22fc1757f..a78f98cf1 100644 --- a/libs/auth/src/lib/utils/auth-storage.ts +++ b/libs/auth/src/lib/utils/auth-storage.ts @@ -1,39 +1,29 @@ -let _prefix = 'ema-auth'; - -export function configureAuthStorage(prefix: string) { - _prefix = prefix; -} - -function key(k: string) { - return `${_prefix}-${k}`; -} - export const authStorage = { - getToken: () => localStorage.getItem(key('auth-token')) ?? undefined, - setToken: (token: string) => localStorage.setItem(key('auth-token'), token), - getRefreshToken: () => localStorage.getItem(key('refresh-token')) ?? undefined, - setRefreshToken: (t: string) => localStorage.setItem(key('refresh-token'), t), + getToken: () => localStorage.getItem('auth-token') ?? undefined, + setToken: (token: string) => localStorage.setItem('auth-token', token), + getRefreshToken: () => localStorage.getItem('refresh-token') ?? undefined, + setRefreshToken: (t: string) => localStorage.setItem('refresh-token', t), getUser: (): T | null => { try { - return JSON.parse(localStorage.getItem(key('auth-user')) ?? 'null') as T | null; + return JSON.parse(localStorage.getItem('auth-user') ?? 'null') as T | null; } catch { return null; } }, - setUser: (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), + setUser: (u: T) => localStorage.setItem('auth-user', JSON.stringify(u)), + getProfileId: () => localStorage.getItem('profile-id') ?? undefined, + setProfileId: (id: string) => localStorage.setItem('profile-id', id), getProfile: (): T | null => { try { - return JSON.parse(localStorage.getItem(key('current-profile')) ?? 'null') as T | null; + return JSON.parse(localStorage.getItem('current-profile') ?? 'null') as T | null; } catch { return null; } }, - setProfile: (p: T) => localStorage.setItem(key('current-profile'), JSON.stringify(p)), - removeProfile: () => localStorage.removeItem(key('current-profile')), + setProfile: (p: T) => localStorage.setItem('current-profile', JSON.stringify(p)), + removeProfile: () => localStorage.removeItem('current-profile'), clear: () => { - [key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id'), key('current-profile')].forEach((k) => + ['auth-token', 'refresh-token', 'auth-user', 'profile-id', 'current-profile'].forEach((k) => localStorage.removeItem(k), ); document.cookie =