diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/keycloak-theme/KcApp.tsx | 48 | ||||
| -rw-r--r-- | src/keycloak-theme/Template.tsx | 103 | ||||
| -rw-r--r-- | src/keycloak-theme/i18n.ts | 50 | ||||
| -rw-r--r-- | src/keycloak-theme/kcContext.ts | 2 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Login.tsx | 370 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/MyExtraPage1.tsx | 8 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/MyExtraPage2.tsx | 6 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Register.tsx | 132 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/RegisterUserProfile.tsx | 46 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Terms.tsx | 68 |
10 files changed, 422 insertions, 411 deletions
diff --git a/src/keycloak-theme/KcApp.tsx b/src/keycloak-theme/KcApp.tsx index 1235b6b..81d83ca 100644 --- a/src/keycloak-theme/KcApp.tsx +++ b/src/keycloak-theme/KcApp.tsx @@ -2,31 +2,29 @@ import "./KcApp.css"; import { lazy, Suspense } from "react"; import type { KcContext } from "./kcContext"; import { useI18n } from "./i18n"; -import Fallback, { defaultKcProps, type KcProps, type PageProps } from "keycloakify"; -import Template from "./Template"; -import DefaultTemplate from "keycloakify/lib/Template"; +import Fallback, { type PageProps } from "keycloakify"; + +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. //import { foo, bar } from "./valuesTransferredOverUrl"; //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 const Register = lazy(() => import("./pages/Register")); const RegisterUserProfile = lazy(() => import("./pages/RegisterUserProfile")); const Terms = lazy(() => import("./pages/Terms")); const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1")); 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 -const kcProps: KcProps = { - ...defaultKcProps, +const classes: PageProps<any, any>["classes"] = { // NOTE: The classes are defined in ./KcApp.css - // You can add your classes alongside thoses that are present in the default Keycloak theme... - "kcHtmlClass": [...defaultKcProps.kcHtmlClass, "my-root-class"], - // ...or overwrite + "kcHtmlClass": "my-root-class", "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. return null; } - + /* * Examples assuming i18n.currentLanguageTag === "en": * i18n.msg("access-denied") === <span>Access denied</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 ( <Suspense> {(() => { switch (kcContext.pageId) { - case "login.ftl": return <Login {...{ kcContext, ...pageProps }} />; - case "register.ftl": return <Register {...{ kcContext, ...pageProps }} />; - case "register-user-profile.ftl": return <RegisterUserProfile {...{ kcContext, ...pageProps }} /> - case "terms.ftl": return <Terms {...{ kcContext, ...pageProps }} />; - case "my-extra-page-1.ftl": return <MyExtraPage1 {...{ kcContext, ...pageProps }} />; - case "my-extra-page-2.ftl": return <MyExtraPage2 {...{ kcContext, ...pageProps }} />; + case "login.ftl": return <Login {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />; + case "register.ftl": return <Register {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />; + case "register-user-profile.ftl": return <RegisterUserProfile {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} /> + case "terms.ftl": return <Terms {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />; + case "my-extra-page-1.ftl": return <MyExtraPage1 {...{ kcContext, i18n, Template, classes, "doUseDefaultCss": true }} />; + 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. - case "info.ftl": return <Info {...{ kcContext, ...pageProps}} Template={DefaultTemplate} doFetchDefaultThemeResources={true} />; - default: return <Fallback {...{ kcContext, ...pageProps }} />; + case "info.ftl": return <Info {...{ kcContext, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />; + default: return <Fallback {...{ kcContext, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />; } })()} </Suspense> diff --git a/src/keycloak-theme/Template.tsx b/src/keycloak-theme/Template.tsx index bd4e46a..57a1566 100644 --- a/src/keycloak-theme/Template.tsx +++ b/src/keycloak-theme/Template.tsx @@ -1,13 +1,9 @@ -// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/components/shared/Template.tsx - -// You can replace all relative imports by cherry picking files from the keycloakify module. -// For example, the following import: -// import { assert } from "./tools/assert"; -// becomes: -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"; +// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/Template.tsx +import { assert } from "keycloakify/tools/assert"; +import { clsx } from "keycloakify/tools/clsx"; +import { usePrepareTemplate } from "keycloakify/lib/usePrepareTemplate"; +import { type TemplateProps, defaultTemplateClasses } from "keycloakify/TemplateProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; import type { KcContext } from "./kcContext"; import type { I18n } from "./i18n"; @@ -24,24 +20,29 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { infoNode = null, kcContext, i18n, - doFetchDefaultThemeResources, - stylesCommon, - styles, - scripts, - kcHtmlClass + doUseDefaultCss, + classes } = props; + const { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultTemplateClasses, + classes + }); + const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext; const { isReady } = usePrepareTemplate({ - doFetchDefaultThemeResources, - stylesCommon, - styles, - scripts, + "doFetchDefaultThemeResources": doUseDefaultCss, 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) { @@ -49,18 +50,18 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { } return ( - <div className={clsx(props.kcLoginClass)}> - <div id="kc-header" className={clsx(props.kcHeaderClass)}> - <div id="kc-header-wrapper" className={clsx(props.kcHeaderWrapperClass)}> + <div className={getClassName("kcLoginClass")}> + <div id="kc-header" className={getClassName("kcHeaderClass")}> + <div id="kc-header-wrapper" className={getClassName("kcHeaderWrapperClass")}> {msg("loginTitleHtml", realm.displayNameHtml)} </div> </div> - <div className={clsx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}> - <header className={clsx(props.kcFormHeaderClass)}> + <div className={clsx(getClassName("kcFormCardClass"), displayWide && getClassName("kcFormCardAccountClass"))}> + <header className={getClassName("kcFormHeaderClass")}> {realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && ( <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"> {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} <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) ? ( displayRequiredFields ? ( - <div className={clsx(props.kcContentWrapperClass)}> - <div className={clsx(props.kcLabelWrapperClass, "subtitle")}> + <div className={getClassName("kcContentWrapperClass")}> + <div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}> <span className="subtitle"> <span className="required">*</span> {msg("requiredFields")} @@ -97,20 +98,20 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { <h1 id="kc-page-title">{headerNode}</h1> ) ) : displayRequiredFields ? ( - <div className={clsx(props.kcContentWrapperClass)}> - <div className={clsx(props.kcLabelWrapperClass, "subtitle")}> + <div className={getClassName("kcContentWrapperClass")}> + <div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}> <span className="subtitle"> <span className="required">*</span> {msg("requiredFields")} </span> </div> <div className="col-md-10"> {showUsernameNode} - <div className={clsx(props.kcFormGroupClass)}> + <div className={getClassName("kcFormGroupClass")}> <div id="kc-username"> <label id="kc-attempted-username">{auth?.attemptedUsername}</label> <a id="reset-login" href={url.loginRestartFlowUrl}> <div className="kc-login-tooltip"> - <i className={clsx(props.kcResetFlowIcon)}></i> + <i className={getClassName("kcResetFlowIcon")}></i> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> </div> </a> @@ -121,12 +122,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { ) : ( <> {showUsernameNode} - <div className={clsx(props.kcFormGroupClass)}> + <div className={getClassName("kcFormGroupClass")}> <div id="kc-username"> <label id="kc-attempted-username">{auth?.attemptedUsername}</label> <a id="reset-login" href={url.loginRestartFlowUrl}> <div className="kc-login-tooltip"> - <i className={clsx(props.kcResetFlowIcon)}></i> + <i className={getClassName("kcResetFlowIcon")}></i> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> </div> </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. */} {displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && ( <div className={clsx("alert", `alert-${message.type}`)}> - {message.type === "success" && <span className={clsx(props.kcFeedbackSuccessIcon)}></span>} - {message.type === "warning" && <span className={clsx(props.kcFeedbackWarningIcon)}></span>} - {message.type === "error" && <span className={clsx(props.kcFeedbackErrorIcon)}></span>} - {message.type === "info" && <span className={clsx(props.kcFeedbackInfoIcon)}></span>} + {message.type === "success" && <span className={getClassName("kcFeedbackSuccessIcon")}></span>} + {message.type === "warning" && <span className={getClassName("kcFeedbackWarningIcon")}></span>} + {message.type === "error" && <span className={getClassName("kcFeedbackErrorIcon")}></span>} + {message.type === "info" && <span className={getClassName("kcFeedbackInfoIcon")}></span>} <span className="kc-feedback-text" dangerouslySetInnerHTML={{ @@ -158,16 +159,24 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { id="kc-select-try-another-way-form" action={url.loginAction} method="post" - className={clsx(displayWide && props.kcContentWrapperClass)} + className={clsx(displayWide && getClassName("kcContentWrapperClass"))} > - <div className={clsx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}> - <div className={clsx(props.kcFormGroupClass)}> + <div + className={clsx( + displayWide && [getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass")] + )} + > + <div className={getClassName("kcFormGroupClass")}> <input type="hidden" name="tryAnotherWay" value="on" /> {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - <a href="#" id="try-another-way" onClick={() => { - document.forms["kc-select-try-another-way-form" as never].submit(); - return false; - }}> + <a + href="#" + id="try-another-way" + onClick={() => { + document.forms["kc-select-try-another-way-form" as never].submit(); + return false; + }} + > {msg("doTryAnotherWay")} </a> </div> @@ -175,8 +184,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { </form> )} {displayInfo && ( - <div id="kc-info" className={clsx(props.kcSignUpClass)}> - <div id="kc-info-wrapper" className={clsx(props.kcInfoAreaWrapperClass)}> + <div id="kc-info" className={getClassName("kcSignUpClass")}> + <div id="kc-info-wrapper" className={getClassName("kcInfoAreaWrapperClass")}> {infoNode} </div> </div> diff --git a/src/keycloak-theme/i18n.ts b/src/keycloak-theme/i18n.ts index 3b89a1e..bbeab0b 100644 --- a/src/keycloak-theme/i18n.ts +++ b/src/keycloak-theme/i18n.ts @@ -1,32 +1,24 @@ -import { useI18n as useI18nBase } from "keycloakify"; +import { createUseI18n } from "keycloakify"; -type Props = Omit<Parameters<typeof useI18nBase>[0], "extraMessages">; - -export function useI18n(props: Props) { - const { kcContext } = props; - return useI18nBase({ - kcContext, - // NOTE: Here you can override the default i18n messages - // or define new ones that, for example, you would have - // defined in the Keycloak admin UI for UserProfile - // https://user-images.githubusercontent.com/6702424/182050652-522b6fe6-8ee5-49df-aca3-dba2d33f24a5.png - "extraMessages": { - "en": { - "alphanumericalCharsOnly": "Only alphanumerical characters", - "gender": "Gender", - // Here we overwrite the default english value for the message "doForgotPassword" - // that is "Forgot Password?" see: https://github.com/InseeFrLab/keycloakify/blob/f0ae5ea908e0aa42391af323b6d5e2fd371af851/src/lib/i18n/generated_messages/18.0.1/login/en.ts#L17 - "doForgotPassword": "I forgot my password", - }, - "fr": { - /* spell-checker: disable */ - "alphanumericalCharsOnly": "Caractère alphanumérique uniquement", - "gender": "Genre", - "doForgotPassword": "J'ai oublié mon mot de passe" - /* spell-checker: enable */ - }, - }, - }); -} +export const { useI18n } = createUseI18n({ + // NOTE: Here you can override the default i18n messages + // or define new ones that, for example, you would have + // defined in the Keycloak admin UI for UserProfile + // https://user-images.githubusercontent.com/6702424/182050652-522b6fe6-8ee5-49df-aca3-dba2d33f24a5.png + en: { + alphanumericalCharsOnly: "Only alphanumerical characters", + gender: "Gender", + // Here we overwrite the default english value for the message "doForgotPassword" + // that is "Forgot Password?" see: https://github.com/InseeFrLab/keycloakify/blob/f0ae5ea908e0aa42391af323b6d5e2fd371af851/src/lib/i18n/generated_messages/18.0.1/login/en.ts#L17 + doForgotPassword: "I forgot my password", + }, + 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>>; diff --git a/src/keycloak-theme/kcContext.ts b/src/keycloak-theme/kcContext.ts index 078efeb..44b8069 100644 --- a/src/keycloak-theme/kcContext.ts +++ b/src/keycloak-theme/kcContext.ts @@ -1,4 +1,4 @@ -import { getKcContext } from "keycloakify/lib/getKcContext"; +import { getKcContext } from "keycloakify"; export type KcContextExtension = // NOTE: A 'keycloakify' field must be added diff --git a/src/keycloak-theme/pages/Login.tsx b/src/keycloak-theme/pages/Login.tsx index 1c1559a..932f1ae 100644 --- a/src/keycloak-theme/pages/Login.tsx +++ b/src/keycloak-theme/pages/Login.tsx @@ -1,198 +1,204 @@ +// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/Login.tsx + import { useState, type FormEventHandler } from "react"; -// This is a copy paste from https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/Login.tsx -// You can replace all relative imports by cherry picking files from the keycloakify module. -// For example, the following import: -// import { clsx } from "./tools/clsx"; -// 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 { clsx } from "keycloakify/tools/clsx"; +import { useConstCallback } from "keycloakify/tools/useConstCallback"; +import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; -export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl"; }>, I18n>) { - const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props; +export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, + classes + }); - const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; + const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; - const { msg, msgStr } = i18n; + const { msg, msgStr } = i18n; - const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); + const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); - const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { - e.preventDefault(); + const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { + e.preventDefault(); - setIsLoginButtonDisabled(true); + setIsLoginButtonDisabled(true); - const formElement = e.target as HTMLFormElement; + const formElement = e.target as HTMLFormElement; - //NOTE: Even if we login with email Keycloak expect username and password in - //the POST request. - formElement.querySelector("input[name='email']")?.setAttribute("name", "username"); + //NOTE: Even if we login with email Keycloak expect username and password in + //the POST request. + formElement.querySelector("input[name='email']")?.setAttribute("name", "username"); - formElement.submit(); - }); + formElement.submit(); + }); - return ( - <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"; + 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"; - const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label; + const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label; - return ( - <> - <label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}> - {msg(label)} - </label> - <input - tabIndex={1} - id={autoCompleteHelper} - className={clsx(kcProps.kcInputClass)} - //NOTE: This is used by Google Chrome auto fill so we use it to tell - //the browser how to pre fill the form but before submit we put it back - //to username because it is what keycloak expects. - name={autoCompleteHelper} - defaultValue={login.username ?? ""} - type="text" - {...(usernameEditDisabled - ? { "disabled": true } - : { - "autoFocus": true, - "autoComplete": "off" - })} - /> - </> - ); - })()} - </div> - <div className={clsx(kcProps.kcFormGroupClass)}> - <label htmlFor="password" className={clsx(kcProps.kcLabelClass)}> - {msg("password")} - </label> - <input - tabIndex={2} - id="password" - className={clsx(kcProps.kcInputClass)} - name="password" - type="password" - autoComplete="off" - /> - </div> - <div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}> - <div id="kc-form-options"> - {realm.rememberMe && !usernameEditDisabled && ( - <div className="checkbox"> - <label> - <input - tabIndex={3} - id="rememberMe" - name="rememberMe" - type="checkbox" - {...(login.rememberMe - ? { - "checked": true - } - : {})} - /> - {msg("rememberMe")} - </label> - </div> - )} - </div> - <div className={clsx(kcProps.kcFormOptionsWrapperClass)}> - {realm.resetPasswordAllowed && ( - <span> - <a tabIndex={5} href={url.loginResetCredentialsUrl}> - {msg("doForgotPassword")} - </a> - </span> - )} - </div> - </div> - <div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}> - <input - type="hidden" - id="id-hidden-input" - name="credentialId" - {...(auth?.selectedCredential !== undefined - ? { - "value": auth.selectedCredential - } - : {})} - /> - <input - tabIndex={4} - className={clsx( - kcProps.kcButtonClass, - kcProps.kcButtonPrimaryClass, - kcProps.kcButtonBlockClass, - kcProps.kcButtonLargeClass - )} - name="login" - id="kc-login" - type="submit" - value={msgStr("doLogIn")} - disabled={isLoginButtonDisabled} - /> - </div> - </form> - )} - </div> - {realm.password && social.providers !== undefined && ( - <div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}> - <ul - className={clsx( - kcProps.kcFormSocialAccountListClass, - social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass - )} - > - {social.providers.map(p => ( - <li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}> - <a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}> - <span>{p.displayName}</span> - </a> - </li> - ))} - </ul> - </div> - )} - </div> - } - infoNode={ - realm.password && - realm.registrationAllowed && - !registrationDisabled && ( - <div id="kc-registration"> - <span> - {msg("noAccount")} - <a tabIndex={6} href={url.registrationUrl}> - {msg("doRegister")} - </a> - </span> - </div> - ) - } - /> - ); + return ( + <> + <label htmlFor={autoCompleteHelper} className={getClassName("kcLabelClass")}> + {msg(label)} + </label> + <input + tabIndex={1} + id={autoCompleteHelper} + className={getClassName("kcInputClass")} + //NOTE: This is used by Google Chrome auto fill so we use it to tell + //the browser how to pre fill the form but before submit we put it back + //to username because it is what keycloak expects. + name={autoCompleteHelper} + defaultValue={login.username ?? ""} + type="text" + {...(usernameEditDisabled + ? { "disabled": true } + : { + "autoFocus": true, + "autoComplete": "off" + })} + /> + </> + ); + })()} + </div> + <div className={getClassName("kcFormGroupClass")}> + <label htmlFor="password" className={getClassName("kcLabelClass")}> + {msg("password")} + </label> + <input + tabIndex={2} + id="password" + className={getClassName("kcInputClass")} + name="password" + type="password" + autoComplete="off" + /> + </div> + <div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}> + <div id="kc-form-options"> + {realm.rememberMe && !usernameEditDisabled && ( + <div className="checkbox"> + <label> + <input + tabIndex={3} + id="rememberMe" + name="rememberMe" + type="checkbox" + {...(login.rememberMe + ? { + "checked": true + } + : {})} + /> + {msg("rememberMe")} + </label> + </div> + )} + </div> + <div className={getClassName("kcFormOptionsWrapperClass")}> + {realm.resetPasswordAllowed && ( + <span> + <a tabIndex={5} href={url.loginResetCredentialsUrl}> + {msg("doForgotPassword")} + </a> + </span> + )} + </div> + </div> + <div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}> + <input + type="hidden" + id="id-hidden-input" + name="credentialId" + {...(auth?.selectedCredential !== undefined + ? { + "value": auth.selectedCredential + } + : {})} + /> + <input + tabIndex={4} + className={clsx( + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonBlockClass"), + getClassName("kcButtonLargeClass") + )} + name="login" + id="kc-login" + type="submit" + value={msgStr("doLogIn")} + disabled={isLoginButtonDisabled} + /> + </div> + </form> + )} + </div> + {realm.password && social.providers !== undefined && ( + <div + id="kc-social-providers" + className={clsx(getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass"))} + > + <ul + className={clsx( + getClassName("kcFormSocialAccountListClass"), + social.providers.length > 4 && getClassName("kcFormSocialAccountDoubleListClass") + )} + > + {social.providers.map(p => ( + <li key={p.providerId} className={getClassName("kcFormSocialAccountListLinkClass")}> + <a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}> + <span>{p.displayName}</span> + </a> + </li> + ))} + </ul> + </div> + )} + </div> + } + infoNode={ + realm.password && + realm.registrationAllowed && + !registrationDisabled && ( + <div id="kc-registration"> + <span> + {msg("noAccount")} + <a tabIndex={6} href={url.registrationUrl}> + {msg("doRegister")} + </a> + </span> + </div> + ) + } + /> + ); } diff --git a/src/keycloak-theme/pages/MyExtraPage1.tsx b/src/keycloak-theme/pages/MyExtraPage1.tsx index 82d54d1..ed82c60 100644 --- a/src/keycloak-theme/pages/MyExtraPage1.tsx +++ b/src/keycloak-theme/pages/MyExtraPage1.tsx @@ -1,21 +1,21 @@ -import type { PageProps } from "keycloakify"; +import type { PageProps } from "keycloakify/pages/PageProps"; import type { KcContext } from "../kcContext"; import type { I18n } from "../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 ( <Template - {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }} + {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={<>Header <i>text</i></>} formNode={ <form> {/*...*/} </form> } - infoNode={<span>footer</span> } + infoNode={<span>footer</span>} /> ); diff --git a/src/keycloak-theme/pages/MyExtraPage2.tsx b/src/keycloak-theme/pages/MyExtraPage2.tsx index a0d00f8..e086f99 100644 --- a/src/keycloak-theme/pages/MyExtraPage2.tsx +++ b/src/keycloak-theme/pages/MyExtraPage2.tsx @@ -1,17 +1,17 @@ -import type { PageProps } from "keycloakify"; +import type { PageProps } from "keycloakify/pages/PageProps"; import type { KcContext } from "../kcContext"; import type { I18n } from "../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 console.log(`TODO: Do something with: ${kcContext.someCustomValue}`); return ( <Template - {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }} + {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={<>Header <i>text</i></>} formNode={ <form> diff --git a/src/keycloak-theme/pages/Register.tsx b/src/keycloak-theme/pages/Register.tsx index dd19bba..666d8b8 100644 --- a/src/keycloak-theme/pages/Register.tsx +++ b/src/keycloak-theme/pages/Register.tsx @@ -1,74 +1,89 @@ -// This is a copy paste from https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/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. -// Note that it is no longer recommended to use register.ftl, it's best to use register-user-profile.ftl -// See: https://docs.keycloakify.dev/realtime-input-validation -import { clsx } from "keycloakify/lib/tools/clsx"; -import type { PageProps } from "keycloakify/lib/KcProps"; +// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/Register.tsx + +import { clsx } from "keycloakify/tools/clsx"; +import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; -export default function Register(props: PageProps<Extract<KcContext, { pageId: "register.ftl"; }>, I18n>) { - const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props; +export default function Register(props: PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n>) { + 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 { msg, msgStr } = i18n; - console.log(`NOTE: It is up to you do do something meaningful with ${kcContext.authorizedMailDomains}`); - return ( <Template - {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }} + {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("registerTitle")} formNode={ - <form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post"> - <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}> - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}> + <form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post"> + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("firstName", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="firstName" className={getClassName("kcLabelClass")}> {msg("firstName")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> <input type="text" id="firstName" - className={clsx(kcProps.kcInputClass)} + className={getClassName("kcInputClass")} name="firstName" defaultValue={register.formData.firstName ?? ""} /> </div> </div> - <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}> - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}> + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("lastName", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="lastName" className={getClassName("kcLabelClass")}> {msg("lastName")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> <input type="text" id="lastName" - className={clsx(kcProps.kcInputClass)} + className={getClassName("kcInputClass")} name="lastName" defaultValue={register.formData.lastName ?? ""} /> - </div> </div> - <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}> - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="email" className={clsx(kcProps.kcLabelClass)}> + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("email", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="email" className={getClassName("kcLabelClass")}> {msg("email")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> <input type="text" id="email" - className={clsx(kcProps.kcInputClass)} + className={getClassName("kcInputClass")} name="email" defaultValue={register.formData.email ?? ""} autoComplete="email" @@ -76,17 +91,22 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: " </div> </div> {!realm.registrationEmailAsUsername && ( - <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}> - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="username" className={clsx(kcProps.kcLabelClass)}> + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("username", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="username" className={getClassName("kcLabelClass")}> {msg("username")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> <input type="text" id="username" - className={clsx(kcProps.kcInputClass)} + className={getClassName("kcInputClass")} name="username" defaultValue={register.formData.username ?? ""} autoComplete="username" @@ -97,18 +117,21 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: " {passwordRequired && ( <> <div - className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))} + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass")) + )} > - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="password" className={clsx(kcProps.kcLabelClass)}> + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="password" className={getClassName("kcLabelClass")}> {msg("password")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> <input type="password" id="password" - className={clsx(kcProps.kcInputClass)} + className={getClassName("kcInputClass")} name="password" autoComplete="new-password" /> @@ -117,44 +140,44 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: " <div className={clsx( - kcProps.kcFormGroupClass, - messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass) + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass")) )} > - <div className={clsx(kcProps.kcLabelWrapperClass)}> - <label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}> + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="password-confirm" className={getClassName("kcLabelClass")}> {msg("passwordConfirm")} </label> </div> - <div className={clsx(kcProps.kcInputWrapperClass)}> - <input type="password" id="password-confirm" className={clsx(kcProps.kcInputClass)} name="password-confirm" /> + <div className={getClassName("kcInputWrapperClass")}> + <input type="password" id="password-confirm" className={getClassName("kcInputClass")} name="password-confirm" /> </div> </div> </> )} {recaptchaRequired && ( <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> </div> )} - <div className={clsx(kcProps.kcFormGroupClass)}> - <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}> - <div className={clsx(kcProps.kcFormOptionsWrapperClass)}> + <div className={getClassName("kcFormGroupClass")}> + <div id="kc-form-options" className={getClassName("kcFormOptionsClass")}> + <div className={getClassName("kcFormOptionsWrapperClass")}> <span> <a href={url.loginUrl}>{msg("backToLogin")}</a> </span> </div> </div> - <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}> + <div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}> <input className={clsx( - kcProps.kcButtonClass, - kcProps.kcButtonPrimaryClass, - kcProps.kcButtonBlockClass, - kcProps.kcButtonLargeClass + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonBlockClass"), + getClassName("kcButtonLargeClass") )} type="submit" value={msgStr("doRegister")} @@ -166,4 +189,3 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: " /> ); } - diff --git a/src/keycloak-theme/pages/RegisterUserProfile.tsx b/src/keycloak-theme/pages/RegisterUserProfile.tsx index e3b1f77..48d791f 100644 --- a/src/keycloak-theme/pages/RegisterUserProfile.tsx +++ b/src/keycloak-theme/pages/RegisterUserProfile.tsx @@ -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 { clsx } from "keycloakify/lib/tools/clsx"; -import { UserProfileFormFields } from "keycloakify/lib/pages/shared/UserProfileCommons"; -import type { PageProps } from "keycloakify/lib/KcProps"; +import { clsx } from "keycloakify/tools/clsx"; +import { UserProfileFormFields } from "./shared/UserProfileCommons"; +import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../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; @@ -17,36 +24,41 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContext, return ( <Template - {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }} + {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={messagesPerField.exists("global")} displayRequiredFields={true} headerNode={msg("registerTitle")} formNode={ - <form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post"> - <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} /> + <form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post"> + <UserProfileFormFields + kcContext={kcContext} + onIsFormSubmittableValueChange={setIsFomSubmittable} + i18n={i18n} + getClassName={getClassName} + /> {recaptchaRequired && ( <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> )} - <div className={clsx(kcProps.kcFormGroupClass)} style={{ "marginBottom": 30 }}> - <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}> - <div className={clsx(kcProps.kcFormOptionsWrapperClass)}> + <div className={getClassName("kcFormGroupClass")} style={{ "marginBottom": 30 }}> + <div id="kc-form-options" className={getClassName("kcFormOptionsClass")}> + <div className={getClassName("kcFormOptionsWrapperClass")}> <span> <a href={url.loginUrl}>{msg("backToLogin")}</a> </span> </div> </div> - <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}> + <div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}> <input className={clsx( - kcProps.kcButtonClass, - kcProps.kcButtonPrimaryClass, - kcProps.kcButtonBlockClass, - kcProps.kcButtonLargeClass + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonBlockClass"), + getClassName("kcButtonLargeClass") )} type="submit" value={msgStr("doRegister")} diff --git a/src/keycloak-theme/pages/Terms.tsx b/src/keycloak-theme/pages/Terms.tsx index 69d1b99..19d490f 100644 --- a/src/keycloak-theme/pages/Terms.tsx +++ b/src/keycloak-theme/pages/Terms.tsx @@ -5,45 +5,28 @@ * in the KcApp.tsx * Example: https://github.com/garronej/keycloakify-starter/blob/a20c21b2aae7c6dc6dbea294f3d321955ddf9355/src/KcApp/KcApp.tsx#L14-L30 */ -import {clsx} from "keycloakify/lib/tools/clsx"; -import {useRerenderOnStateChange} from "evt/hooks"; -import {Markdown} from "keycloakify/lib/tools/Markdown"; -import {evtTermMarkdown, useDownloadTerms} from "keycloakify/lib/pages/Terms"; -import tos_en_url from "../assets/tos_en.md"; -import tos_fr_url from "../assets/tos_fr.md"; -import type {PageProps} from "keycloakify/lib/KcProps"; -import type {KcContext} from "../kcContext"; -import type {I18n} from "../i18n"; +import { clsx } from "keycloakify/tools/clsx"; +import { useRerenderOnStateChange } from "evt/hooks"; +import { Markdown } from "keycloakify/tools/Markdown"; +import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; +import { evtTermMarkdown } from "keycloakify/lib/useDownloadTerms"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; -export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl"; }>, I18n>) { - const {kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps} = props; +export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; - const {msg, msgStr} = i18n; - - useDownloadTerms({ - 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 { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, + classes }); + const { msg, msgStr } = i18n; + useRerenderOnStateChange(evtTermMarkdown); - const {url} = kcContext; + const { url } = kcContext; if (evtTermMarkdown.state === undefined) { return null; @@ -51,21 +34,20 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter return ( <Template - {...{kcContext, i18n, doFetchDefaultThemeResources, ...kcProps}} + {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")} formNode={ <> - <div id="kc-terms-text">{evtTermMarkdown.state && - <Markdown>{evtTermMarkdown.state}</Markdown>}</div> + <div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div> <form className="form-actions" action={url.loginAction} method="POST"> <input className={clsx( - kcProps.kcButtonClass, - kcProps.kcButtonClass, - kcProps.kcButtonClass, - kcProps.kcButtonPrimaryClass, - kcProps.kcButtonLargeClass + getClassName("kcButtonClass"), + getClassName("kcButtonClass"), + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonLargeClass") )} name="accept" id="kc-accept" @@ -73,14 +55,14 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter value={msgStr("doAccept")} /> <input - className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)} + className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))} name="cancel" id="kc-decline" type="submit" value={msgStr("doDecline")} /> </form> - <div className="clearfix"/> + <div className="clearfix" /> </> } /> |