Skip to content

Commit 749640d

Browse files
committed
feat(account-center): add error page
1 parent afbad7d commit 749640d

File tree

40 files changed

+258
-75
lines changed

40 files changed

+258
-75
lines changed

packages/account-center/src/App.module.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
@include _.flex_column;
1818
}
1919

20+
.status {
21+
@include _.flex_column(center, center);
22+
text-align: center;
23+
gap: _.unit(2);
24+
flex: 1;
25+
}
26+
2027
:global(body.mobile) {
2128
.container {
2229
padding-bottom: env(safe-area-inset-bottom);

packages/account-center/src/App.tsx

Lines changed: 14 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import AppBoundary from '@experience/Providers/AppBoundary';
2-
import { type IdTokenClaims, LogtoProvider, useLogto } from '@logto/react';
1+
import { LogtoProvider, useLogto } from '@logto/react';
32
import { accountCenterApplicationId } from '@logto/schemas';
4-
import { useContext, useEffect, useState } from 'react';
3+
import { useContext, useEffect } from 'react';
54

65
import AppBoundary from '@ac/Providers/AppBoundary';
76

@@ -10,7 +9,9 @@ import Callback from './Callback';
109
import PageContextProvider from './Providers/PageContextProvider';
1110
import PageContext from './Providers/PageContextProvider/PageContext';
1211
import BrandingHeader from './components/BrandingHeader';
12+
import ErrorPage from './components/ErrorPage';
1313
import initI18n from './i18n/init';
14+
import Home from './pages/Home';
1415

1516
import '@experience/shared/scss/normalized.scss';
1617

@@ -21,32 +22,9 @@ const redirectUri = `${window.location.origin}/account-center`;
2122
const Main = () => {
2223
const params = new URLSearchParams(window.location.search);
2324
const isInCallback = Boolean(params.get('code'));
24-
const { isAuthenticated, isLoading, getIdTokenClaims, signIn } = useLogto();
25-
const [user, setUser] = useState<Pick<IdTokenClaims, 'sub' | 'username'>>();
26-
const [isLoadingUser, setIsLoadingUser] = useState(false);
25+
const { isAuthenticated, isLoading, signIn } = useLogto();
2726
const { isLoadingExperience, experienceError } = useContext(PageContext);
2827

29-
useEffect(() => {
30-
if (!isAuthenticated) {
31-
setUser(undefined);
32-
setIsLoadingUser(false);
33-
return;
34-
}
35-
36-
const loadUser = async () => {
37-
setIsLoadingUser(true);
38-
39-
try {
40-
const claims = await getIdTokenClaims();
41-
setUser(claims ?? undefined);
42-
} finally {
43-
setIsLoadingUser(false);
44-
}
45-
};
46-
47-
void loadUser();
48-
}, [getIdTokenClaims, isAuthenticated]);
49-
5028
useEffect(() => {
5129
if (isInCallback || isLoading) {
5230
return;
@@ -58,44 +36,27 @@ const Main = () => {
5836
}, [isAuthenticated, isInCallback, isLoading, signIn]);
5937

6038
if (isInCallback) {
61-
return (
62-
<div>
63-
<Callback />
64-
</div>
65-
);
39+
return <Callback />;
6640
}
6741

6842
if (experienceError) {
6943
return (
70-
<div>
71-
<p>We were unable to load your experience settings. Please refresh the page.</p>
72-
</div>
44+
<ErrorPage
45+
titleKey="error.something_went_wrong"
46+
rawMessage="We were unable to load your experience settings. Please refresh the page."
47+
/>
7348
);
7449
}
7550

76-
if (isLoading || isLoadingUser || isLoadingExperience) {
77-
return (
78-
<div>
79-
<p>Loading…</p>
80-
</div>
81-
);
51+
if (isLoading || isLoadingExperience) {
52+
return <div className={styles.status}>Loading…</div>;
8253
}
8354

8455
if (!isAuthenticated) {
85-
return (
86-
<div>
87-
<p>Redirecting to sign in…</p>
88-
</div>
89-
);
56+
return <div className={styles.status}>Redirecting to sign in…</div>;
9057
}
9158

92-
return (
93-
<div>
94-
<div>
95-
Signed in as <strong>{user?.username ?? user?.sub ?? 'your account'}</strong>.
96-
</div>
97-
</div>
98-
);
59+
return <Home />;
9960
};
10061

10162
const App = () => (
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@use '@experience/scss/_underscore.scss' as _;
2+
3+
.errorPage {
4+
@include _.flex_column(center, center);
5+
gap: _.unit(4);
6+
text-align: center;
7+
flex: 1;
8+
}
9+
10+
.illustration {
11+
width: 194px;
12+
max-width: 100%;
13+
align-self: center;
14+
15+
svg {
16+
width: 100%;
17+
height: auto;
18+
}
19+
}
20+
21+
.title {
22+
@include _.title;
23+
}
24+
25+
.message {
26+
@include _.text-hint;
27+
}
28+
29+
.support {
30+
margin-top: _.unit(2);
31+
display: grid;
32+
gap: _.unit(1);
33+
justify-items: center;
34+
}
35+
36+
.supportItem {
37+
@include _.text-hint;
38+
}
39+
40+
.supportLink {
41+
color: var(--color-primary);
42+
text-decoration: none;
43+
44+
&:hover {
45+
text-decoration: underline;
46+
}
47+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import EmptyStateDark from '@experience/assets/icons/empty-state-dark.svg';
2+
import EmptyState from '@experience/assets/icons/empty-state.svg';
3+
import DynamicT from '@experience/shared/components/DynamicT';
4+
import PageMeta from '@experience/shared/components/PageMeta';
5+
import { Theme } from '@logto/schemas';
6+
import type { TFuncKey } from 'i18next';
7+
import { useContext } from 'react';
8+
import { Trans } from 'react-i18next';
9+
10+
import PageContext from '@ac/Providers/PageContextProvider/PageContext';
11+
12+
import styles from './index.module.scss';
13+
14+
type Props = {
15+
readonly titleKey?: TFuncKey;
16+
readonly messageKey?: TFuncKey;
17+
readonly rawMessage?: string;
18+
};
19+
20+
const SupportInfo = () => {
21+
const { experienceSettings } = useContext(PageContext);
22+
23+
if (!experienceSettings) {
24+
return null;
25+
}
26+
27+
const { supportEmail, supportWebsiteUrl } = experienceSettings;
28+
29+
if (!supportEmail && !supportWebsiteUrl) {
30+
return null;
31+
}
32+
33+
return (
34+
<div className={styles.support}>
35+
{supportEmail && (
36+
<div className={styles.supportItem}>
37+
<Trans
38+
i18nKey="description.support_email"
39+
components={{
40+
link: (
41+
<a className={styles.supportLink} href={`mailto:${supportEmail}`}>
42+
{supportEmail}
43+
</a>
44+
),
45+
}}
46+
/>
47+
</div>
48+
)}
49+
{supportWebsiteUrl && (
50+
<div className={styles.supportItem}>
51+
<Trans
52+
i18nKey="description.support_website"
53+
components={{
54+
link: (
55+
<a
56+
className={styles.supportLink}
57+
href={supportWebsiteUrl}
58+
rel="noopener"
59+
target="_blank"
60+
>
61+
{supportWebsiteUrl}
62+
</a>
63+
),
64+
}}
65+
/>
66+
</div>
67+
)}
68+
</div>
69+
);
70+
};
71+
72+
const ErrorPage = ({ titleKey = 'description.not_found', messageKey, rawMessage }: Props) => {
73+
const { theme } = useContext(PageContext);
74+
const message = rawMessage ?? (messageKey ? <DynamicT forKey={messageKey} /> : undefined);
75+
const illustration = theme === Theme.Light ? EmptyState : EmptyStateDark;
76+
77+
return (
78+
<div className={styles.errorPage}>
79+
<PageMeta titleKey={titleKey} />
80+
<div className={styles.illustration}>
81+
<img src={illustration} alt="" role="presentation" />
82+
</div>
83+
<div className={styles.title}>
84+
<DynamicT forKey={titleKey} />
85+
</div>
86+
{message && <div className={styles.message}>{message}</div>}
87+
<SupportInfo />
88+
</div>
89+
);
90+
};
91+
92+
export default ErrorPage;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ErrorPage from '@ac/components/ErrorPage';
2+
3+
const Home = () => (
4+
<ErrorPage titleKey="account_center.home.title" messageKey="account_center.home.description" />
5+
);
6+
7+
export default Home;

packages/experience/src/Layout/FirstScreenLayout/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { type ReactNode, useContext } from 'react';
22

33
import PageContext from '@/Providers/PageContextProvider/PageContext';
4-
5-
import PageMeta from '../../components/PageMeta';
6-
import type { Props as PageMetaProps } from '../../components/PageMeta';
4+
import PageMeta from '@/shared/components/PageMeta';
5+
import type { Props as PageMetaProps } from '@/shared/components/PageMeta';
76

87
import styles from './index.module.scss';
98

packages/experience/src/Layout/FocusedAuthPageLayout/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { type AgreeToTermsPolicy } from '@logto/schemas';
22
import { type TFuncKey } from 'i18next';
33
import { useMemo, type ReactNode } from 'react';
44

5-
import DynamicT from '@/components/DynamicT';
6-
import type { Props as PageMetaProps } from '@/components/PageMeta';
75
import type { Props as TextLinkProps } from '@/components/TextLink';
86
import TextLink from '@/components/TextLink';
97
import TermsAndPrivacyLinks from '@/containers/TermsAndPrivacyLinks';
108
import useTerms from '@/hooks/use-terms';
9+
import DynamicT from '@/shared/components/DynamicT';
10+
import type { Props as PageMetaProps } from '@/shared/components/PageMeta';
1111

1212
import FirstScreenLayout from '../FirstScreenLayout';
1313

packages/experience/src/Layout/SecondaryPageLayout/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { TFuncKey } from 'i18next';
22
import { type ReactElement } from 'react';
33

4-
import DynamicT from '@/components/DynamicT';
54
import NavBar from '@/components/NavBar';
6-
import PageMeta from '@/components/PageMeta';
75
import usePlatform from '@/hooks/use-platform';
6+
import DynamicT from '@/shared/components/DynamicT';
7+
import PageMeta from '@/shared/components/PageMeta';
88

99
import { InlineNotification } from '../../components/Notification';
1010

packages/experience/src/Layout/SectionLayout/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type TFuncKey } from 'i18next';
22
import { type ReactNode } from 'react';
33

4-
import DynamicT from '@/components/DynamicT';
4+
import DynamicT from '@/shared/components/DynamicT';
55

66
import styles from './index.module.scss';
77

packages/experience/src/components/BrandingHeader/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import classNames from 'classnames';
33
import type { TFuncKey } from 'i18next';
44

55
import ConnectIcon from '@/assets/icons/connect-icon.svg?react';
6-
7-
import DynamicT from '../DynamicT';
6+
import DynamicT from '@/shared/components/DynamicT';
87

98
import styles from './index.module.scss';
109

0 commit comments

Comments
 (0)