diff options
| author | 2023-03-21 02:55:32 +0100 | |
|---|---|---|
| committer | 2023-03-21 02:55:32 +0100 | |
| commit | df766401002b6350bb06b0b6769d22b8b7240e6f (patch) | |
| tree | 960cebcb599ca5ca6d93794e5d6606ba66c7374d /src | |
| parent | update setup (diff) | |
Working with v7 (without account theme)
Diffstat (limited to 'src')
| -rw-r--r-- | src/App/App.tsx | 3 | ||||
| -rw-r--r-- | src/App/oidc.tsx | 33 | ||||
| -rw-r--r-- | src/index.tsx | 17 | ||||
| -rw-r--r-- | src/keycloak-theme/login/KcApp.css (renamed from src/keycloak-theme/KcApp.css) | 0 | ||||
| -rw-r--r-- | src/keycloak-theme/login/KcApp.tsx (renamed from src/keycloak-theme/KcApp.tsx) | 22 | ||||
| -rw-r--r-- | src/keycloak-theme/login/Template.tsx (renamed from src/keycloak-theme/Template.tsx) | 11 | ||||
| -rw-r--r-- | src/keycloak-theme/login/assets/background.svg (renamed from src/keycloak-theme/assets/background.svg) | 0 | ||||
| -rw-r--r-- | src/keycloak-theme/login/assets/tos_en.md (renamed from src/keycloak-theme/assets/tos_en.md) | 0 | ||||
| -rw-r--r-- | src/keycloak-theme/login/assets/tos_fr.md (renamed from src/keycloak-theme/assets/tos_fr.md) | 0 | ||||
| -rw-r--r-- | src/keycloak-theme/login/i18n.ts (renamed from src/keycloak-theme/i18n.ts) | 2 | ||||
| -rw-r--r-- | src/keycloak-theme/login/kcContext.ts (renamed from src/keycloak-theme/kcContext.ts) | 2 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Login.tsx | 201 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/MyExtraPage1.tsx (renamed from src/keycloak-theme/pages/MyExtraPage1.tsx) | 13 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/MyExtraPage2.tsx (renamed from src/keycloak-theme/pages/MyExtraPage2.tsx) | 16 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Register.tsx | 182 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/RegisterUserProfile.tsx | 70 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Terms.tsx | 56 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx (renamed from src/keycloak-theme/pages/shared/UserProfileCommons.tsx) | 58 | ||||
| -rw-r--r-- | src/keycloak-theme/login/valuesTransferredOverUrl.ts (renamed from src/keycloak-theme/valuesTransferredOverUrl.ts) | 0 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Login.tsx | 204 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Register.tsx | 191 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/RegisterUserProfile.tsx | 73 | ||||
| -rw-r--r-- | src/keycloak-theme/pages/Terms.tsx | 70 |
23 files changed, 600 insertions, 624 deletions
diff --git a/src/App/App.tsx b/src/App/App.tsx index c0f9222..68f1393 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -2,7 +2,7 @@ import "./App.css"; import logo from "./logo.svg"; import myimg from "./myimg.png"; import { createOidcClientProvider, useOidcClient } from "./oidc"; -import { addFooToQueryParams, addBarToQueryParams } from "../keycloak-theme/valuesTransferredOverUrl"; +import { addFooToQueryParams, addBarToQueryParams } from "../keycloak-theme/login/valuesTransferredOverUrl"; import jwt_decode from "jwt-decode"; const { OidcClientProvider } = createOidcClientProvider({ @@ -29,7 +29,6 @@ export default function App() { <ContextualizedApp /> </OidcClientProvider> ); - } function ContextualizedApp() { diff --git a/src/App/oidc.tsx b/src/App/oidc.tsx index ce4a854..44eb9e1 100644 --- a/src/App/oidc.tsx +++ b/src/App/oidc.tsx @@ -23,7 +23,7 @@ export declare namespace OidcClient { export type LoggedIn = { isUserLoggedIn: true; - getAccessToken: ()=> string; + getAccessToken: () => string; logout: (params: { redirectTo: "home" | "current page" }) => Promise<never>; //If we have sent a API request to change user's email for example //and we want that jwt_decode(oidcClient.getAccessToken()).email be the new email @@ -36,8 +36,8 @@ type Params = { url: string; realm: string; clientId: string; - transformUrlBeforeRedirect: (url: string) => string; - getUiLocales: () => string; + transformUrlBeforeRedirect?: (url: string) => string; + getUiLocales?: () => string; log?: typeof console.log; }; @@ -65,14 +65,19 @@ async function createKeycloakOidcClient(params: Params): Promise<OidcClient> { checkLoginIframe: false, adapter: createKeycloakAdapter({ transformUrlBeforeRedirect: url => - [url].map(transformUrlBeforeRedirect).map( - url => - addParamToUrl({ - url, - "name": "ui_locales", - "value": getUiLocales() - }).newUrl - )[0], + [url] + .map(transformUrlBeforeRedirect ?? (url => url)) + .map( + getUiLocales === undefined ? + (url => url) : + url => + addParamToUrl({ + url, + "name": "ui_locales", + "value": getUiLocales() + }).newUrl + ) + [0], keycloakInstance, getRedirectMethod: () => redirectMethod }) @@ -103,11 +108,11 @@ async function createKeycloakOidcClient(params: Params): Promise<OidcClient> { }); } - let currentAccessToken= keycloakInstance.token!; + let currentAccessToken = keycloakInstance.token!; const oidcClient = id<OidcClient.LoggedIn>({ "isUserLoggedIn": true, - "getAccessToken": ()=> currentAccessToken, + "getAccessToken": () => currentAccessToken, "logout": async ({ redirectTo }) => { await keycloakInstance.logout({ "redirectUri": (() => { @@ -157,7 +162,7 @@ async function createKeycloakOidcClient(params: Params): Promise<OidcClient> { currentAccessToken = keycloakInstance.token!; callee(); - + }, msBeforeExpiration - minValiditySecond * 1000); })(); diff --git a/src/index.tsx b/src/index.tsx index 37d6a5e..ba55bfe 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,17 +1,22 @@ import { createRoot } from "react-dom/client"; import { StrictMode, lazy, Suspense } from "react"; -import { kcContext } from "./keycloak-theme/kcContext"; +import { kcContext as kcLoginThemeContext } from "./keycloak-theme/login/kcContext"; const App = lazy(() => import("./App")); -const KcApp = lazy(() => import("./keycloak-theme/KcApp")); +const KcLoginThemeApp = lazy(() => import("./keycloak-theme/login/KcApp")); createRoot(document.getElementById("root")!).render( <StrictMode> <Suspense> - {kcContext === undefined ? - <App /> : - <KcApp kcContext={kcContext} /> - } + {(()=>{ + + if( kcLoginThemeContext !== undefined ){ + return <KcLoginThemeApp kcContext={kcLoginThemeContext} />; + } + + return <App />; + + })()} </Suspense> </StrictMode> ); diff --git a/src/keycloak-theme/KcApp.css b/src/keycloak-theme/login/KcApp.css index aeaebbe..aeaebbe 100644 --- a/src/keycloak-theme/KcApp.css +++ b/src/keycloak-theme/login/KcApp.css diff --git a/src/keycloak-theme/KcApp.tsx b/src/keycloak-theme/login/KcApp.tsx index 81d83ca..f41f00d 100644 --- a/src/keycloak-theme/KcApp.tsx +++ b/src/keycloak-theme/login/KcApp.tsx @@ -1,11 +1,11 @@ import "./KcApp.css"; import { lazy, Suspense } from "react"; +import Fallback, { type PageProps } from "keycloakify/login"; import type { KcContext } from "./kcContext"; import { useI18n } from "./i18n"; -import Fallback, { type PageProps } from "keycloakify"; const Template = lazy(() => import("./Template")); -const DefaultTemplate = lazy(() => import("keycloakify/Template")); +const DefaultTemplate = lazy(() => import("keycloakify/login/Template")); // You can uncomment this to see the values passed by the main app before redirecting. //import { foo, bar } from "./valuesTransferredOverUrl"; @@ -18,7 +18,7 @@ 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/pages/Info")); +const Info = lazy(() => import("keycloakify/login/pages/Info")); // 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 @@ -49,15 +49,15 @@ export default function App(props: { kcContext: KcContext; }) { <Suspense> {(() => { switch (kcContext.pageId) { - 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 }} />; + 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, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />; - default: return <Fallback {...{ kcContext, i18n, "Template": DefaultTemplate, classes, "doUseDefaultCss": true }} />; + case "info.ftl": return <Info {...{ kcContext, i18n, classes }} Template={DefaultTemplate} doUseDefaultCss={true} />; + default: return <Fallback {...{ kcContext, i18n, classes }} Template={DefaultTemplate} doUseDefaultCss={true} />; } })()} </Suspense> diff --git a/src/keycloak-theme/Template.tsx b/src/keycloak-theme/login/Template.tsx index 57a1566..1fd077a 100644 --- a/src/keycloak-theme/Template.tsx +++ b/src/keycloak-theme/login/Template.tsx @@ -2,7 +2,7 @@ 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 { type TemplateProps, defaultTemplateClasses } from "keycloakify/login/TemplateProps"; import { useGetClassName } from "keycloakify/lib/useGetClassName"; import type { KcContext } from "./kcContext"; import type { I18n } from "./i18n"; @@ -16,12 +16,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { showAnotherWayIfPresent = true, headerNode, showUsernameNode = null, - formNode, infoNode = null, kcContext, i18n, doUseDefaultCss, - classes + classes, + children } = props; const { getClassName } = useGetClassName({ @@ -42,7 +42,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { "lib/zocial/zocial.css" ], "styles": ["css/login.css"], - "htmlClassName": getClassName("kcHtmlClass") + "htmlClassName": getClassName("kcHtmlClass"), + "bodyClassName": undefined }); if (!isReady) { @@ -153,7 +154,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) { /> </div> )} - {formNode} + {children} {auth !== undefined && auth.showTryAnotherWayLink && showAnotherWayIfPresent && ( <form id="kc-select-try-another-way-form" diff --git a/src/keycloak-theme/assets/background.svg b/src/keycloak-theme/login/assets/background.svg index 0e1cada..0e1cada 100644 --- a/src/keycloak-theme/assets/background.svg +++ b/src/keycloak-theme/login/assets/background.svg diff --git a/src/keycloak-theme/assets/tos_en.md b/src/keycloak-theme/login/assets/tos_en.md index 9436328..9436328 100644 --- a/src/keycloak-theme/assets/tos_en.md +++ b/src/keycloak-theme/login/assets/tos_en.md diff --git a/src/keycloak-theme/assets/tos_fr.md b/src/keycloak-theme/login/assets/tos_fr.md index 0621cd3..0621cd3 100644 --- a/src/keycloak-theme/assets/tos_fr.md +++ b/src/keycloak-theme/login/assets/tos_fr.md diff --git a/src/keycloak-theme/i18n.ts b/src/keycloak-theme/login/i18n.ts index bbeab0b..fadaca5 100644 --- a/src/keycloak-theme/i18n.ts +++ b/src/keycloak-theme/login/i18n.ts @@ -1,4 +1,4 @@ -import { createUseI18n } from "keycloakify"; +import { createUseI18n } from "keycloakify/login"; export const { useI18n } = createUseI18n({ // NOTE: Here you can override the default i18n messages diff --git a/src/keycloak-theme/kcContext.ts b/src/keycloak-theme/login/kcContext.ts index 44b8069..3e86c20 100644 --- a/src/keycloak-theme/kcContext.ts +++ b/src/keycloak-theme/login/kcContext.ts @@ -1,4 +1,4 @@ -import { getKcContext } from "keycloakify"; +import { getKcContext } from "keycloakify/login"; export type KcContextExtension = // NOTE: A 'keycloakify' field must be added diff --git a/src/keycloak-theme/login/pages/Login.tsx b/src/keycloak-theme/login/pages/Login.tsx new file mode 100644 index 0000000..f89c259 --- /dev/null +++ b/src/keycloak-theme/login/pages/Login.tsx @@ -0,0 +1,201 @@ +import { useState, type FormEventHandler } from "react"; +import { clsx } from "keycloakify/tools/clsx"; +import { useConstCallback } from "keycloakify/tools/useConstCallback"; +import { type PageProps, defaultClasses } from "keycloakify/login/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, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, + classes + }); + + const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; + + const { msg, msgStr } = i18n; + + const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); + + const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { + e.preventDefault(); + + setIsLoginButtonDisabled(true); + + 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"); + + formElement.submit(); + }); + + return ( + <Template + {...{ kcContext, i18n, doUseDefaultCss, classes }} + displayInfo={social.displayInfo} + displayWide={realm.password && social.providers !== undefined} + headerNode={msg("doLogIn")} + infoNode={ + realm.password && + realm.registrationAllowed && + !registrationDisabled && ( + <div id="kc-registration"> + <span> + {msg("noAccount")} + <a tabIndex={6} href={url.registrationUrl}> + {msg("doRegister")} + </a> + </span> + </div> + ) + } + > + <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; + + 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> + </Template> + ); +} diff --git a/src/keycloak-theme/pages/MyExtraPage1.tsx b/src/keycloak-theme/login/pages/MyExtraPage1.tsx index ed82c60..b7c73bd 100644 --- a/src/keycloak-theme/pages/MyExtraPage1.tsx +++ b/src/keycloak-theme/login/pages/MyExtraPage1.tsx @@ -1,4 +1,4 @@ -import type { PageProps } from "keycloakify/pages/PageProps"; +import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; @@ -10,13 +10,12 @@ export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageI <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={<>Header <i>text</i></>} - formNode={ - <form> - {/*...*/} - </form> - } infoNode={<span>footer</span>} - /> + > + <form> + {/*...*/} + </form> + </Template> ); } diff --git a/src/keycloak-theme/pages/MyExtraPage2.tsx b/src/keycloak-theme/login/pages/MyExtraPage2.tsx index e086f99..e0ce046 100644 --- a/src/keycloak-theme/pages/MyExtraPage2.tsx +++ b/src/keycloak-theme/login/pages/MyExtraPage2.tsx @@ -1,4 +1,4 @@ -import type { PageProps } from "keycloakify/pages/PageProps"; +import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; @@ -13,13 +13,13 @@ export default function MyExtraPage1(props: PageProps<Extract<KcContext, { pageI <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={<>Header <i>text</i></>} - formNode={ - <form> - {/*...*/} - </form> - } - infoNode={<span>footer</span> } - /> + infoNode={<span>footer</span>} + > + + <form> + {/*...*/} + </form> + </Template> ); } diff --git a/src/keycloak-theme/login/pages/Register.tsx b/src/keycloak-theme/login/pages/Register.tsx new file mode 100644 index 0000000..20fbed0 --- /dev/null +++ b/src/keycloak-theme/login/pages/Register.tsx @@ -0,0 +1,182 @@ +import { clsx } from "keycloakify/tools/clsx"; +import { type PageProps, defaultClasses } from "keycloakify/login/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, 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; + + return ( + <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("registerTitle")}> + <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={getClassName("kcInputWrapperClass")}> + <input + type="text" + id="firstName" + className={getClassName("kcInputClass")} + name="firstName" + defaultValue={register.formData.firstName ?? ""} + /> + </div> + </div> + + <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={getClassName("kcInputWrapperClass")}> + <input + type="text" + id="lastName" + className={getClassName("kcInputClass")} + name="lastName" + defaultValue={register.formData.lastName ?? ""} + /> + </div> + </div> + + <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={getClassName("kcInputWrapperClass")}> + <input + type="text" + id="email" + className={getClassName("kcInputClass")} + name="email" + defaultValue={register.formData.email ?? ""} + autoComplete="email" + /> + </div> + </div> + {!realm.registrationEmailAsUsername && ( + <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={getClassName("kcInputWrapperClass")}> + <input + type="text" + id="username" + className={getClassName("kcInputClass")} + name="username" + defaultValue={register.formData.username ?? ""} + autoComplete="username" + /> + </div> + </div> + )} + {passwordRequired && ( + <> + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="password" className={getClassName("kcLabelClass")}> + {msg("password")} + </label> + </div> + <div className={getClassName("kcInputWrapperClass")}> + <input + type="password" + id="password" + className={getClassName("kcInputClass")} + name="password" + autoComplete="new-password" + /> + </div> + </div> + + <div + className={clsx( + getClassName("kcFormGroupClass"), + messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass")) + )} + > + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor="password-confirm" className={getClassName("kcLabelClass")}> + {msg("passwordConfirm")} + </label> + </div> + <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={getClassName("kcInputWrapperClass")}> + <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div> + </div> + </div> + )} + <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={getClassName("kcFormButtonsClass")}> + <input + className={clsx( + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonBlockClass"), + getClassName("kcButtonLargeClass") + )} + type="submit" + value={msgStr("doRegister")} + /> + </div> + </div> + </form> + </Template> + ); +} diff --git a/src/keycloak-theme/login/pages/RegisterUserProfile.tsx b/src/keycloak-theme/login/pages/RegisterUserProfile.tsx new file mode 100644 index 0000000..1955845 --- /dev/null +++ b/src/keycloak-theme/login/pages/RegisterUserProfile.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { clsx } from "keycloakify/tools/clsx"; +import { UserProfileFormFields } from "./shared/UserProfileCommons"; +import { type PageProps, defaultClasses } from "keycloakify/login/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, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, + classes + }); + + const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext; + + const { msg, msgStr } = i18n; + + const [isFomSubmittable, setIsFomSubmittable] = useState(false); + + return ( + <Template + {...{ kcContext, i18n, doUseDefaultCss, classes }} + displayMessage={messagesPerField.exists("global")} + displayRequiredFields={true} + headerNode={msg("registerTitle")} + > + <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={getClassName("kcInputWrapperClass")}> + <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} /> + </div> + </div> + )} + <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={getClassName("kcFormButtonsClass")}> + <input + className={clsx( + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonBlockClass"), + getClassName("kcButtonLargeClass") + )} + type="submit" + value={msgStr("doRegister")} + disabled={!isFomSubmittable} + /> + </div> + </div> + </form> + </Template> + ); +} diff --git a/src/keycloak-theme/login/pages/Terms.tsx b/src/keycloak-theme/login/pages/Terms.tsx new file mode 100644 index 0000000..819db53 --- /dev/null +++ b/src/keycloak-theme/login/pages/Terms.tsx @@ -0,0 +1,56 @@ +import { clsx } from "keycloakify/tools/clsx"; +import { useRerenderOnStateChange } from "evt/hooks"; +import { Markdown } from "keycloakify/tools/Markdown"; +import { type PageProps, defaultClasses } from "keycloakify/login/pages/PageProps"; +import { useGetClassName } from "keycloakify/lib/useGetClassName"; +import { evtTermMarkdown } from "keycloakify/login/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, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, + classes + }); + + const { msg, msgStr } = i18n; + + useRerenderOnStateChange(evtTermMarkdown); + + const { url } = kcContext; + + if (evtTermMarkdown.state === undefined) { + return null; + } + + return ( + <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}> + <div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div> + <form className="form-actions" action={url.loginAction} method="POST"> + <input + className={clsx( + getClassName("kcButtonClass"), + getClassName("kcButtonClass"), + getClassName("kcButtonClass"), + getClassName("kcButtonPrimaryClass"), + getClassName("kcButtonLargeClass") + )} + name="accept" + id="kc-accept" + type="submit" + value={msgStr("doAccept")} + /> + <input + className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))} + name="cancel" + id="kc-decline" + type="submit" + value={msgStr("doDecline")} + /> + </form> + <div className="clearfix" /> + </Template> + ); +} diff --git a/src/keycloak-theme/pages/shared/UserProfileCommons.tsx b/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx index 08b9442..0a50bfd 100644 --- a/src/keycloak-theme/pages/shared/UserProfileCommons.tsx +++ b/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx @@ -1,28 +1,22 @@ -//NOTE: Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/pages/shared/UserProfileCommons.tsx - import { useEffect, Fragment } from "react"; -import type { KcProps } from "keycloakify/lib/KcProps"; -import { clsx } from "keycloakify/lib/tools/clsx"; -import type { I18nBase } from "keycloakify/lib/i18n"; -import type { Attribute } from "keycloakify/lib/getKcContext"; -import { useFormValidation } from "keycloakify/lib/pages/shared/UserProfileCommons"; +import type { ClassKey } from "keycloakify/login/pages/PageProps"; +import { clsx } from "keycloakify/tools/clsx"; +import { useFormValidation } from "keycloakify/login/lib/useFormValidation"; +import type { Attribute } from "keycloakify/login/kcContext/KcContext"; +import type { I18n } from "../../i18n"; export type UserProfileFormFieldsProps = { kcContext: Parameters<typeof useFormValidation>[0]["kcContext"]; - i18n: I18nBase; -} & KcProps & - Partial<Record<"BeforeField" | "AfterField", (props: { attribute: Attribute }) => JSX.Element | null>> & { - onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void; - }; + i18n: I18n; + getClassName: (classKey: ClassKey) => string; + onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void; + BeforeField?: (props: { attribute: Attribute }) => JSX.Element | null; + AfterField?: (props: { attribute: Attribute }) => JSX.Element | null; +}; + +export function UserProfileFormFields(props: UserProfileFormFieldsProps) { + const { kcContext, onIsFormSubmittableValueChange, i18n, getClassName, BeforeField, AfterField } = props; -export function UserProfileFormFields({ - kcContext, - onIsFormSubmittableValueChange, - i18n, - BeforeField, - AfterField, - ...props -}: UserProfileFormFieldsProps) { const { advancedMsg } = i18n; const { @@ -47,20 +41,23 @@ export function UserProfileFormFields({ const { value, displayableErrors } = fieldStateByAttributeName[attribute.name]; - const formGroupClassName = clsx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass); + const formGroupClassName = clsx( + getClassName("kcFormGroupClass"), + displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass") + ); return ( <Fragment key={i}> {group !== currentGroup && (currentGroup = group) !== "" && ( <div className={formGroupClassName}> - <div className={clsx(props.kcContentWrapperClass)}> - <label id={`header-${group}`} className={clsx(props.kcFormGroupHeader)}> + <div className={getClassName("kcContentWrapperClass")}> + <label id={`header-${group}`} className={getClassName("kcFormGroupHeader")}> {advancedMsg(groupDisplayHeader) || currentGroup} </label> </div> {groupDisplayDescription !== "" && ( - <div className={clsx(props.kcLabelWrapperClass)}> - <label id={`description-${group}`} className={`${clsx(props.kcLabelClass)}`}> + <div className={getClassName("kcLabelWrapperClass")}> + <label id={`description-${group}`} className={getClassName("kcLabelClass")}> {advancedMsg(groupDisplayDescription)} </label> </div> @@ -71,13 +68,13 @@ export function UserProfileFormFields({ {BeforeField && <BeforeField attribute={attribute} />} <div className={formGroupClassName}> - <div className={clsx(props.kcLabelWrapperClass)}> - <label htmlFor={attribute.name} className={clsx(props.kcLabelClass)}> + <div className={getClassName("kcLabelWrapperClass")}> + <label htmlFor={attribute.name} className={getClassName("kcLabelClass")}> {advancedMsg(attribute.displayName ?? "")} </label> {attribute.required && <>*</>} </div> - <div className={clsx(props.kcInputWrapperClass)}> + <div className={getClassName("kcInputWrapperClass")}> {(() => { const { options } = attribute.validators; @@ -137,7 +134,7 @@ export function UserProfileFormFields({ "name": attribute.name }) } - className={clsx(props.kcInputClass)} + className={getClassName("kcInputClass")} aria-invalid={displayableErrors.length !== 0} disabled={attribute.readOnly} autoComplete={attribute.autocomplete} @@ -153,7 +150,7 @@ export function UserProfileFormFields({ <style>{`#${divId} > span: { display: block; }`}</style> <span id={divId} - className={clsx(props.kcInputErrorMessageClass)} + className={getClassName("kcInputErrorMessageClass")} style={{ "position": displayableErrors.length === 1 ? "absolute" : undefined }} @@ -173,4 +170,3 @@ export function UserProfileFormFields({ </> ); } - diff --git a/src/keycloak-theme/valuesTransferredOverUrl.ts b/src/keycloak-theme/login/valuesTransferredOverUrl.ts index 18dd4f9..18dd4f9 100644 --- a/src/keycloak-theme/valuesTransferredOverUrl.ts +++ b/src/keycloak-theme/login/valuesTransferredOverUrl.ts diff --git a/src/keycloak-theme/pages/Login.tsx b/src/keycloak-theme/pages/Login.tsx deleted file mode 100644 index 932f1ae..0000000 --- a/src/keycloak-theme/pages/Login.tsx +++ /dev/null @@ -1,204 +0,0 @@ -// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/Login.tsx - -import { useState, type FormEventHandler } from "react"; -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, doUseDefaultCss, Template, classes } = props; - - const { getClassName } = useGetClassName({ - "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, - classes - }); - - const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; - - const { msg, msgStr } = i18n; - - const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); - - const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { - e.preventDefault(); - - setIsLoginButtonDisabled(true); - - 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"); - - formElement.submit(); - }); - - 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; - - 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/Register.tsx b/src/keycloak-theme/pages/Register.tsx deleted file mode 100644 index 666d8b8..0000000 --- a/src/keycloak-theme/pages/Register.tsx +++ /dev/null @@ -1,191 +0,0 @@ -// 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, 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; - - return ( - <Template - {...{ kcContext, i18n, doUseDefaultCss, classes }} - headerNode={msg("registerTitle")} - formNode={ - <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={getClassName("kcInputWrapperClass")}> - <input - type="text" - id="firstName" - className={getClassName("kcInputClass")} - name="firstName" - defaultValue={register.formData.firstName ?? ""} - /> - </div> - </div> - - <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={getClassName("kcInputWrapperClass")}> - <input - type="text" - id="lastName" - className={getClassName("kcInputClass")} - name="lastName" - defaultValue={register.formData.lastName ?? ""} - /> - </div> - </div> - - <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={getClassName("kcInputWrapperClass")}> - <input - type="text" - id="email" - className={getClassName("kcInputClass")} - name="email" - defaultValue={register.formData.email ?? ""} - autoComplete="email" - /> - </div> - </div> - {!realm.registrationEmailAsUsername && ( - <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={getClassName("kcInputWrapperClass")}> - <input - type="text" - id="username" - className={getClassName("kcInputClass")} - name="username" - defaultValue={register.formData.username ?? ""} - autoComplete="username" - /> - </div> - </div> - )} - {passwordRequired && ( - <> - <div - className={clsx( - getClassName("kcFormGroupClass"), - messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass")) - )} - > - <div className={getClassName("kcLabelWrapperClass")}> - <label htmlFor="password" className={getClassName("kcLabelClass")}> - {msg("password")} - </label> - </div> - <div className={getClassName("kcInputWrapperClass")}> - <input - type="password" - id="password" - className={getClassName("kcInputClass")} - name="password" - autoComplete="new-password" - /> - </div> - </div> - - <div - className={clsx( - getClassName("kcFormGroupClass"), - messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass")) - )} - > - <div className={getClassName("kcLabelWrapperClass")}> - <label htmlFor="password-confirm" className={getClassName("kcLabelClass")}> - {msg("passwordConfirm")} - </label> - </div> - <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={getClassName("kcInputWrapperClass")}> - <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div> - </div> - </div> - )} - <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={getClassName("kcFormButtonsClass")}> - <input - className={clsx( - getClassName("kcButtonClass"), - getClassName("kcButtonPrimaryClass"), - getClassName("kcButtonBlockClass"), - getClassName("kcButtonLargeClass") - )} - type="submit" - value={msgStr("doRegister")} - /> - </div> - </div> - </form> - } - /> - ); -} diff --git a/src/keycloak-theme/pages/RegisterUserProfile.tsx b/src/keycloak-theme/pages/RegisterUserProfile.tsx deleted file mode 100644 index 48d791f..0000000 --- a/src/keycloak-theme/pages/RegisterUserProfile.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/pages/RegisterUserProfile.tsx - -import { useState } from "react"; -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, doUseDefaultCss, Template, classes } = props; - - const { getClassName } = useGetClassName({ - "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, - classes - }); - - const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext; - - const { msg, msgStr } = i18n; - - const [isFomSubmittable, setIsFomSubmittable] = useState(false); - - return ( - <Template - {...{ kcContext, i18n, doUseDefaultCss, classes }} - displayMessage={messagesPerField.exists("global")} - displayRequiredFields={true} - headerNode={msg("registerTitle")} - formNode={ - <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={getClassName("kcInputWrapperClass")}> - <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} /> - </div> - </div> - )} - <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={getClassName("kcFormButtonsClass")}> - <input - className={clsx( - getClassName("kcButtonClass"), - getClassName("kcButtonPrimaryClass"), - getClassName("kcButtonBlockClass"), - getClassName("kcButtonLargeClass") - )} - type="submit" - value={msgStr("doRegister")} - disabled={!isFomSubmittable} - /> - </div> - </div> - </form> - } - /> - ); -} diff --git a/src/keycloak-theme/pages/Terms.tsx b/src/keycloak-theme/pages/Terms.tsx deleted file mode 100644 index 19d490f..0000000 --- a/src/keycloak-theme/pages/Terms.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * NOTE: You do not need to do all this to put your own Terms and conditions - * this is if you want component level customization. - * If the default works for you, you can just use the useDownloadTerms hook - * in the KcApp.tsx - * Example: https://github.com/garronej/keycloakify-starter/blob/a20c21b2aae7c6dc6dbea294f3d321955ddf9355/src/KcApp/KcApp.tsx#L14-L30 - */ -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, doUseDefaultCss, Template, classes } = props; - - const { getClassName } = useGetClassName({ - "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, - classes - }); - - const { msg, msgStr } = i18n; - - useRerenderOnStateChange(evtTermMarkdown); - - const { url } = kcContext; - - if (evtTermMarkdown.state === undefined) { - return null; - } - - return ( - <Template - {...{ kcContext, i18n, doUseDefaultCss, classes }} - displayMessage={false} - headerNode={msg("termsTitle")} - formNode={ - <> - <div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div> - <form className="form-actions" action={url.loginAction} method="POST"> - <input - className={clsx( - getClassName("kcButtonClass"), - getClassName("kcButtonClass"), - getClassName("kcButtonClass"), - getClassName("kcButtonPrimaryClass"), - getClassName("kcButtonLargeClass") - )} - name="accept" - id="kc-accept" - type="submit" - value={msgStr("doAccept")} - /> - <input - className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))} - name="cancel" - id="kc-decline" - type="submit" - value={msgStr("doDecline")} - /> - </form> - <div className="clearfix" /> - </> - } - /> - ); -} |