Skip to content

Commit d01dd98

Browse files
authored
fix(account-center): handle session expiration (#8004)
1 parent 929c08e commit d01dd98

File tree

8 files changed

+77
-6
lines changed

8 files changed

+77
-6
lines changed

packages/account-center/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import PageContextProvider from './Providers/PageContextProvider';
1212
import PageContext from './Providers/PageContextProvider/PageContext';
1313
import BrandingHeader from './components/BrandingHeader';
1414
import ErrorPage from './components/ErrorPage';
15+
import { emailRoute, sessionExpiredRoute } from './constants/routes';
1516
import initI18n from './i18n/init';
1617
import Email from './pages/Email';
1718
import Home from './pages/Home';
19+
import SessionExpired from './pages/SessionExpired';
1820
import { accountCenterBasePath, handleAccountCenterRoute } from './utils/account-center-route';
1921

2022
import '@experience/shared/scss/normalized.scss';
@@ -64,7 +66,8 @@ const Main = () => {
6466

6567
return (
6668
<Routes>
67-
<Route path="email" element={<Email />} />
69+
<Route path={sessionExpiredRoute} element={<SessionExpired />} />
70+
<Route path={emailRoute} element={<Email />} />
6871
<Route index element={<Home />} />
6972
<Route path="*" element={<Home />} />
7073
</Routes>

packages/account-center/src/Providers/PageContextProvider/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { useLogto } from '@logto/react';
22
import { Theme } from '@logto/schemas';
3+
import { HTTPError } from 'ky';
34
import { useCallback, useEffect, useMemo, useState } from 'react';
5+
import { useNavigate } from 'react-router-dom';
46

57
import { getAccountCenterSettings } from '@ac/apis/account-center';
68
import { getSignInExperienceSettings } from '@ac/apis/sign-in-experience';
79
import { getUserInfo } from '@ac/apis/user';
10+
import { sessionExpiredRoute } from '@ac/constants/routes';
811
import { getThemeBySystemPreference, subscribeToSystemTheme } from '@ac/utils/theme';
912

1013
import type { PageContextType } from './PageContext';
@@ -21,6 +24,7 @@ type Props = {
2124

2225
const PageContextProvider = ({ children }: Props) => {
2326
const { isAuthenticated, getAccessToken } = useLogto();
27+
const navigate = useNavigate();
2428
const [theme, setTheme] = useState(Theme.Light);
2529
const [toast, setToast] = useState('');
2630
const [experienceSettings, setExperienceSettings] =
@@ -72,14 +76,19 @@ const PageContextProvider = ({ children }: Props) => {
7276
setUserInfo(data);
7377
setUserInfoError(undefined);
7478
} catch (error: unknown) {
79+
if (error instanceof HTTPError && error.response.status === 401) {
80+
void navigate(sessionExpiredRoute, { replace: true });
81+
return;
82+
}
83+
7584
setUserInfoError(
7685
error instanceof Error ? error : new Error('Failed to load user information.')
7786
);
7887
}
7988
};
8089

8190
void fetchUserInfo();
82-
}, [isAuthenticated, getAccessToken]);
91+
}, [isAuthenticated, getAccessToken, navigate]);
8392

8493
useEffect(() => {
8594
const loadSettings = async () => {
@@ -94,6 +103,11 @@ const PageContextProvider = ({ children }: Props) => {
94103
setAccountCenterSettings(accountCenter);
95104
setExperienceError(undefined);
96105
} catch (error: unknown) {
106+
if (error instanceof HTTPError && error.response.status === 401) {
107+
void navigate(sessionExpiredRoute, { replace: true });
108+
return;
109+
}
110+
97111
setExperienceSettings(undefined);
98112
setAccountCenterSettings(undefined);
99113
setExperienceError(
@@ -105,7 +119,7 @@ const PageContextProvider = ({ children }: Props) => {
105119
};
106120

107121
void loadSettings();
108-
}, []);
122+
}, [navigate]);
109123

110124
useEffect(() => {
111125
if (!experienceSettings?.color.isDarkModeEnabled) {

packages/account-center/src/components/ErrorPage/index.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@
4545
text-decoration: underline;
4646
}
4747
}
48+
49+
.action {
50+
align-self: center;
51+
}

packages/account-center/src/components/ErrorPage/index.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import EmptyStateDark from '@experience/assets/icons/empty-state-dark.svg';
22
import EmptyState from '@experience/assets/icons/empty-state.svg';
3+
import Button from '@experience/shared/components/Button';
34
import DynamicT from '@experience/shared/components/DynamicT';
45
import PageMeta from '@experience/shared/components/PageMeta';
56
import { Theme } from '@logto/schemas';
@@ -15,6 +16,10 @@ type Props = {
1516
readonly titleKey?: TFuncKey;
1617
readonly messageKey?: TFuncKey;
1718
readonly rawMessage?: string;
19+
readonly action?: {
20+
titleKey: TFuncKey;
21+
onClick: () => void;
22+
};
1823
};
1924

2025
const SupportInfo = () => {
@@ -69,7 +74,12 @@ const SupportInfo = () => {
6974
);
7075
};
7176

72-
const ErrorPage = ({ titleKey = 'description.not_found', messageKey, rawMessage }: Props) => {
77+
const ErrorPage = ({
78+
titleKey = 'description.not_found',
79+
messageKey,
80+
rawMessage,
81+
action,
82+
}: Props) => {
7383
const { theme } = useContext(PageContext);
7484
const message = rawMessage ?? (messageKey ? <DynamicT forKey={messageKey} /> : undefined);
7585
const illustration = theme === Theme.Light ? EmptyState : EmptyStateDark;
@@ -84,6 +94,9 @@ const ErrorPage = ({ titleKey = 'description.not_found', messageKey, rawMessage
8494
<DynamicT forKey={titleKey} />
8595
</div>
8696
{message && <div className={styles.message}>{message}</div>}
97+
{action && (
98+
<Button className={styles.action} title={action.titleKey} onClick={action.onClick} />
99+
)}
87100
<SupportInfo />
88101
</div>
89102
);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const sessionExpiredRoute = '/session-expired';
2+
export const emailRoute = '/email';

packages/account-center/src/hooks/use-error-handler.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { RequestErrorBody } from '@logto/schemas';
22
import { HTTPError, TimeoutError } from 'ky';
33
import { useCallback, useContext } from 'react';
44
import { useTranslation } from 'react-i18next';
5+
import { useNavigate } from 'react-router-dom';
56

67
import PageContext from '@ac/Providers/PageContextProvider/PageContext';
8+
import { sessionExpiredRoute } from '@ac/constants/routes';
79

810
export type ErrorHandlers = {
911
[key in RequestErrorBody['code']]?: (error: RequestErrorBody) => void | Promise<void>;
@@ -15,10 +17,16 @@ export type ErrorHandlers = {
1517
const useErrorHandler = () => {
1618
const { t } = useTranslation();
1719
const { setToast } = useContext(PageContext);
20+
const navigate = useNavigate();
1821

1922
const handleError = useCallback(
2023
async (error: unknown, errorHandlers?: ErrorHandlers) => {
2124
if (error instanceof HTTPError) {
25+
if (error.response.status === 401) {
26+
void navigate(sessionExpiredRoute, { replace: true });
27+
return;
28+
}
29+
2230
try {
2331
const logtoError = await error.response.json<RequestErrorBody>();
2432

@@ -50,7 +58,7 @@ const useErrorHandler = () => {
5058
setToast(t('error.unknown'));
5159
console.error(error);
5260
},
53-
[setToast, t]
61+
[navigate, setToast, t]
5462
);
5563

5664
return handleError;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useLogto } from '@logto/react';
2+
3+
import ErrorPage from '@ac/components/ErrorPage';
4+
import { accountCenterBasePath } from '@ac/utils/account-center-route';
5+
6+
const redirectUri = `${window.location.origin}${accountCenterBasePath}`;
7+
8+
const SessionExpired = () => {
9+
const { signIn } = useLogto();
10+
11+
return (
12+
<ErrorPage
13+
titleKey="error.something_went_wrong"
14+
messageKey="error.invalid_session"
15+
action={{
16+
titleKey: 'action.sign_in',
17+
onClick: () => {
18+
void signIn({ redirectUri });
19+
},
20+
}}
21+
/>
22+
);
23+
};
24+
25+
export default SessionExpired;

packages/account-center/src/utils/account-center-route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { emailRoute } from '@ac/constants/routes';
2+
13
export const accountCenterBasePath = '/account-center';
24
const storageKey = 'account-center-route-cache';
35

4-
const knownRoutePrefixes: readonly string[] = ['/email', '/phone', '/username', '/password'];
6+
const knownRoutePrefixes: readonly string[] = [emailRoute, '/phone', '/username', '/password'];
57

68
const isKnownRoute = (pathname?: string): pathname is string =>
79
pathname !== undefined &&

0 commit comments

Comments
 (0)