Skip to content

Commit 8bb7fff

Browse files
authored
Merge pull request #28 from damienlethiec/feature/add-login-reset-password-page
feat: add themed LoginResetPassword page
2 parents 1fc6bce + 37ea37f commit 8bb7fff

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-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 LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
1617
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));
1718
const LoginVerifyEmail = lazy(() => import("./pages/LoginVerifyEmail"));
1819

@@ -79,6 +80,14 @@ export default function KcPage(props: { kcContext: KcContext }) {
7980
doUseDefaultCss={false}
8081
/>
8182
);
83+
case "login-reset-password.ftl":
84+
return (
85+
<LoginResetPassword
86+
{...{ kcContext, i18n, classes }}
87+
Template={Template}
88+
doUseDefaultCss={false}
89+
/>
90+
);
8291
case "login-page-expired.ftl":
8392
return (
8493
<LoginPageExpired
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { createKcPageStory } from "../KcPageStory";
3+
4+
const { KcPageStory } = createKcPageStory({ pageId: "login-reset-password.ftl" });
5+
6+
const meta = {
7+
title: "login/login-reset-password.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: () => <KcPageStory />
17+
};
18+
19+
/**
20+
* WithUsernameError:
21+
* - Purpose: Tests when there is an error with the email/username input (e.g., user not found).
22+
* - Scenario: Simulates the case where the user enters an invalid or non-existent email, and an error message is displayed.
23+
* - Key Aspect: Ensures the email input field shows an error message when validation fails.
24+
*/
25+
export const WithUsernameError: Story = {
26+
render: () => (
27+
<KcPageStory
28+
kcContext={{
29+
url: {
30+
loginAction: "/mock-reset-password-action"
31+
},
32+
messagesPerField: {
33+
existsError: (field: string) => field === "username",
34+
getFirstError: () => "User not found."
35+
}
36+
}}
37+
/>
38+
)
39+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { fr } from "@codegouvfr/react-dsfr";
2+
import Button from "@codegouvfr/react-dsfr/Button";
3+
import Input from "@codegouvfr/react-dsfr/Input";
4+
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
5+
import type { PageProps } from "keycloakify/login/pages/PageProps";
6+
import type { I18n } from "../i18n";
7+
import type { KcContext } from "../KcContext";
8+
9+
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
10+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
11+
12+
const { kcClsx } = getKcClsx({
13+
doUseDefaultCss,
14+
classes
15+
});
16+
17+
const { url, messagesPerField } = kcContext;
18+
19+
const { msg, msgStr } = i18n;
20+
21+
return (
22+
<Template
23+
kcContext={kcContext}
24+
i18n={i18n}
25+
doUseDefaultCss={doUseDefaultCss}
26+
classes={classes}
27+
displayMessage={!messagesPerField.existsError("username")}
28+
headerNode={msg("emailForgotTitle")}
29+
>
30+
<form
31+
id="kc-reset-password-form"
32+
className={kcClsx("kcFormClass")}
33+
action={url.loginAction}
34+
method="post"
35+
>
36+
<div className={kcClsx("kcFormGroupClass")}>
37+
<Input
38+
label={msgStr("email")}
39+
state={messagesPerField.existsError("username") ? "error" : "default"}
40+
stateRelatedMessage={messagesPerField.getFirstError("username")}
41+
nativeInputProps={{
42+
type: "email",
43+
id: "username",
44+
name: "username",
45+
autoFocus: true,
46+
autoComplete: "email"
47+
}}
48+
/>
49+
</div>
50+
51+
<div className={kcClsx("kcFormGroupClass")}>
52+
<div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>
53+
<div className={kcClsx("kcFormOptionsWrapperClass")} />
54+
</div>
55+
<div id="kc-form-buttons" className={fr.cx("fr-mt-3w")} style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
56+
<Button type="submit">{msgStr("doSubmit")}</Button>
57+
58+
<div>
59+
<a className={fr.cx("fr-link")} href={url.loginUrl}>
60+
{msg("backToLogin")}
61+
</a>
62+
</div>
63+
</div>
64+
</div>
65+
</form>
66+
</Template>
67+
);
68+
}

0 commit comments

Comments
 (0)