update setup
This commit is contained in:
parent
39ae948f24
commit
93780a9702
|
@ -2,31 +2,29 @@ import "./KcApp.css";
|
||||||
import { lazy, Suspense } from "react";
|
import { lazy, Suspense } from "react";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import { useI18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
import Fallback, { defaultKcProps, type KcProps, type PageProps } from "keycloakify";
|
import Fallback, { type PageProps } from "keycloakify";
|
||||||
import Template from "./Template";
|
|
||||||
import DefaultTemplate from "keycloakify/lib/Template";
|
const Template = lazy(() => import("./Template"));
|
||||||
|
const DefaultTemplate = lazy(() => import("keycloakify/Template"));
|
||||||
|
|
||||||
// You can uncomment this to see the values passed by the main app before redirecting.
|
// You can uncomment this to see the values passed by the main app before redirecting.
|
||||||
//import { foo, bar } from "./valuesTransferredOverUrl";
|
//import { foo, bar } from "./valuesTransferredOverUrl";
|
||||||
//console.log(`Values passed by the main app in the URL parameter:`, { foo, bar });
|
//console.log(`Values passed by the main app in the URL parameter:`, { foo, bar });
|
||||||
|
|
||||||
const Login = lazy(()=> import("./pages/Login"));
|
const Login = lazy(() => import("./pages/Login"));
|
||||||
// If you can, favor register-user-profile.ftl over register.ftl, see: https://docs.keycloakify.dev/realtime-input-validation
|
// If you can, favor register-user-profile.ftl over register.ftl, see: https://docs.keycloakify.dev/realtime-input-validation
|
||||||
const Register = lazy(() => import("./pages/Register"));
|
const Register = lazy(() => import("./pages/Register"));
|
||||||
const RegisterUserProfile = lazy(() => import("./pages/RegisterUserProfile"));
|
const RegisterUserProfile = lazy(() => import("./pages/RegisterUserProfile"));
|
||||||
const Terms = lazy(() => import("./pages/Terms"));
|
const Terms = lazy(() => import("./pages/Terms"));
|
||||||
const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1"));
|
const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1"));
|
||||||
const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2"));
|
const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2"));
|
||||||
const Info = lazy(()=> import("keycloakify/lib/pages/Info"));
|
const Info = lazy(() => import("keycloakify/pages/Info"));
|
||||||
|
|
||||||
// This is like editing the theme.properties
|
// This is like adding classes to theme.properties
|
||||||
// https://github.com/keycloak/keycloak/blob/11.0.3/themes/src/main/resources/theme/keycloak/login/theme.properties
|
// https://github.com/keycloak/keycloak/blob/11.0.3/themes/src/main/resources/theme/keycloak/login/theme.properties
|
||||||
const kcProps: KcProps = {
|
const classes: PageProps<any, any>["classes"] = {
|
||||||
...defaultKcProps,
|
|
||||||
// NOTE: The classes are defined in ./KcApp.css
|
// NOTE: The classes are defined in ./KcApp.css
|
||||||
// You can add your classes alongside thoses that are present in the default Keycloak theme...
|
"kcHtmlClass": "my-root-class",
|
||||||
"kcHtmlClass": [...defaultKcProps.kcHtmlClass, "my-root-class"],
|
|
||||||
// ...or overwrite
|
|
||||||
"kcHeaderWrapperClass": "my-color my-font"
|
"kcHeaderWrapperClass": "my-color my-font"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,36 +38,26 @@ export default function App(props: { kcContext: KcContext; }) {
|
||||||
//NOTE: Locales not yet downloaded, we could as well display a loading progress but it's usually a matter of milliseconds.
|
//NOTE: Locales not yet downloaded, we could as well display a loading progress but it's usually a matter of milliseconds.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Examples assuming i18n.currentLanguageTag === "en":
|
* Examples assuming i18n.currentLanguageTag === "en":
|
||||||
* i18n.msg("access-denied") === <span>Access denied</span>
|
* i18n.msg("access-denied") === <span>Access denied</span>
|
||||||
* i18n.msg("foo") === <span>foo in English</span>
|
* i18n.msg("foo") === <span>foo in English</span>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const pageProps: Omit<PageProps<any, typeof i18n>, "kcContext"> = {
|
|
||||||
i18n,
|
|
||||||
// Here we have overloaded the default template, however you could use the default one with:
|
|
||||||
//Template: DefaultTemplate,
|
|
||||||
Template,
|
|
||||||
// Wether or not we should download the CSS and JS resources that comes with the default Keycloak theme.
|
|
||||||
doFetchDefaultThemeResources: true,
|
|
||||||
...kcProps,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
{(() => {
|
{(() => {
|
||||||
switch (kcContext.pageId) {
|
switch (kcContext.pageId) {
|
||||||
case "login.ftl": return <Login {...{ kcContext, ...pageProps }} />;
|
case "login.ftl": return <Login {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />;
|
||||||
case "register.ftl": return <Register {...{ kcContext, ...pageProps }} />;
|
case "register.ftl": return <Register {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />;
|
||||||
case "register-user-profile.ftl": return <RegisterUserProfile {...{ kcContext, ...pageProps }} />
|
case "register-user-profile.ftl": return <RegisterUserProfile {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />
|
||||||
case "terms.ftl": return <Terms {...{ kcContext, ...pageProps }} />;
|
case "terms.ftl": return <Terms {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />;
|
||||||
case "my-extra-page-1.ftl": return <MyExtraPage1 {...{ kcContext, ...pageProps }} />;
|
case "my-extra-page-1.ftl": return <MyExtraPage1 {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />;
|
||||||
case "my-extra-page-2.ftl": return <MyExtraPage2 {...{ kcContext, ...pageProps }} />;
|
case "my-extra-page-2.ftl": return <MyExtraPage2 {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />;
|
||||||
// We choose to use the default Template for the Info page and to download the theme resources.
|
// We choose to use the default Template for the Info page and to download the theme resources.
|
||||||
case "info.ftl": return <Info {...{ kcContext, ...pageProps}} Template={DefaultTemplate} doFetchDefaultThemeResources={true} />;
|
case "info.ftl": return <Info {...{ kcContext, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />;
|
||||||
default: return <Fallback {...{ kcContext, ...pageProps }} />;
|
default: return <Fallback {...{ kcContext, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />;
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/components/shared/Template.tsx
|
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/Template.tsx
|
||||||
|
import { assert } from "keycloakify/tools/assert";
|
||||||
// You can replace all relative imports by cherry picking files from the keycloakify module.
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
// For example, the following import:
|
import { usePrepareTemplate } from "keycloakify/lib/usePrepareTemplate";
|
||||||
// import { assert } from "./tools/assert";
|
import { type TemplateProps, defaultTemplateClasses } from "keycloakify/TemplateProps";
|
||||||
// becomes:
|
import { useGetClassName } from "keycloakify/lib/useGetClassName";
|
||||||
import { assert } from "keycloakify/lib/tools/assert";
|
|
||||||
import { clsx } from "keycloakify/lib/tools/clsx";
|
|
||||||
import type { TemplateProps } from "keycloakify/lib/KcProps";
|
|
||||||
import { usePrepareTemplate } from "keycloakify/lib/Template";
|
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
|
|
||||||
|
@ -24,24 +20,29 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
infoNode = null,
|
infoNode = null,
|
||||||
kcContext,
|
kcContext,
|
||||||
i18n,
|
i18n,
|
||||||
doFetchDefaultThemeResources,
|
doUseDefaultCss,
|
||||||
stylesCommon,
|
classes
|
||||||
styles,
|
|
||||||
scripts,
|
|
||||||
kcHtmlClass
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
"defaultClasses": !doUseDefaultCss ? undefined : defaultTemplateClasses,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||||
|
|
||||||
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
||||||
|
|
||||||
const { isReady } = usePrepareTemplate({
|
const { isReady } = usePrepareTemplate({
|
||||||
doFetchDefaultThemeResources,
|
"doFetchDefaultThemeResources": doUseDefaultCss,
|
||||||
stylesCommon,
|
|
||||||
styles,
|
|
||||||
scripts,
|
|
||||||
url,
|
url,
|
||||||
kcHtmlClass
|
"stylesCommon": [
|
||||||
|
"node_modules/patternfly/dist/css/patternfly.min.css",
|
||||||
|
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
||||||
|
"lib/zocial/zocial.css"
|
||||||
|
],
|
||||||
|
"styles": ["css/login.css"],
|
||||||
|
"htmlClassName": getClassName("kcHtmlClass")
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
|
@ -49,18 +50,18 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(props.kcLoginClass)}>
|
<div className={getClassName("kcLoginClass")}>
|
||||||
<div id="kc-header" className={clsx(props.kcHeaderClass)}>
|
<div id="kc-header" className={getClassName("kcHeaderClass")}>
|
||||||
<div id="kc-header-wrapper" className={clsx(props.kcHeaderWrapperClass)}>
|
<div id="kc-header-wrapper" className={getClassName("kcHeaderWrapperClass")}>
|
||||||
{msg("loginTitleHtml", realm.displayNameHtml)}
|
{msg("loginTitleHtml", realm.displayNameHtml)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={clsx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
|
<div className={clsx(getClassName("kcFormCardClass"), displayWide && getClassName("kcFormCardAccountClass"))}>
|
||||||
<header className={clsx(props.kcFormHeaderClass)}>
|
<header className={getClassName("kcFormHeaderClass")}>
|
||||||
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
|
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
|
||||||
<div id="kc-locale">
|
<div id="kc-locale">
|
||||||
<div id="kc-locale-wrapper" className={clsx(props.kcLocaleWrapperClass)}>
|
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
|
||||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||||
<a href="#" id="kc-current-locale-link">
|
<a href="#" id="kc-current-locale-link">
|
||||||
|
@ -82,8 +83,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
)}
|
)}
|
||||||
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
|
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
|
||||||
displayRequiredFields ? (
|
displayRequiredFields ? (
|
||||||
<div className={clsx(props.kcContentWrapperClass)}>
|
<div className={getClassName("kcContentWrapperClass")}>
|
||||||
<div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
|
<div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}>
|
||||||
<span className="subtitle">
|
<span className="subtitle">
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
{msg("requiredFields")}
|
{msg("requiredFields")}
|
||||||
|
@ -97,20 +98,20 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
<h1 id="kc-page-title">{headerNode}</h1>
|
<h1 id="kc-page-title">{headerNode}</h1>
|
||||||
)
|
)
|
||||||
) : displayRequiredFields ? (
|
) : displayRequiredFields ? (
|
||||||
<div className={clsx(props.kcContentWrapperClass)}>
|
<div className={getClassName("kcContentWrapperClass")}>
|
||||||
<div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
|
<div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}>
|
||||||
<span className="subtitle">
|
<span className="subtitle">
|
||||||
<span className="required">*</span> {msg("requiredFields")}
|
<span className="required">*</span> {msg("requiredFields")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-10">
|
<div className="col-md-10">
|
||||||
{showUsernameNode}
|
{showUsernameNode}
|
||||||
<div className={clsx(props.kcFormGroupClass)}>
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<div id="kc-username">
|
<div id="kc-username">
|
||||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||||
<div className="kc-login-tooltip">
|
<div className="kc-login-tooltip">
|
||||||
<i className={clsx(props.kcResetFlowIcon)}></i>
|
<i className={getClassName("kcResetFlowIcon")}></i>
|
||||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -121,12 +122,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{showUsernameNode}
|
{showUsernameNode}
|
||||||
<div className={clsx(props.kcFormGroupClass)}>
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<div id="kc-username">
|
<div id="kc-username">
|
||||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||||
<div className="kc-login-tooltip">
|
<div className="kc-login-tooltip">
|
||||||
<i className={clsx(props.kcResetFlowIcon)}></i>
|
<i className={getClassName("kcResetFlowIcon")}></i>
|
||||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -140,10 +141,10 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||||
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
|
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
|
||||||
<div className={clsx("alert", `alert-${message.type}`)}>
|
<div className={clsx("alert", `alert-${message.type}`)}>
|
||||||
{message.type === "success" && <span className={clsx(props.kcFeedbackSuccessIcon)}></span>}
|
{message.type === "success" && <span className={getClassName("kcFeedbackSuccessIcon")}></span>}
|
||||||
{message.type === "warning" && <span className={clsx(props.kcFeedbackWarningIcon)}></span>}
|
{message.type === "warning" && <span className={getClassName("kcFeedbackWarningIcon")}></span>}
|
||||||
{message.type === "error" && <span className={clsx(props.kcFeedbackErrorIcon)}></span>}
|
{message.type === "error" && <span className={getClassName("kcFeedbackErrorIcon")}></span>}
|
||||||
{message.type === "info" && <span className={clsx(props.kcFeedbackInfoIcon)}></span>}
|
{message.type === "info" && <span className={getClassName("kcFeedbackInfoIcon")}></span>}
|
||||||
<span
|
<span
|
||||||
className="kc-feedback-text"
|
className="kc-feedback-text"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
|
@ -158,16 +159,24 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
id="kc-select-try-another-way-form"
|
id="kc-select-try-another-way-form"
|
||||||
action={url.loginAction}
|
action={url.loginAction}
|
||||||
method="post"
|
method="post"
|
||||||
className={clsx(displayWide && props.kcContentWrapperClass)}
|
className={clsx(displayWide && getClassName("kcContentWrapperClass"))}
|
||||||
>
|
>
|
||||||
<div className={clsx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}>
|
<div
|
||||||
<div className={clsx(props.kcFormGroupClass)}>
|
className={clsx(
|
||||||
|
displayWide && [getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass")]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||||
<a href="#" id="try-another-way" onClick={() => {
|
<a
|
||||||
document.forms["kc-select-try-another-way-form" as never].submit();
|
href="#"
|
||||||
return false;
|
id="try-another-way"
|
||||||
}}>
|
onClick={() => {
|
||||||
|
document.forms["kc-select-try-another-way-form" as never].submit();
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
{msg("doTryAnotherWay")}
|
{msg("doTryAnotherWay")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -175,8 +184,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
{displayInfo && (
|
{displayInfo && (
|
||||||
<div id="kc-info" className={clsx(props.kcSignUpClass)}>
|
<div id="kc-info" className={getClassName("kcSignUpClass")}>
|
||||||
<div id="kc-info-wrapper" className={clsx(props.kcInfoAreaWrapperClass)}>
|
<div id="kc-info-wrapper" className={getClassName("kcInfoAreaWrapperClass")}>
|
||||||
{infoNode}
|
{infoNode}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,32 +1,24 @@
|
||||||
import { useI18n as useI18nBase } from "keycloakify";
|
import { createUseI18n } from "keycloakify";
|
||||||
|
|
||||||
type Props = Omit<Parameters<typeof useI18nBase>[0], "extraMessages">;
|
export const { useI18n } = createUseI18n({
|
||||||
|
// NOTE: Here you can override the default i18n messages
|
||||||
export function useI18n(props: Props) {
|
// or define new ones that, for example, you would have
|
||||||
const { kcContext } = props;
|
// defined in the Keycloak admin UI for UserProfile
|
||||||
return useI18nBase({
|
// https://user-images.githubusercontent.com/6702424/182050652-522b6fe6-8ee5-49df-aca3-dba2d33f24a5.png
|
||||||
kcContext,
|
en: {
|
||||||
// NOTE: Here you can override the default i18n messages
|
alphanumericalCharsOnly: "Only alphanumerical characters",
|
||||||
// or define new ones that, for example, you would have
|
gender: "Gender",
|
||||||
// defined in the Keycloak admin UI for UserProfile
|
// Here we overwrite the default english value for the message "doForgotPassword"
|
||||||
// https://user-images.githubusercontent.com/6702424/182050652-522b6fe6-8ee5-49df-aca3-dba2d33f24a5.png
|
// that is "Forgot Password?" see: https://github.com/InseeFrLab/keycloakify/blob/f0ae5ea908e0aa42391af323b6d5e2fd371af851/src/lib/i18n/generated_messages/18.0.1/login/en.ts#L17
|
||||||
"extraMessages": {
|
doForgotPassword: "I forgot my password",
|
||||||
"en": {
|
},
|
||||||
"alphanumericalCharsOnly": "Only alphanumerical characters",
|
fr: {
|
||||||
"gender": "Gender",
|
/* spell-checker: disable */
|
||||||
// Here we overwrite the default english value for the message "doForgotPassword"
|
alphanumericalCharsOnly: "Caractère alphanumérique uniquement",
|
||||||
// that is "Forgot Password?" see: https://github.com/InseeFrLab/keycloakify/blob/f0ae5ea908e0aa42391af323b6d5e2fd371af851/src/lib/i18n/generated_messages/18.0.1/login/en.ts#L17
|
gender: "Genre",
|
||||||
"doForgotPassword": "I forgot my password",
|
doForgotPassword: "J'ai oublié mon mot de passe"
|
||||||
},
|
/* spell-checker: enable */
|
||||||
"fr": {
|
}
|
||||||
/* spell-checker: disable */
|
});
|
||||||
"alphanumericalCharsOnly": "Caractère alphanumérique uniquement",
|
|
||||||
"gender": "Genre",
|
|
||||||
"doForgotPassword": "J'ai oublié mon mot de passe"
|
|
||||||
/* spell-checker: enable */
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export type I18n = NonNullable<ReturnType<typeof useI18n>>;
|
export type I18n = NonNullable<ReturnType<typeof useI18n>>;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getKcContext } from "keycloakify/lib/getKcContext";
|
import { getKcContext } from "keycloakify";
|
||||||
|
|
||||||
export type KcContextExtension =
|
export type KcContextExtension =
|
||||||
// NOTE: A 'keycloakify' field must be added
|
// NOTE: A 'keycloakify' field must be added
|
||||||
|
|
|
@ -1,198 +1,204 @@
|
||||||
|
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/Login.tsx
|
||||||
|
|
||||||
import { useState, type FormEventHandler } from "react";
|
import { useState, type FormEventHandler } from "react";
|
||||||
// This is a copy paste from https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/Login.tsx
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
// You can replace all relative imports by cherry picking files from the keycloakify module.
|
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
||||||
// For example, the following import:
|
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
|
||||||
// import { clsx } from "./tools/clsx";
|
import { useGetClassName } from "keycloakify/lib/useGetClassName";
|
||||||
// becomes:
|
|
||||||
import { clsx } from "keycloakify/lib/tools/clsx";
|
|
||||||
import { useConstCallback } from "keycloakify/lib/tools/useConstCallback";
|
|
||||||
import type { PageProps } from "keycloakify/lib/KcProps";
|
|
||||||
// Here use your own KcContext and I18n that you might have overloaded.
|
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl"; }>, I18n>) {
|
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
const { getClassName } = useGetClassName({
|
||||||
|
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||||
|
|
||||||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
setIsLoginButtonDisabled(true);
|
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const formElement = e.target as HTMLFormElement;
|
setIsLoginButtonDisabled(true);
|
||||||
|
|
||||||
//NOTE: Even if we login with email Keycloak expect username and password in
|
const formElement = e.target as HTMLFormElement;
|
||||||
//the POST request.
|
|
||||||
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
|
|
||||||
|
|
||||||
formElement.submit();
|
//NOTE: Even if we login with email Keycloak expect username and password in
|
||||||
});
|
//the POST request.
|
||||||
|
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
|
||||||
|
|
||||||
return (
|
formElement.submit();
|
||||||
<Template
|
});
|
||||||
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
|
|
||||||
displayInfo={social.displayInfo}
|
|
||||||
displayWide={realm.password && social.providers !== undefined}
|
|
||||||
headerNode={msg("doLogIn")}
|
|
||||||
formNode={
|
|
||||||
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
|
|
||||||
<div
|
|
||||||
id="kc-form-wrapper"
|
|
||||||
className={clsx(
|
|
||||||
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{realm.password && (
|
|
||||||
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
|
||||||
<div className={clsx(kcProps.kcFormGroupClass)}>
|
|
||||||
{(() => {
|
|
||||||
const label = !realm.loginWithEmailAllowed
|
|
||||||
? "username"
|
|
||||||
: realm.registrationEmailAsUsername
|
|
||||||
? "email"
|
|
||||||
: "usernameOrEmail";
|
|
||||||
|
|
||||||
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
|
displayInfo={social.displayInfo}
|
||||||
|
displayWide={realm.password && social.providers !== undefined}
|
||||||
|
headerNode={msg("doLogIn")}
|
||||||
|
formNode={
|
||||||
|
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && getClassName("kcContentWrapperClass"))}>
|
||||||
|
<div
|
||||||
|
id="kc-form-wrapper"
|
||||||
|
className={clsx(
|
||||||
|
realm.password &&
|
||||||
|
social.providers && [getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass")]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{realm.password && (
|
||||||
|
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
||||||
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
|
{(() => {
|
||||||
|
const label = !realm.loginWithEmailAllowed
|
||||||
|
? "username"
|
||||||
|
: realm.registrationEmailAsUsername
|
||||||
|
? "email"
|
||||||
|
: "usernameOrEmail";
|
||||||
|
|
||||||
return (
|
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
|
||||||
<>
|
|
||||||
<label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
|
return (
|
||||||
{msg(label)}
|
<>
|
||||||
</label>
|
<label htmlFor={autoCompleteHelper} className={getClassName("kcLabelClass")}>
|
||||||
<input
|
{msg(label)}
|
||||||
tabIndex={1}
|
</label>
|
||||||
id={autoCompleteHelper}
|
<input
|
||||||
className={clsx(kcProps.kcInputClass)}
|
tabIndex={1}
|
||||||
//NOTE: This is used by Google Chrome auto fill so we use it to tell
|
id={autoCompleteHelper}
|
||||||
//the browser how to pre fill the form but before submit we put it back
|
className={getClassName("kcInputClass")}
|
||||||
//to username because it is what keycloak expects.
|
//NOTE: This is used by Google Chrome auto fill so we use it to tell
|
||||||
name={autoCompleteHelper}
|
//the browser how to pre fill the form but before submit we put it back
|
||||||
defaultValue={login.username ?? ""}
|
//to username because it is what keycloak expects.
|
||||||
type="text"
|
name={autoCompleteHelper}
|
||||||
{...(usernameEditDisabled
|
defaultValue={login.username ?? ""}
|
||||||
? { "disabled": true }
|
type="text"
|
||||||
: {
|
{...(usernameEditDisabled
|
||||||
"autoFocus": true,
|
? { "disabled": true }
|
||||||
"autoComplete": "off"
|
: {
|
||||||
})}
|
"autoFocus": true,
|
||||||
/>
|
"autoComplete": "off"
|
||||||
</>
|
})}
|
||||||
);
|
/>
|
||||||
})()}
|
</>
|
||||||
</div>
|
);
|
||||||
<div className={clsx(kcProps.kcFormGroupClass)}>
|
})()}
|
||||||
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
|
</div>
|
||||||
{msg("password")}
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
</label>
|
<label htmlFor="password" className={getClassName("kcLabelClass")}>
|
||||||
<input
|
{msg("password")}
|
||||||
tabIndex={2}
|
</label>
|
||||||
id="password"
|
<input
|
||||||
className={clsx(kcProps.kcInputClass)}
|
tabIndex={2}
|
||||||
name="password"
|
id="password"
|
||||||
type="password"
|
className={getClassName("kcInputClass")}
|
||||||
autoComplete="off"
|
name="password"
|
||||||
/>
|
type="password"
|
||||||
</div>
|
autoComplete="off"
|
||||||
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
|
/>
|
||||||
<div id="kc-form-options">
|
</div>
|
||||||
{realm.rememberMe && !usernameEditDisabled && (
|
<div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}>
|
||||||
<div className="checkbox">
|
<div id="kc-form-options">
|
||||||
<label>
|
{realm.rememberMe && !usernameEditDisabled && (
|
||||||
<input
|
<div className="checkbox">
|
||||||
tabIndex={3}
|
<label>
|
||||||
id="rememberMe"
|
<input
|
||||||
name="rememberMe"
|
tabIndex={3}
|
||||||
type="checkbox"
|
id="rememberMe"
|
||||||
{...(login.rememberMe
|
name="rememberMe"
|
||||||
? {
|
type="checkbox"
|
||||||
"checked": true
|
{...(login.rememberMe
|
||||||
}
|
? {
|
||||||
: {})}
|
"checked": true
|
||||||
/>
|
}
|
||||||
{msg("rememberMe")}
|
: {})}
|
||||||
</label>
|
/>
|
||||||
</div>
|
{msg("rememberMe")}
|
||||||
)}
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
|
)}
|
||||||
{realm.resetPasswordAllowed && (
|
</div>
|
||||||
<span>
|
<div className={getClassName("kcFormOptionsWrapperClass")}>
|
||||||
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
|
{realm.resetPasswordAllowed && (
|
||||||
{msg("doForgotPassword")}
|
<span>
|
||||||
</a>
|
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
|
||||||
</span>
|
{msg("doForgotPassword")}
|
||||||
)}
|
</a>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
)}
|
||||||
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
|
</div>
|
||||||
<input
|
</div>
|
||||||
type="hidden"
|
<div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}>
|
||||||
id="id-hidden-input"
|
<input
|
||||||
name="credentialId"
|
type="hidden"
|
||||||
{...(auth?.selectedCredential !== undefined
|
id="id-hidden-input"
|
||||||
? {
|
name="credentialId"
|
||||||
"value": auth.selectedCredential
|
{...(auth?.selectedCredential !== undefined
|
||||||
}
|
? {
|
||||||
: {})}
|
"value": auth.selectedCredential
|
||||||
/>
|
}
|
||||||
<input
|
: {})}
|
||||||
tabIndex={4}
|
/>
|
||||||
className={clsx(
|
<input
|
||||||
kcProps.kcButtonClass,
|
tabIndex={4}
|
||||||
kcProps.kcButtonPrimaryClass,
|
className={clsx(
|
||||||
kcProps.kcButtonBlockClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonLargeClass
|
getClassName("kcButtonPrimaryClass"),
|
||||||
)}
|
getClassName("kcButtonBlockClass"),
|
||||||
name="login"
|
getClassName("kcButtonLargeClass")
|
||||||
id="kc-login"
|
)}
|
||||||
type="submit"
|
name="login"
|
||||||
value={msgStr("doLogIn")}
|
id="kc-login"
|
||||||
disabled={isLoginButtonDisabled}
|
type="submit"
|
||||||
/>
|
value={msgStr("doLogIn")}
|
||||||
</div>
|
disabled={isLoginButtonDisabled}
|
||||||
</form>
|
/>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</form>
|
||||||
{realm.password && social.providers !== undefined && (
|
)}
|
||||||
<div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
|
</div>
|
||||||
<ul
|
{realm.password && social.providers !== undefined && (
|
||||||
className={clsx(
|
<div
|
||||||
kcProps.kcFormSocialAccountListClass,
|
id="kc-social-providers"
|
||||||
social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
|
className={clsx(getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass"))}
|
||||||
)}
|
>
|
||||||
>
|
<ul
|
||||||
{social.providers.map(p => (
|
className={clsx(
|
||||||
<li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
|
getClassName("kcFormSocialAccountListClass"),
|
||||||
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}>
|
social.providers.length > 4 && getClassName("kcFormSocialAccountDoubleListClass")
|
||||||
<span>{p.displayName}</span>
|
)}
|
||||||
</a>
|
>
|
||||||
</li>
|
{social.providers.map(p => (
|
||||||
))}
|
<li key={p.providerId} className={getClassName("kcFormSocialAccountListLinkClass")}>
|
||||||
</ul>
|
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}>
|
||||||
</div>
|
<span>{p.displayName}</span>
|
||||||
)}
|
</a>
|
||||||
</div>
|
</li>
|
||||||
}
|
))}
|
||||||
infoNode={
|
</ul>
|
||||||
realm.password &&
|
</div>
|
||||||
realm.registrationAllowed &&
|
)}
|
||||||
!registrationDisabled && (
|
</div>
|
||||||
<div id="kc-registration">
|
}
|
||||||
<span>
|
infoNode={
|
||||||
{msg("noAccount")}
|
realm.password &&
|
||||||
<a tabIndex={6} href={url.registrationUrl}>
|
realm.registrationAllowed &&
|
||||||
{msg("doRegister")}
|
!registrationDisabled && (
|
||||||
</a>
|
<div id="kc-registration">
|
||||||
</span>
|
<span>
|
||||||
</div>
|
{msg("noAccount")}
|
||||||
)
|
<a tabIndex={6} href={url.registrationUrl}>
|
||||||
}
|
{msg("doRegister")}
|
||||||
/>
|
</a>
|
||||||
);
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import type { PageProps } from "keycloakify";
|
import type { PageProps } from "keycloakify/pages/PageProps";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageId: "my-extra-page-1.ftl"; }>, I18n>) {
|
export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageId: "my-extra-page-1.ftl"; }>, I18n>) {
|
||||||
|
|
||||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
headerNode={<>Header <i>text</i></>}
|
headerNode={<>Header <i>text</i></>}
|
||||||
formNode={
|
formNode={
|
||||||
<form>
|
<form>
|
||||||
{/*...*/}
|
{/*...*/}
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
infoNode={<span>footer</span> }
|
infoNode={<span>footer</span>}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import type { PageProps } from "keycloakify";
|
import type { PageProps } from "keycloakify/pages/PageProps";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageId: "my-extra-page-2.ftl"; }>, I18n>) {
|
export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageId: "my-extra-page-2.ftl"; }>, I18n>) {
|
||||||
|
|
||||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
// someCustomValue is declared by you in ../kcContext.ts
|
// someCustomValue is declared by you in ../kcContext.ts
|
||||||
console.log(`TODO: Do something with: ${kcContext.someCustomValue}`);
|
console.log(`TODO: Do something with: ${kcContext.someCustomValue}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
headerNode={<>Header <i>text</i></>}
|
headerNode={<>Header <i>text</i></>}
|
||||||
formNode={
|
formNode={
|
||||||
<form>
|
<form>
|
||||||
|
|
|
@ -1,74 +1,89 @@
|
||||||
// This is a copy paste from https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/Register.tsx
|
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/Register.tsx
|
||||||
// It is now up to us to implement a special behavior to leverage the non standard authorizedMailDomains
|
|
||||||
// provided by the plugin: https://github.com/micedre/keycloak-mail-whitelisting installed on our keycloak server.
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
// Note that it is no longer recommended to use register.ftl, it's best to use register-user-profile.ftl
|
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
|
||||||
// See: https://docs.keycloakify.dev/realtime-input-validation
|
import { useGetClassName } from "keycloakify/lib/useGetClassName";
|
||||||
import { clsx } from "keycloakify/lib/tools/clsx";
|
|
||||||
import type { PageProps } from "keycloakify/lib/KcProps";
|
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export default function Register(props: PageProps<Extract<KcContext, { pageId: "register.ftl"; }>, I18n>) {
|
export default function Register(props: PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
console.log(`NOTE: It is up to you do do something meaningful with ${kcContext.authorizedMailDomains}`);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
headerNode={msg("registerTitle")}
|
headerNode={msg("registerTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
|
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
|
||||||
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
|
<div
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
className={clsx(
|
||||||
<label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}>
|
getClassName("kcFormGroupClass"),
|
||||||
|
messagesPerField.printIfExists("firstName", getClassName("kcFormGroupErrorClass"))
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<label htmlFor="firstName" className={getClassName("kcLabelClass")}>
|
||||||
{msg("firstName")}
|
{msg("firstName")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="firstName"
|
id="firstName"
|
||||||
className={clsx(kcProps.kcInputClass)}
|
className={getClassName("kcInputClass")}
|
||||||
name="firstName"
|
name="firstName"
|
||||||
defaultValue={register.formData.firstName ?? ""}
|
defaultValue={register.formData.firstName ?? ""}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
|
<div
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
className={clsx(
|
||||||
<label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}>
|
getClassName("kcFormGroupClass"),
|
||||||
|
messagesPerField.printIfExists("lastName", getClassName("kcFormGroupErrorClass"))
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<label htmlFor="lastName" className={getClassName("kcLabelClass")}>
|
||||||
{msg("lastName")}
|
{msg("lastName")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="lastName"
|
id="lastName"
|
||||||
className={clsx(kcProps.kcInputClass)}
|
className={getClassName("kcInputClass")}
|
||||||
name="lastName"
|
name="lastName"
|
||||||
defaultValue={register.formData.lastName ?? ""}
|
defaultValue={register.formData.lastName ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
|
<div
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
className={clsx(
|
||||||
<label htmlFor="email" className={clsx(kcProps.kcLabelClass)}>
|
getClassName("kcFormGroupClass"),
|
||||||
|
messagesPerField.printIfExists("email", getClassName("kcFormGroupErrorClass"))
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<label htmlFor="email" className={getClassName("kcLabelClass")}>
|
||||||
{msg("email")}
|
{msg("email")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="email"
|
id="email"
|
||||||
className={clsx(kcProps.kcInputClass)}
|
className={getClassName("kcInputClass")}
|
||||||
name="email"
|
name="email"
|
||||||
defaultValue={register.formData.email ?? ""}
|
defaultValue={register.formData.email ?? ""}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
|
@ -76,17 +91,22 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: "
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!realm.registrationEmailAsUsername && (
|
{!realm.registrationEmailAsUsername && (
|
||||||
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
|
<div
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
className={clsx(
|
||||||
<label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
|
getClassName("kcFormGroupClass"),
|
||||||
|
messagesPerField.printIfExists("username", getClassName("kcFormGroupErrorClass"))
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<label htmlFor="username" className={getClassName("kcLabelClass")}>
|
||||||
{msg("username")}
|
{msg("username")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="username"
|
id="username"
|
||||||
className={clsx(kcProps.kcInputClass)}
|
className={getClassName("kcInputClass")}
|
||||||
name="username"
|
name="username"
|
||||||
defaultValue={register.formData.username ?? ""}
|
defaultValue={register.formData.username ?? ""}
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
|
@ -97,18 +117,21 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: "
|
||||||
{passwordRequired && (
|
{passwordRequired && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}
|
className={clsx(
|
||||||
|
getClassName("kcFormGroupClass"),
|
||||||
|
messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass"))
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
|
<label htmlFor="password" className={getClassName("kcLabelClass")}>
|
||||||
{msg("password")}
|
{msg("password")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
className={clsx(kcProps.kcInputClass)}
|
className={getClassName("kcInputClass")}
|
||||||
name="password"
|
name="password"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
|
@ -117,44 +140,44 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: "
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
kcProps.kcFormGroupClass,
|
getClassName("kcFormGroupClass"),
|
||||||
messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass)
|
messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass"))
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={clsx(kcProps.kcLabelWrapperClass)}>
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
<label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
|
<label htmlFor="password-confirm" className={getClassName("kcLabelClass")}>
|
||||||
{msg("passwordConfirm")}
|
{msg("passwordConfirm")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<input type="password" id="password-confirm" className={clsx(kcProps.kcInputClass)} name="password-confirm" />
|
<input type="password" id="password-confirm" className={getClassName("kcInputClass")} name="password-confirm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{recaptchaRequired && (
|
{recaptchaRequired && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={clsx(kcProps.kcFormGroupClass)}>
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
|
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
|
||||||
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
|
<div className={getClassName("kcFormOptionsWrapperClass")}>
|
||||||
<span>
|
<span>
|
||||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
|
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
|
||||||
<input
|
<input
|
||||||
className={clsx(
|
className={clsx(
|
||||||
kcProps.kcButtonClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonPrimaryClass,
|
getClassName("kcButtonPrimaryClass"),
|
||||||
kcProps.kcButtonBlockClass,
|
getClassName("kcButtonBlockClass"),
|
||||||
kcProps.kcButtonLargeClass
|
getClassName("kcButtonLargeClass")
|
||||||
)}
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
value={msgStr("doRegister")}
|
value={msgStr("doRegister")}
|
||||||
|
@ -166,4 +189,3 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: "
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/RegisterUserProfile.tsx
|
// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/RegisterUserProfile.tsx
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { clsx } from "keycloakify/lib/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { UserProfileFormFields } from "keycloakify/lib/pages/shared/UserProfileCommons";
|
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||||
import type { PageProps } from "keycloakify/lib/KcProps";
|
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
|
||||||
|
import { useGetClassName } from "keycloakify/lib/useGetClassName";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export default function RegisterUserProfile(props: PageProps<Extract<KcContext, { pageId: "register-user-profile.ftl" }>, I18n>) {
|
export default function RegisterUserProfile(props: PageProps<Extract<KcContext, { pageId: "register-user-profile.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
|
@ -17,36 +24,41 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContext,
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
displayMessage={messagesPerField.exists("global")}
|
displayMessage={messagesPerField.exists("global")}
|
||||||
displayRequiredFields={true}
|
displayRequiredFields={true}
|
||||||
headerNode={msg("registerTitle")}
|
headerNode={msg("registerTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
|
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
|
||||||
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
|
<UserProfileFormFields
|
||||||
|
kcContext={kcContext}
|
||||||
|
onIsFormSubmittableValueChange={setIsFomSubmittable}
|
||||||
|
i18n={i18n}
|
||||||
|
getClassName={getClassName}
|
||||||
|
/>
|
||||||
{recaptchaRequired && (
|
{recaptchaRequired && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className={clsx(kcProps.kcInputWrapperClass)}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
|
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={clsx(kcProps.kcFormGroupClass)} style={{ "marginBottom": 30 }}>
|
<div className={getClassName("kcFormGroupClass")} style={{ "marginBottom": 30 }}>
|
||||||
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
|
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
|
||||||
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
|
<div className={getClassName("kcFormOptionsWrapperClass")}>
|
||||||
<span>
|
<span>
|
||||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
|
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
|
||||||
<input
|
<input
|
||||||
className={clsx(
|
className={clsx(
|
||||||
kcProps.kcButtonClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonPrimaryClass,
|
getClassName("kcButtonPrimaryClass"),
|
||||||
kcProps.kcButtonBlockClass,
|
getClassName("kcButtonBlockClass"),
|
||||||
kcProps.kcButtonLargeClass
|
getClassName("kcButtonLargeClass")
|
||||||
)}
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
value={msgStr("doRegister")}
|
value={msgStr("doRegister")}
|
||||||
|
|
|
@ -5,45 +5,28 @@
|
||||||
* in the KcApp.tsx
|
* in the KcApp.tsx
|
||||||
* Example: https://github.com/garronej/keycloakify-starter/blob/a20c21b2aae7c6dc6dbea294f3d321955ddf9355/src/KcApp/KcApp.tsx#L14-L30
|
* Example: https://github.com/garronej/keycloakify-starter/blob/a20c21b2aae7c6dc6dbea294f3d321955ddf9355/src/KcApp/KcApp.tsx#L14-L30
|
||||||
*/
|
*/
|
||||||
import {clsx} from "keycloakify/lib/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import {useRerenderOnStateChange} from "evt/hooks";
|
import { useRerenderOnStateChange } from "evt/hooks";
|
||||||
import {Markdown} from "keycloakify/lib/tools/Markdown";
|
import { Markdown } from "keycloakify/tools/Markdown";
|
||||||
import {evtTermMarkdown, useDownloadTerms} from "keycloakify/lib/pages/Terms";
|
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
|
||||||
import tos_en_url from "../assets/tos_en.md";
|
import { useGetClassName } from "keycloakify/lib/useGetClassName";
|
||||||
import tos_fr_url from "../assets/tos_fr.md";
|
import { evtTermMarkdown } from "keycloakify/lib/useDownloadTerms";
|
||||||
import type {PageProps} from "keycloakify/lib/KcProps";
|
import type { KcContext } from "../kcContext";
|
||||||
import type {KcContext} from "../kcContext";
|
import type { I18n } from "../i18n";
|
||||||
import type {I18n} from "../i18n";
|
|
||||||
|
|
||||||
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl"; }>, I18n>) {
|
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
|
||||||
const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
const {msg, msgStr} = i18n;
|
const { getClassName } = useGetClassName({
|
||||||
|
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
|
||||||
useDownloadTerms({
|
classes
|
||||||
kcContext,
|
|
||||||
"downloadTermMarkdown": async ({currentLanguageTag}) => {
|
|
||||||
|
|
||||||
const resource = (() => {
|
|
||||||
switch (currentLanguageTag) {
|
|
||||||
case "fr":
|
|
||||||
return tos_fr_url;
|
|
||||||
default:
|
|
||||||
return tos_en_url;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// webpack5 (used via storybook) loads markdown as string, not url
|
|
||||||
if (resource.includes("\n")) return resource
|
|
||||||
|
|
||||||
const response = await fetch(resource);
|
|
||||||
return response.text();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
useRerenderOnStateChange(evtTermMarkdown);
|
useRerenderOnStateChange(evtTermMarkdown);
|
||||||
|
|
||||||
const {url} = kcContext;
|
const { url } = kcContext;
|
||||||
|
|
||||||
if (evtTermMarkdown.state === undefined) {
|
if (evtTermMarkdown.state === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -51,21 +34,20 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("termsTitle")}
|
headerNode={msg("termsTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<>
|
<>
|
||||||
<div id="kc-terms-text">{evtTermMarkdown.state &&
|
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
|
||||||
<Markdown>{evtTermMarkdown.state}</Markdown>}</div>
|
|
||||||
<form className="form-actions" action={url.loginAction} method="POST">
|
<form className="form-actions" action={url.loginAction} method="POST">
|
||||||
<input
|
<input
|
||||||
className={clsx(
|
className={clsx(
|
||||||
kcProps.kcButtonClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonClass,
|
getClassName("kcButtonClass"),
|
||||||
kcProps.kcButtonPrimaryClass,
|
getClassName("kcButtonPrimaryClass"),
|
||||||
kcProps.kcButtonLargeClass
|
getClassName("kcButtonLargeClass")
|
||||||
)}
|
)}
|
||||||
name="accept"
|
name="accept"
|
||||||
id="kc-accept"
|
id="kc-accept"
|
||||||
|
@ -73,14 +55,14 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
||||||
value={msgStr("doAccept")}
|
value={msgStr("doAccept")}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
|
className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))}
|
||||||
name="cancel"
|
name="cancel"
|
||||||
id="kc-decline"
|
id="kc-decline"
|
||||||
type="submit"
|
type="submit"
|
||||||
value={msgStr("doDecline")}
|
value={msgStr("doDecline")}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<div className="clearfix"/>
|
<div className="clearfix" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue