Skip to content

Commit d5ab46f

Browse files
authored
Merge pull request #27 from damienlethiec/feature/add-error-page
feat: add themed Error page
2 parents 8bb7fff + 815aa36 commit d5ab46f

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

src/login/KcPage.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ const Login = lazy(() => import("./pages/Login"));
1313
const Register = lazy(() => import("./pages/Register"));
1414
const LoginUpdateProfile = lazy(() => import("./pages/LoginUpdateProfile"));
1515
const LoginUpdatePassword = lazy(() => import("./pages/LoginUpdatePassword"));
16+
17+
const Error = lazy(() => import("./pages/Error"));
1618
const LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
1719
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));
1820
const LoginVerifyEmail = lazy(() => import("./pages/LoginVerifyEmail"));
1921

22+
2023
const doMakeUserConfirmPassword = false;
2124

2225
export default function KcPage(props: { kcContext: KcContext }) {
@@ -80,6 +83,14 @@ export default function KcPage(props: { kcContext: KcContext }) {
8083
doUseDefaultCss={false}
8184
/>
8285
);
86+
case "error.ftl":
87+
return (
88+
<Error
89+
{...{ kcContext, i18n, classes }}
90+
Template={Template}
91+
doUseDefaultCss={false}
92+
/>
93+
);
8394
case "login-reset-password.ftl":
8495
return (
8596
<LoginResetPassword

src/login/pages/Error.stories.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { createKcPageStory } from "../KcPageStory";
3+
4+
const { KcPageStory } = createKcPageStory({ pageId: "error.ftl" });
5+
6+
const meta = {
7+
title: "login/error.ftl",
8+
component: KcPageStory
9+
} satisfies Meta<typeof KcPageStory>;
10+
11+
export default meta;
12+
13+
type Story = StoryObj<typeof meta>;
14+
15+
export const Default: Story = {
16+
render: () => (
17+
<KcPageStory
18+
kcContext={{
19+
message: {
20+
summary: "An unexpected error occurred. Please try again."
21+
}
22+
}}
23+
/>
24+
)
25+
};
26+
27+
/**
28+
* AccountLocked:
29+
* - Purpose: Tests the error page for account lockout scenarios.
30+
* - Scenario: Simulates a user whose account has been locked due to too many failed login attempts.
31+
* - Key Aspect: Displays a specific error message about account lockout.
32+
*/
33+
export const AccountLocked: Story = {
34+
render: () => (
35+
<KcPageStory
36+
kcContext={{
37+
message: {
38+
summary: "Your account has been temporarily locked due to too many failed login attempts."
39+
}
40+
}}
41+
/>
42+
)
43+
};
44+
45+
/**
46+
* WithClientBaseUrl:
47+
* - Purpose: Tests the error page when a client base URL is available.
48+
* - Scenario: Simulates an error with the option to return to the application.
49+
* - Key Aspect: Displays a "Back to Application" button in addition to "Back to Login".
50+
*/
51+
export const WithClientBaseUrl: Story = {
52+
render: () => (
53+
<KcPageStory
54+
kcContext={{
55+
message: {
56+
summary: "Session expired. Please log in again."
57+
},
58+
client: {
59+
baseUrl: "https://hubee.com"
60+
}
61+
}}
62+
/>
63+
)
64+
};

src/login/pages/Error.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
2+
import type { PageProps } from "keycloakify/login/pages/PageProps";
3+
import type { KcContext } from "../KcContext";
4+
import type { I18n } from "../i18n";
5+
import Alert from "@codegouvfr/react-dsfr/Alert";
6+
import { fr } from "@codegouvfr/react-dsfr";
7+
8+
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
9+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
10+
11+
const { kcClsx } = getKcClsx({
12+
doUseDefaultCss,
13+
classes
14+
});
15+
16+
const { url, message, client } = kcContext;
17+
18+
const { msg } = i18n;
19+
20+
return (
21+
<Template
22+
kcContext={kcContext}
23+
i18n={i18n}
24+
doUseDefaultCss={doUseDefaultCss}
25+
classes={classes}
26+
displayMessage={false}
27+
headerNode={msg("errorTitle")}
28+
>
29+
<Alert
30+
severity="error"
31+
description={message?.summary}
32+
className={fr.cx("fr-mb-4w")}
33+
small
34+
/>
35+
36+
<div className={kcClsx("kcFormGroupClass")} style={{ display: "flex", justifyContent: "flex-end" }}>
37+
{client.baseUrl !== undefined ? (
38+
<a className={fr.cx("fr-link")} href={client.baseUrl}>
39+
{msg("backToApplication")}
40+
</a>
41+
) : (
42+
<a className={fr.cx("fr-link")} href={url.loginUrl}>
43+
{msg("backToLogin")}
44+
</a>
45+
)}
46+
</div>
47+
</Template>
48+
);
49+
}

0 commit comments

Comments
 (0)