Working with v7 (without account theme)
This commit is contained in:
@ -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() {
|
||||
|
@ -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);
|
||||
})();
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
@ -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"
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
@ -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
|
@ -1,4 +1,4 @@
|
||||
import { getKcContext } from "keycloakify";
|
||||
import { getKcContext } from "keycloakify/login";
|
||||
|
||||
export type KcContextExtension =
|
||||
// NOTE: A 'keycloakify' field must be added
|
201
src/keycloak-theme/login/pages/Login.tsx
Normal file
201
src/keycloak-theme/login/pages/Login.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
||||
}
|
182
src/keycloak-theme/login/pages/Register.tsx
Normal file
182
src/keycloak-theme/login/pages/Register.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
70
src/keycloak-theme/login/pages/RegisterUserProfile.tsx
Normal file
70
src/keycloak-theme/login/pages/RegisterUserProfile.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
56
src/keycloak-theme/login/pages/Terms.tsx
Normal file
56
src/keycloak-theme/login/pages/Terms.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
@ -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({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user