Skip to content

Commit 37ea37f

Browse files
authored
Merge branch 'main' into feature/add-login-reset-password-page
Signed-off-by: Joseph Garrone <[email protected]>
2 parents f66cb49 + 1fc6bce commit 37ea37f

File tree

10 files changed

+234
-4
lines changed

10 files changed

+234
-4
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Keycloak DSFR Theme
22

33
Welcome to the Keycloak DSFR Theme, a Keycloak theme with [react-dsfr](https://github.com/codegouvfr/react-dsfr) and [Keycloakify](https://www.keycloakify.dev/).
4-
To see the theme in action, please visit [the SILL](https://sill-preprod.lab.sspcloud.fr/) and attempt to log in.
4+
To see the theme in action, please visit [the SILL](https://code.gouv.fr/sill/) and attempt to log in.
55
This theme is configurable at runtime, via providing environnement variable, there is no need to clone this repository.
66
Simply use the bundled .jar file that is released an asset with every new [GitHub Release of this project](https://github.com/codegouvfr/keycloak-theme-dsfr/releases).
77

@@ -28,6 +28,7 @@ Several environment variables can be used to tailor the theme to your needs:
2828
```env
2929
DSFR_THEME_HOME_URL
3030
DSFR_THEME_SERVICE_TITLE
31+
DSFR_THEME_SERVICE_TAG_LINE
3132
DSFR_THEME_BRAND_TOP
3233
DSFR_NOTICE_TITLE
3334
DSFR_NOTICE_DESCRIPTION
@@ -45,6 +46,8 @@ If you are deploying Keycloak on Kubernetes using Helm, here's how to configure
4546
value: https://code.gouv.fr
4647
- name: DSFR_THEME_SERVICE_TITLE
4748
value: CodeGouv
49+
- name: DSFR_THEME_SERVICE_TAG_LINE
50+
value: My tag line
4851
- name: DSFR_THEME_BRAND_TOP
4952
value: "République<br/>Française"
5053
- name: DSFR_NOTICE_TITLE

src/account/Template.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
7171
}}
7272
/>
7373
}
74+
serviceTagline={
75+
<span
76+
dangerouslySetInnerHTML={{
77+
__html:
78+
kcContext.properties.DSFR_THEME_SERVICE_TAG_LINE || ""
79+
}}
80+
/>
81+
}
7482
quickAccessItems={[
7583
...(referrer?.url
7684
? [

src/kc.gen.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file is auto-generated by the `update-kc-gen` command. Do not edit it manually.
2-
// Hash: 415229e332a814cae52152aa8f2b43dab69ceadc6ed220a115d8a8349abbde0b
2+
// Hash: 1ded21a7b8002bb4044342419812de41d9262e5530b9ef4de8909352536a5744
33

44
/* eslint-disable */
55

@@ -16,6 +16,7 @@ export const themeNames: ThemeName[] = ["DSFR"];
1616
export type KcEnvName =
1717
| "DSFR_THEME_HOME_URL"
1818
| "DSFR_THEME_SERVICE_TITLE"
19+
| "DSFR_THEME_SERVICE_TAG_LINE"
1920
| "DSFR_THEME_BRAND_TOP"
2021
| "DSFR_NOTICE_TITLE"
2122
| "DSFR_NOTICE_DESCRIPTION"
@@ -24,6 +25,7 @@ export type KcEnvName =
2425
export const kcEnvNames: KcEnvName[] = [
2526
"DSFR_THEME_HOME_URL",
2627
"DSFR_THEME_SERVICE_TITLE",
28+
"DSFR_THEME_SERVICE_TAG_LINE",
2729
"DSFR_THEME_BRAND_TOP",
2830
"DSFR_NOTICE_TITLE",
2931
"DSFR_NOTICE_DESCRIPTION",
@@ -33,9 +35,10 @@ export const kcEnvNames: KcEnvName[] = [
3335
export const kcEnvDefaults: Record<KcEnvName, string> = {
3436
DSFR_THEME_HOME_URL: "",
3537
DSFR_THEME_SERVICE_TITLE: "",
38+
DSFR_THEME_SERVICE_TAG_LINE: "",
3639
DSFR_THEME_BRAND_TOP: "République<br/>Française",
37-
DSFR_NOTICE_TITLE: "Mon super titre",
38-
DSFR_NOTICE_DESCRIPTION: "Ma super description",
40+
DSFR_NOTICE_TITLE: "",
41+
DSFR_NOTICE_DESCRIPTION: "",
3942
DSFR_NOTICE_SEVERITY: "info"
4043
};
4144

src/login/KcPage.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const Register = lazy(() => import("./pages/Register"));
1414
const LoginUpdateProfile = lazy(() => import("./pages/LoginUpdateProfile"));
1515
const LoginUpdatePassword = lazy(() => import("./pages/LoginUpdatePassword"));
1616
const LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
17+
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));
18+
const LoginVerifyEmail = lazy(() => import("./pages/LoginVerifyEmail"));
1719

1820
const doMakeUserConfirmPassword = false;
1921

@@ -85,6 +87,22 @@ export default function KcPage(props: { kcContext: KcContext }) {
8587
Template={Template}
8688
doUseDefaultCss={false}
8789
/>
90+
);
91+
case "login-page-expired.ftl":
92+
return (
93+
<LoginPageExpired
94+
{...{ kcContext, i18n, classes }}
95+
Template={Template}
96+
doUseDefaultCss={false}
97+
/>
98+
);
99+
case "login-verify-email.ftl":
100+
return (
101+
<LoginVerifyEmail
102+
{...{ kcContext, i18n, classes }}
103+
Template={Template}
104+
doUseDefaultCss={false}
105+
/>
88106
);
89107
default:
90108
return (

src/login/Template.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ export default function Template(props: Props) {
9090
}}
9191
/>
9292
}
93+
serviceTagline={
94+
<span
95+
dangerouslySetInnerHTML={{
96+
__html:
97+
kcContext.properties.DSFR_THEME_SERVICE_TAG_LINE || ""
98+
}}
99+
/>
100+
}
93101
/>
94102
<main role="main" id="content">
95103
{(kcContext.properties.DSFR_NOTICE_TITLE || kcContext.properties.DSFR_NOTICE_DESCRIPTION) && (
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { createKcPageStory } from "../KcPageStory";
3+
4+
const { KcPageStory } = createKcPageStory({ pageId: "login-page-expired.ftl" });
5+
6+
const meta = {
7+
title: "login/login-page-expired.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+
* WithCustomUrl:
21+
* - Purpose: Tests the page with a custom restart flow URL.
22+
* - Scenario: Simulates a page expired scenario with a specific URL to restart the authentication flow.
23+
* - Key Aspect: Ensures the "click here to login" link points to the correct restart URL.
24+
*/
25+
export const WithCustomUrl: Story = {
26+
render: () => (
27+
<KcPageStory
28+
kcContext={{
29+
url: {
30+
loginRestartFlowUrl: "https://hubee.com/auth/restart"
31+
}
32+
}}
33+
/>
34+
)
35+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { PageProps } from "keycloakify/login/pages/PageProps";
2+
import type { KcContext } from "../KcContext";
3+
import type { I18n } from "../i18n";
4+
import { fr } from "@codegouvfr/react-dsfr";
5+
6+
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
7+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
8+
9+
const { url } = kcContext;
10+
11+
const { msg } = i18n;
12+
13+
return (
14+
<Template
15+
kcContext={kcContext}
16+
i18n={i18n}
17+
doUseDefaultCss={doUseDefaultCss}
18+
classes={classes}
19+
displayMessage={false}
20+
headerNode={msg("pageExpiredTitle")}
21+
>
22+
<div className={fr.cx("fr-grid-row", "fr-grid-row--gutters")}>
23+
<div className={fr.cx("fr-col-12")}>
24+
<div className={fr.cx("fr-callout")}>
25+
<p className={fr.cx("fr-callout__text")}>{msg("pageExpiredMsg1")}</p>
26+
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: "1rem" }}>
27+
<a className={fr.cx("fr-btn", "fr-btn--secondary", "fr-btn--icon-right", "fr-icon-refresh-line")} href={url.loginRestartFlowUrl}>
28+
{msg("doClickHere")}
29+
</a>
30+
</div>
31+
</div>
32+
</div>
33+
34+
<div className={fr.cx("fr-col-12")}>
35+
<div className={fr.cx("fr-callout")}>
36+
<p className={fr.cx("fr-callout__text")}>{msg("pageExpiredMsg2")}</p>
37+
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: "1rem" }}>
38+
<a className={fr.cx("fr-btn", "fr-btn--icon-right", "fr-icon-arrow-right-line")} href={url.loginAction}>
39+
{msg("doClickHere")}
40+
</a>
41+
</div>
42+
</div>
43+
</div>
44+
</div>
45+
</Template>
46+
);
47+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { createKcPageStory } from "../KcPageStory";
3+
4+
const { KcPageStory } = createKcPageStory({ pageId: "login-verify-email.ftl" });
5+
6+
const meta = {
7+
title: "login/login-verify-email.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+
user: {
20+
21+
}
22+
}}
23+
/>
24+
)
25+
};
26+
27+
/**
28+
* WithCustomEmail:
29+
* - Purpose: Tests the page with a specific email address.
30+
* - Scenario: Simulates a user with a custom email address to verify the display.
31+
* - Key Aspect: Ensures the email is correctly displayed in the verification message.
32+
*/
33+
export const WithCustomEmail: Story = {
34+
render: () => (
35+
<KcPageStory
36+
kcContext={{
37+
user: {
38+
39+
}
40+
}}
41+
/>
42+
)
43+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {
9+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
10+
11+
const { kcClsx } = getKcClsx({
12+
doUseDefaultCss,
13+
classes
14+
});
15+
16+
const { url, user } = kcContext;
17+
18+
const { msg, msgStr } = i18n;
19+
20+
return (
21+
<Template
22+
kcContext={kcContext}
23+
i18n={i18n}
24+
doUseDefaultCss={doUseDefaultCss}
25+
classes={classes}
26+
displayMessage={false}
27+
headerNode={msg("emailVerifyTitle")}
28+
>
29+
<Alert
30+
severity="info"
31+
description={msgStr("emailVerifyInstruction1", user?.email ?? "")}
32+
className={fr.cx("fr-mb-4w")}
33+
small
34+
/>
35+
36+
<div className={kcClsx("kcFormGroupClass")}>
37+
<p className={fr.cx("fr-text--sm", "fr-mb-3w")}>
38+
{msgStr("emailVerifyInstruction2")}{" "}
39+
<form action={url.loginAction} method="post" style={{ display: "inline" }}>
40+
<input type="hidden" name="email" value={user?.email ?? ""} />
41+
<button
42+
type="submit"
43+
className={fr.cx("fr-link")}
44+
style={{
45+
background: "none",
46+
border: "none",
47+
padding: 0,
48+
cursor: "pointer"
49+
}}
50+
>
51+
{msgStr("doClickHere")} {msgStr("emailVerifyInstruction3")}
52+
</button>
53+
</form>
54+
</p>
55+
</div>
56+
57+
<div className={kcClsx("kcFormGroupClass")} style={{ display: "flex", justifyContent: "flex-end" }}>
58+
<a className={fr.cx("fr-link")} href={url.loginUrl}>
59+
{msg("backToLogin")}
60+
</a>
61+
</div>
62+
</Template>
63+
);
64+
}

vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default defineConfig({
1212
environmentVariables: [
1313
{ name: "DSFR_THEME_HOME_URL", default: "" },
1414
{ name: "DSFR_THEME_SERVICE_TITLE", default: "" },
15+
{ name: "DSFR_THEME_SERVICE_TAG_LINE", default: "" },
1516
{ name: "DSFR_THEME_BRAND_TOP", default: "République<br/>Française" },
1617
{ name: "DSFR_NOTICE_TITLE", default: "" },
1718
{ name: "DSFR_NOTICE_DESCRIPTION", default: "" },

0 commit comments

Comments
 (0)