Skip to content

Commit efa6bcf

Browse files
committed
add France Connect, Agent Connect and Pro Connect providers
1 parent ea0ae0f commit efa6bcf

File tree

5 files changed

+94
-30
lines changed

5 files changed

+94
-30
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"type": "module",
1010
"scripts": {
1111
"dev": "vite",
12+
"typecheck": "tsc --noEmit",
1213
"build": "tsc && vite build",
1314
"build-keycloak-theme": "npm run build && keycloakify build",
1415
"storybook": "storybook dev -p 6006",

src/login/Template.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export default function Template(props: Props) {
126126
}
127127
/>
128128
)}
129+
{socialProvidersNode}
129130
{children}
130131
{auth !== undefined && auth.showTryAnotherWayLink && (
131132
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
@@ -144,7 +145,6 @@ export default function Template(props: Props) {
144145
</div>
145146
</form>
146147
)}
147-
{socialProvidersNode}
148148
{displayInfo && (
149149
<div id="kc-info" className={kcClsx("kcSignUpClass")}>
150150
<div id="kc-info-wrapper" className={kcClsx("kcInfoAreaWrapperClass")}>

src/login/i18n.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import type { ThemeName } from "../kc.gen";
33

44
/** @see: https://docs.keycloakify.dev/i18n */
55
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6-
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
6+
const { useI18n, ofTypeI18n } = i18nBuilder
7+
.withThemeName<ThemeName>()
8+
.withCustomTranslations({
9+
en: { "or-login-with-email": "Or sign in with your email" },
10+
fr: { "or-login-with-email": "Ou connectez-vous avec votre email" }
11+
})
12+
.build();
713

814
type I18n = typeof ofTypeI18n;
915

src/login/pages/Login.stories.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,34 @@ export const WithImmutablePresetUsername: Story = {
110110
)
111111
};
112112

113+
export const WithFranceConnectAndProConnectProviders: Story = {
114+
render: () => (
115+
<KcPageStory
116+
kcContext={{
117+
social: {
118+
displayInfo: true,
119+
providers: [
120+
{
121+
loginUrl: "france-connect",
122+
alias: "france-connect",
123+
providerId: "franceconnect",
124+
displayName: "France Connect",
125+
iconClasses: "fa fa-france-connect"
126+
},
127+
{
128+
loginUrl: "proConnect",
129+
alias: "proConnect",
130+
providerId: "proConnect",
131+
displayName: "Pro Connect",
132+
iconClasses: "fa fa-proconnect"
133+
}
134+
]
135+
}
136+
}}
137+
/>
138+
)
139+
};
140+
113141
export const WithSocialProviders: Story = {
114142
render: () => (
115143
<KcPageStory

src/login/pages/Login.tsx

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { fr } from "@codegouvfr/react-dsfr";
22
import { Button } from "@codegouvfr/react-dsfr/Button";
33
import { Input } from "@codegouvfr/react-dsfr/Input";
4+
import { FranceConnectButton } from "@codegouvfr/react-dsfr/FranceConnectButton";
5+
import { AgentConnectButton } from "@codegouvfr/react-dsfr/AgentConnectButton";
6+
import { ProConnectButton } from "@codegouvfr/react-dsfr/ProConnectButton";
47
import { PasswordInput } from "@codegouvfr/react-dsfr/blocks/PasswordInput";
5-
import { kcSanitize } from "keycloakify/lib/kcSanitize";
68
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
79
import type { PageProps } from "keycloakify/login/pages/PageProps";
8-
import { clsx } from "keycloakify/tools/clsx";
910
import { useState } from "react";
1011
import type { KcContext } from "../KcContext";
1112
import type { I18n } from "../i18n";
@@ -23,7 +24,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
2324
const { msg, msgStr } = i18n;
2425

2526
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
26-
27+
2728
return (
2829
<Template
2930
kcContext={kcContext}
@@ -50,27 +51,55 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
5051
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
5152
<div id="kc-social-providers" className={kcClsx("kcFormSocialAccountSectionClass")}>
5253
<hr />
53-
<h2>{msg("identity-provider-login-label")}</h2>
54-
<ul className={kcClsx("kcFormSocialAccountListClass", social.providers.length > 3 && "kcFormSocialAccountListGridClass")}>
55-
{social.providers.map((...[p, , providers]) => (
56-
<li key={p.alias}>
57-
<a
58-
id={`social-${p.alias}`}
59-
className={kcClsx(
60-
"kcFormSocialAccountListButtonClass",
61-
providers.length > 3 && "kcFormSocialAccountGridItem"
62-
)}
63-
type="button"
64-
href={p.loginUrl}
54+
<ul>
55+
{social.providers.map((...[p]) => {
56+
if (p.providerId === "proConnect") {
57+
return <ProConnectButton key={p.alias} style={{ textAlign: "center" }} url={p.loginUrl} />;
58+
}
59+
60+
if (p.providerId === "agentconnect") {
61+
return <AgentConnectButton key={p.alias} style={{ textAlign: "center" }} url={p.loginUrl} />;
62+
}
63+
64+
if (p.providerId === "franceconnect") {
65+
return <FranceConnectButton key={p.alias} style={{ textAlign: "center" }} url={p.loginUrl} />;
66+
}
67+
68+
return (
69+
<Button
70+
className={fr.cx("fr-m-1w")}
71+
key={p.alias}
72+
iconId={(() => {
73+
switch (p.providerId) {
74+
case "github":
75+
return "ri-github-fill";
76+
case "google":
77+
return "ri-google-fill";
78+
case "facebook":
79+
return "ri-facebook-fill";
80+
case "microsoft":
81+
return "ri-microsoft-fill";
82+
case "twitter":
83+
return "ri-twitter-fill";
84+
case "instagram":
85+
return "ri-instagram-fill";
86+
case "linkedin":
87+
return "ri-linkedin-fill";
88+
case "stackoverflow":
89+
return "ri-stack-overflow-fill";
90+
case "gitlab":
91+
return "ri-gitlab-fill";
92+
}
93+
return "ri-external-link-line";
94+
})()}
95+
linkProps={{
96+
href: p.loginUrl
97+
}}
6598
>
66-
{p.iconClasses && <i className={clsx(kcClsx("kcCommonLogoIdP"), p.iconClasses)} aria-hidden="true"></i>}
67-
<span
68-
className={clsx(kcClsx("kcFormSocialAccountNameClass"), p.iconClasses && "kc-social-icon-text")}
69-
dangerouslySetInnerHTML={{ __html: kcSanitize(p.displayName) }}
70-
></span>
71-
</a>
72-
</li>
73-
))}
99+
{p.displayName}
100+
</Button>
101+
);
102+
})}
74103
</ul>
75104
</div>
76105
)}
@@ -79,6 +108,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
79108
>
80109
<div id="kc-form">
81110
<div id="kc-form-wrapper">
111+
{social?.providers !== undefined && social.providers.length !== 0 && <h2>{msg("or-login-with-email")}</h2>}
82112
{realm.password && (
83113
<form
84114
id="kc-form-login"
@@ -122,7 +152,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
122152
}
123153
/>
124154

125-
<div style={{display: "flex", justifyContent: "space-between"}}>
155+
<div style={{ display: "flex", justifyContent: "space-between" }}>
126156
<div>
127157
{realm.rememberMe && !usernameHidden && (
128158
<div className={fr.cx("fr-checkbox-group", "fr-checkbox-group--sm")}>
@@ -139,10 +169,9 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
139169
</div>
140170
{realm.resetPasswordAllowed && (
141171
<div>
142-
143-
<a className={fr.cx("fr-link")} href={url.loginResetCredentialsUrl} tabIndex={6}>
144-
{msg("doForgotPassword")}
145-
</a>
172+
<a className={fr.cx("fr-link")} href={url.loginResetCredentialsUrl} tabIndex={6}>
173+
{msg("doForgotPassword")}
174+
</a>
146175
</div>
147176
)}
148177
</div>

0 commit comments

Comments
 (0)