Skip to content

Commit e2e35e2

Browse files
authored
Merge pull request #26 from damienlethiec/feature/add-info-page
feat: add themed Info page
2 parents d5ab46f + 03bbb96 commit e2e35e2

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

src/login/KcPage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ 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+
const Info = lazy(() => import("./pages/Info"));
1617

1718
const Error = lazy(() => import("./pages/Error"));
1819
const LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
@@ -83,6 +84,14 @@ export default function KcPage(props: { kcContext: KcContext }) {
8384
doUseDefaultCss={false}
8485
/>
8586
);
87+
case "info.ftl":
88+
return (
89+
<Info
90+
{...{ kcContext, i18n, classes }}
91+
Template={Template}
92+
doUseDefaultCss={false}
93+
/>
94+
);
8695
case "error.ftl":
8796
return (
8897
<Error

src/login/pages/Info.stories.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { createKcPageStory } from "../KcPageStory";
3+
4+
const { KcPageStory } = createKcPageStory({ pageId: "info.ftl" });
5+
6+
const meta = {
7+
title: "login/info.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: "Your email has been verified successfully."
21+
},
22+
pageRedirectUri: "https://hubee.com"
23+
}}
24+
/>
25+
)
26+
};
27+
28+
/**
29+
* WithRequiredActions:
30+
* - Purpose: Tests the info page when certain actions are required from the user.
31+
* - Scenario: Simulates a scenario where the user needs to complete specific actions (e.g., verify email, update password).
32+
* - Key Aspect: Displays a list of required actions that the user must complete.
33+
*/
34+
export const WithRequiredActions: Story = {
35+
render: () => (
36+
<KcPageStory
37+
kcContext={{
38+
messageHeader: "Action Required",
39+
message: {
40+
summary: "Please complete the following actions to continue."
41+
},
42+
requiredActions: ["VERIFY_EMAIL", "UPDATE_PASSWORD"],
43+
actionUri: "/auth/realms/hubee/login-actions/required-action"
44+
}}
45+
/>
46+
)
47+
};
48+
49+
/**
50+
* WithActionUri:
51+
* - Purpose: Tests the page with an action URI link.
52+
* - Scenario: Simulates an info page that provides a link to proceed with a specific action.
53+
* - Key Aspect: Displays a "proceed with action" link instead of "back to application".
54+
*/
55+
export const WithActionUri: Story = {
56+
render: () => (
57+
<KcPageStory
58+
kcContext={{
59+
message: {
60+
summary: "Your profile has been updated."
61+
},
62+
actionUri: "/auth/realms/hubee/account"
63+
}}
64+
/>
65+
)
66+
};
67+
68+
/**
69+
* WithClientBaseUrl:
70+
* - Purpose: Tests the page with only a client base URL available.
71+
* - Scenario: Simulates when no specific redirect URI is provided, falling back to the client's base URL.
72+
* - Key Aspect: Uses the client base URL as the "back to application" link.
73+
*/
74+
export const WithClientBaseUrl: Story = {
75+
render: () => (
76+
<KcPageStory
77+
kcContext={{
78+
message: {
79+
summary: "Your settings have been saved."
80+
},
81+
client: {
82+
baseUrl: "https://hubee.com"
83+
}
84+
}}
85+
/>
86+
)
87+
};
88+
89+
/**
90+
* EmailVerificationSuccess:
91+
* - Purpose: Tests a typical email verification success message.
92+
* - Scenario: User clicks on email verification link and sees success message.
93+
* - Key Aspect: Shows success message with redirect to application.
94+
*/
95+
export const EmailVerificationSuccess: Story = {
96+
render: () => (
97+
<KcPageStory
98+
kcContext={{
99+
messageHeader: "Email verified",
100+
message: {
101+
summary: "Your email address has been verified successfully."
102+
},
103+
pageRedirectUri: "https://hubee.com/dashboard"
104+
}}
105+
/>
106+
)
107+
};

src/login/pages/Info.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
9+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
10+
11+
const { kcClsx } = getKcClsx({
12+
doUseDefaultCss,
13+
classes
14+
});
15+
16+
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client, url } = kcContext;
17+
18+
const { msg, advancedMsg } = i18n;
19+
20+
return (
21+
<Template
22+
kcContext={kcContext}
23+
i18n={i18n}
24+
doUseDefaultCss={doUseDefaultCss}
25+
classes={classes}
26+
displayMessage={false}
27+
headerNode={messageHeader !== undefined ? advancedMsg(messageHeader) : undefined}
28+
>
29+
{message !== undefined && (
30+
<Alert
31+
severity="success"
32+
description={advancedMsg(message.summary)}
33+
className={fr.cx("fr-mb-4w")}
34+
small
35+
/>
36+
)}
37+
38+
{requiredActions !== undefined && requiredActions.length > 0 && (
39+
<div className={kcClsx("kcFormGroupClass")}>
40+
<ul className={fr.cx("fr-mb-2w")}>
41+
{requiredActions.map((requiredAction, index) => (
42+
<li key={index}>{advancedMsg(`requiredAction.${requiredAction}` as any)}</li>
43+
))}
44+
</ul>
45+
</div>
46+
)}
47+
48+
<div className={kcClsx("kcFormGroupClass")} style={{ display: "flex", justifyContent: "flex-end" }}>
49+
{!skipLink && (
50+
actionUri !== undefined ? (
51+
<a className={fr.cx("fr-link", "fr-link--icon-right", "fr-icon-arrow-right-line")} href={actionUri}>
52+
{msg("doContinue")}
53+
</a>
54+
) : pageRedirectUri !== undefined ? (
55+
<a className={fr.cx("fr-link")} href={pageRedirectUri}>
56+
{msg("backToApplication")}
57+
</a>
58+
) : client.baseUrl !== undefined ? (
59+
<a className={fr.cx("fr-link")} href={client.baseUrl}>
60+
{msg("backToApplication")}
61+
</a>
62+
) : (
63+
<a className={fr.cx("fr-link")} href={url.loginUrl}>
64+
{msg("backToLogin")}
65+
</a>
66+
)
67+
)}
68+
</div>
69+
</Template>
70+
);
71+
}

0 commit comments

Comments
 (0)