#177: Provide a simple way to disable fetching of default resources

This commit is contained in:
garronej
2022-09-27 21:30:33 +02:00
parent 2c11ba6520
commit ff16e66275
19 changed files with 1345 additions and 1198 deletions

View File

@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Error = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Error; i18n: I18n } & KcProps) => { const Error = memo(
const { message, client } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.Error; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { message, client } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={msg("errorTitle")}
headerNode={msg("errorTitle")} formNode={
formNode={ <div id="kc-error-message">
<div id="kc-error-message"> <p className="instruction">{message.summary}</p>
<p className="instruction">{message.summary}</p> {client !== undefined && client.baseUrl !== undefined && (
{client !== undefined && client.baseUrl !== undefined && ( <p>
<p> <a id="backToApplication" href={client.baseUrl}>
<a id="backToApplication" href={client.baseUrl}> {msg("backToApplication")}
{msg("backToApplication")} </a>
</a> </p>
</p> )}
)} </div>
</div> }
} />
/> );
); }
}); );
export default Error; export default Error;

View File

@ -6,41 +6,52 @@ import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const IdpReviewUserProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n } & KcProps) => { const IdpReviewUserProfile = memo(
const { cx } = useCssAndCx(); ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { url } = kcContext; const { url } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("loginIdpReviewProfileTitle")}
headerNode={msg("loginIdpReviewProfileTitle")} formNode={
formNode={ <form id="kc-idp-review-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-idp-review-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} /> <div className={cx(props.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
</div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> </form>
<input }
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} />
type="submit" );
value={msgStr("doSubmit")} }
disabled={!isFomSubmittable} );
/>
</div>
</div>
</form>
}
/>
);
});
export default IdpReviewUserProfile; export default IdpReviewUserProfile;

View File

@ -5,47 +5,53 @@ import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Info = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Info; i18n: I18n } & KcProps) => { const Info = memo(
const { msgStr, msg } = i18n; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.Info; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msgStr, msg } = i18n;
assert(kcContext.message !== undefined); assert(kcContext.message !== undefined);
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext; const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>} formNode={
formNode={ <div id="kc-info-message">
<div id="kc-info-message"> <p className="instruction">
<p className="instruction"> {message.summary}
{message.summary}
{requiredActions !== undefined && ( {requiredActions !== undefined && (
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b> <b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)} )}
</p>
{!skipLink && pageRedirectUri !== undefined ? (
<p>
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
</p> </p>
) : actionUri !== undefined ? ( {!skipLink && pageRedirectUri !== undefined ? (
<p>
<a href={actionUri}>{msg("proceedWithAction")}</a>
</p>
) : (
client.baseUrl !== undefined && (
<p> <p>
<a href={client.baseUrl}>{msg("backToApplication")}</a> <a href={pageRedirectUri}>{msg("backToApplication")}</a>
</p> </p>
) ) : actionUri !== undefined ? (
)} <p>
</div> <a href={actionUri}>{msg("proceedWithAction")}</a>
} </p>
/> ) : (
); client.baseUrl !== undefined && (
}); <p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)}
</div>
}
/>
);
}
);
export default Info; export default Info;

View File

@ -23,67 +23,73 @@ const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
const UpdateUserProfile = lazy(() => import("./UpdateUserProfile")); const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile")); const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile"));
const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => { const KcApp = memo(
const i18n = (function useClosure() { ({
const i18n = useI18n({ kcContext,
kcContext, i18n: userProvidedI18n,
"extraMessages": {}, ...kcProps
"doSkip": userProvidedI18n !== undefined }: { kcContext: KcContextBase; i18n?: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
}); const i18n = (function useClosure() {
const i18n = useI18n({
kcContext,
"extraMessages": {},
"doSkip": userProvidedI18n !== undefined
});
return userProvidedI18n ?? i18n; return userProvidedI18n ?? i18n;
})(); })();
if (i18n === null) { if (i18n === null) {
return null; return null;
}
const props = { i18n, ...kcProps };
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...props }} />;
case "register.ftl":
return <Register {...{ kcContext, ...props }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...props }} />;
case "info.ftl":
return <Info {...{ kcContext, ...props }} />;
case "error.ftl":
return <Error {...{ kcContext, ...props }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...props }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...props }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...props }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...props }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
}
})()}
</Suspense>
);
} }
);
const props = { i18n, ...kcProps };
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...props }} />;
case "register.ftl":
return <Register {...{ kcContext, ...props }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...props }} />;
case "info.ftl":
return <Info {...{ kcContext, ...props }} />;
case "error.ftl":
return <Error {...{ kcContext, ...props }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...props }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...props }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...props }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...props }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
}
})()}
</Suspense>
);
});
export default KcApp; export default KcApp;

View File

@ -7,189 +7,197 @@ import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react"; import type { FormEventHandler } from "react";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Login; i18n: I18n } & KcProps) => { const Login = memo(
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.Login; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault(); e.preventDefault();
setIsLoginButtonDisabled(true); setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement; const formElement = e.target as HTMLFormElement;
//NOTE: Even if we login with email Keycloak expect username and password in //NOTE: Even if we login with email Keycloak expect username and password in
//the POST request. //the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username"); formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit(); formElement.submit();
}); });
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayInfo={social.displayInfo}
displayInfo={social.displayInfo} displayWide={realm.password && social.providers !== undefined}
displayWide={realm.password && social.providers !== undefined} headerNode={msg("doLogIn")}
headerNode={msg("doLogIn")} formNode={
formNode={ <div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}> <div
<div id="kc-form-wrapper"
id="kc-form-wrapper" className={cx(
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass]
> )}
{realm.password && ( >
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post"> {realm.password && (
<div className={cx(props.kcFormGroupClass)}> <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
{(() => { <div className={cx(props.kcFormGroupClass)}>
const label = !realm.loginWithEmailAllowed {(() => {
? "username" const label = !realm.loginWithEmailAllowed
: realm.registrationEmailAsUsername ? "username"
? "email" : realm.registrationEmailAsUsername
: "usernameOrEmail"; ? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label; const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
return ( return (
<> <>
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}> <label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
{msg(label)} {msg(label)}
</label> </label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={cx(props.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={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={cx(props.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox">
<label>
<input <input
tabIndex={3} tabIndex={1}
id="rememberMe" id={autoCompleteHelper}
name="rememberMe" className={cx(props.kcInputClass)}
type="checkbox" //NOTE: This is used by Google Chrome auto fill so we use it to tell
{...(login.rememberMe //the browser how to pre fill the form but before submit we put it back
? { //to username because it is what keycloak expects.
"checked": true name={autoCompleteHelper}
} defaultValue={login.username ?? ""}
: {})} type="text"
{...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off"
})}
/> />
{msg("rememberMe")} </>
</label> );
</div> })()}
)}
</div> </div>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={cx(props.kcFormGroupClass)}>
{realm.resetPasswordAllowed && ( <label htmlFor="password" className={cx(props.kcLabelClass)}>
<span> {msg("password")}
<a tabIndex={5} href={url.loginResetCredentialsUrl}> </label>
{msg("doForgotPassword")} <input
</a> tabIndex={2}
</span> id="password"
)} className={cx(props.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div> </div>
</div> <div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <div id="kc-form-options">
<input {realm.rememberMe && !usernameEditDisabled && (
type="hidden" <div className="checkbox">
id="id-hidden-input" <label>
name="credentialId" <input
{...(auth?.selectedCredential !== undefined tabIndex={3}
? { id="rememberMe"
"value": auth.selectedCredential name="rememberMe"
} type="checkbox"
: {})} {...(login.rememberMe
/> ? {
<input "checked": true
tabIndex={4} }
className={cx( : {})}
props.kcButtonClass, />
props.kcButtonPrimaryClass, {msg("rememberMe")}
props.kcButtonBlockClass, </label>
props.kcButtonLargeClass </div>
)} )}
name="login" </div>
id="kc-login" <div className={cx(props.kcFormOptionsWrapperClass)}>
type="submit" {realm.resetPasswordAllowed && (
value={msgStr("doLogIn")} <span>
disabled={isLoginButtonDisabled} <a tabIndex={5} href={url.loginResetCredentialsUrl}>
/> {msg("doForgotPassword")}
</div> </a>
</form> </span>
)}
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
type="hidden"
id="id-hidden-input"
name="credentialId"
{...(auth?.selectedCredential !== undefined
? {
"value": auth.selectedCredential
}
: {})}
/>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.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={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)} )}
</div> </div>
{realm.password && social.providers !== undefined && ( }
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}> infoNode={
<ul realm.password &&
className={cx( realm.registrationAllowed &&
props.kcFormSocialAccountListClass, !registrationDisabled && (
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass <div id="kc-registration">
)} <span>
> {msg("noAccount")}
{social.providers.map(p => ( <a tabIndex={6} href={url.registrationUrl}>
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}> {msg("doRegister")}
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}> </a>
<span>{p.displayName}</span> </span>
</a>
</li>
))}
</ul>
</div> </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>
)
}
/>
);
});
export default Login; export default Login;

View File

@ -5,182 +5,188 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n } & KcProps) => { const LoginConfigTotp = memo(
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = { const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
HmacSHA1: "SHA1", HmacSHA1: "SHA1",
HmacSHA256: "SHA256", HmacSHA256: "SHA256",
HmacSHA512: "SHA512" HmacSHA512: "SHA512"
}; };
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("loginTotpTitle")}
headerNode={msg("loginTotpTitle")} formNode={
formNode={ <>
<> <ol id="kc-totp-settings">
<ol id="kc-totp-settings"> <li>
<li> <p>{msg("loginTotpStep1")}</p>
<p>{msg("loginTotpStep1")}</p>
<ul id="kc-totp-supported-apps"> <ul id="kc-totp-supported-apps">
{totp.policy.supportedApplications.map(app => ( {totp.policy.supportedApplications.map(app => (
<li>{app}</li> <li>{app}</li>
))} ))}
</ul> </ul>
</li> </li>
{mode && mode == "manual" ? ( {mode && mode == "manual" ? (
<> <>
<li>
<p>{msg("loginTotpManualStep2")}</p>
<p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
</p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a>
</p>
</li>
<li>
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li> <li>
<p>{msg("loginTotpManualStep2")}</p> <p>{msg("loginTotpStep2")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
<br />
<p> <p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span> <a href={totp.manualUrl} id="mode-manual">
</p> {msg("loginTotpUnableToScan")}
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a> </a>
</p> </p>
</li> </li>
<li> )}
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li> <li>
<p>{msg("loginTotpStep2")}</p> <p>{msg("loginTotpStep3")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" /> <p>{msg("loginTotpStep3DeviceName")}</p>
<br />
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("loginTotpUnableToScan")}
</a>
</p>
</li> </li>
)} </ol>
<li>
<p>{msg("loginTotpStep3")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p>
</li>
</ol>
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post"> <form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="totp" className={cx(props.kcLabelClass)}> <label htmlFor="totp" className={cx(props.kcLabelClass)}>
{msg("authenticatorCode")} {msg("authenticatorCode")}
</label>{" "} </label>{" "}
<span className="required">*</span> <span className="required">*</span>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="totp" id="totp"
name="totp" name="totp"
autoComplete="off" autoComplete="off"
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")} aria-invalid={messagesPerField.existsError("totp")}
/> />
{messagesPerField.existsError("totp") && ( {messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite"> <span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("totp")} {messagesPerField.get("totp")}
</span> </span>
)} )}
</div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div> </div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}> <label htmlFor="userLabel" className={cx(props.kcLabelClass)}>
{msg("loginTotpDeviceName")} {msg("loginTotpDeviceName")}
</label>{" "} </label>{" "}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>} {totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div>
{isAppInitiatedAction ? ( {isAppInitiatedAction ? (
<> <>
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
<button
type="submit"
className={cx(
props.kcButtonClass,
props.kcButtonDefaultClass,
props.kcButtonLargeClass,
props.kcButtonLargeClass
)}
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input <input
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn" id="saveTOTPBtn"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />
<button )}
type="submit" </form>
className={cx( </>
props.kcButtonClass, }
props.kcButtonDefaultClass, />
props.kcButtonLargeClass, );
props.kcButtonLargeClass }
)} );
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
)}
</form>
</>
}
/>
);
});
export default LoginConfigTotp; export default LoginConfigTotp;

View File

@ -5,44 +5,50 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n } & KcProps) => { const LoginIdpLinkConfirm = memo(
const { url, idpAlias } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, idpAlias } = kcContext;
const { msg } = i18n; const { msg } = i18n;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("confirmLinkIdpTitle")}
headerNode={msg("confirmLinkIdpTitle")} formNode={
formNode={ <form id="kc-register-form" action={url.loginAction} method="post">
<form id="kc-register-form" action={url.loginAction} method="post"> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}> <button
<button type="submit"
type="submit" className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="submitAction"
name="submitAction" id="updateProfile"
id="updateProfile" value="updateProfile"
value="updateProfile" >
> {msg("confirmLinkIdpReviewProfile")}
{msg("confirmLinkIdpReviewProfile")} </button>
</button> <button
<button type="submit"
type="submit" className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="submitAction"
name="submitAction" id="linkAccount"
id="linkAccount" value="linkAccount"
value="linkAccount" >
> {msg("confirmLinkIdpContinue", idpAlias)}
{msg("confirmLinkIdpContinue", idpAlias)} </button>
</button> </div>
</div> </form>
</form> }
} />
/> );
); }
}); );
export default LoginIdpLinkConfirm; export default LoginIdpLinkConfirm;

View File

@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginIdpLinkEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n } & KcProps) => { const LoginIdpLinkEmail = memo(
const { url, realm, brokerContext, idpAlias } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("emailLinkIdpTitle", idpAlias)}
headerNode={msg("emailLinkIdpTitle", idpAlias)} formNode={
formNode={ <>
<> <p id="instruction1" className="instruction">
<p id="instruction1" className="instruction"> {msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)} </p>
</p> <p id="instruction2" className="instruction">
<p id="instruction2" className="instruction"> {msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")} </p>
</p> <p id="instruction3" className="instruction">
<p id="instruction3" className="instruction"> {msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")} </p>
</p> </>
</> }
} />
/> );
); }
}); );
export default LoginIdpLinkEmail; export default LoginIdpLinkEmail;

View File

@ -7,84 +7,95 @@ import { pathJoin } from "../../bin/tools/pathJoin";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginOtp; i18n: I18n } & KcProps) => { const LoginOtp = memo(
const { otpLogin, url } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginOtp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { otpLogin, url } = kcContext;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
useEffect(() => { useEffect(() => {
let isCleanedUp = false; let isCleanedUp = false;
headInsert({ headInsert({
"type": "javascript", "type": "javascript",
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js") "src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
}).then(() => { }).then(() => {
if (isCleanedUp) return; if (isCleanedUp) return;
evaluateInlineScript(); evaluateInlineScript();
}); });
return () => { return () => {
isCleanedUp = true; isCleanedUp = true;
}; };
}, []); }, []);
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("doLogIn")}
headerNode={msg("doLogIn")} formNode={
formNode={ <form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> {otpLogin.userOtpCredentials.length > 1 && (
{otpLogin.userOtpCredentials.length > 1 && ( <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcInputWrapperClass)}>
<div className={cx(props.kcInputWrapperClass)}> {otpLogin.userOtpCredentials.map(otpCredential => (
{otpLogin.userOtpCredentials.map(otpCredential => ( <div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}>
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}> <input type="hidden" value="${otpCredential.id}" />
<input type="hidden" value="${otpCredential.id}" /> <div className={cx(props.kcSelectOTPListItemClass)}>
<div className={cx(props.kcSelectOTPListItemClass)}> <span className={cx(props.kcAuthenticatorOtpCircleClass)} />
<span className={cx(props.kcAuthenticatorOtpCircleClass)} /> <h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2> </div>
</div> </div>
</div> ))}
))} </div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
</div> </div>
</div> </div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcFormGroupClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus /> <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
</div> <div className={cx(props.kcFormOptionsWrapperClass)} />
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <input
<div className={cx(props.kcFormOptionsWrapperClass)} /> className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div> </div>
</form>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> }
<input />
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} );
name="login" }
id="kc-login" );
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div>
</form>
}
/>
);
});
declare const $: any; declare const $: any;

View File

@ -4,35 +4,41 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginPageExpired = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n } & KcProps) => { const LoginPageExpired = memo(
const { url } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={msg("pageExpiredTitle")}
headerNode={msg("pageExpiredTitle")} formNode={
formNode={ <>
<> <p id="instruction1" className="instruction">
<p id="instruction1" className="instruction"> {msg("pageExpiredMsg1")}
{msg("pageExpiredMsg1")} <a id="loginRestartLink" href={url.loginRestartFlowUrl}>
<a id="loginRestartLink" href={url.loginRestartFlowUrl}> {msg("doClickHere")}
{msg("doClickHere")} </a>{" "}
</a>{" "} .<br />
.<br /> {msg("pageExpiredMsg2")}{" "}
{msg("pageExpiredMsg2")}{" "} <a id="loginContinueLink" href={url.loginAction}>
<a id="loginContinueLink" href={url.loginAction}> {msg("doClickHere")}
{msg("doClickHere")} </a>{" "}
</a>{" "} .
. </p>
</p> </>
</> }
} />
/> );
); }
}); );
export default LoginPageExpired; export default LoginPageExpired;

View File

@ -5,64 +5,75 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n } & KcProps) => { const LoginResetPassword = memo(
const { url, realm, auth } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, realm, auth } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={msg("emailForgotTitle")}
headerNode={msg("emailForgotTitle")} formNode={
formNode={ <form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <label htmlFor="username" className={cx(props.kcLabelClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> {!realm.loginWithEmailAllowed
{!realm.loginWithEmailAllowed ? msg("username")
? msg("username") : !realm.registrationEmailAsUsername
: !realm.registrationEmailAsUsername ? msg("usernameOrEmail")
? msg("usernameOrEmail") : msg("email")}
: msg("email")} </label>
</label> </div>
</div> <div className={cx(props.kcInputWrapperClass)}>
<div className={cx(props.kcInputWrapperClass)}> <input
<input type="text"
type="text" id="username"
id="username" name="username"
name="username" className={cx(props.kcInputClass)}
className={cx(props.kcInputClass)} autoFocus
autoFocus defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined} />
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={cx(
type="submit" props.kcButtonClass,
value={msgStr("doSubmit")} props.kcButtonPrimaryClass,
/> props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
/>
</div>
</div> </div>
</div> </form>
</form> }
} infoNode={msg("emailInstruction")}
infoNode={msg("emailInstruction")} />
/> );
); }
}); );
export default LoginResetPassword; export default LoginResetPassword;

View File

@ -5,115 +5,121 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n } & KcProps) => { const LoginUpdatePassword = memo(
const { cx } = useCssAndCx(); ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext; const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("updatePasswordTitle")}
headerNode={msg("updatePasswordTitle")} formNode={
formNode={ <form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <input
<input type="text"
type="text" id="username"
id="username" name="username"
name="username" value={username}
value={username} readOnly={true}
readOnly={true} autoComplete="username"
autoComplete="username" style={{ display: "none" }}
style={{ display: "none" }} />
/> <input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={cx(props.kcLabelClass)}> <label htmlFor="password-new" className={cx(props.kcLabelClass)}>
{msg("passwordNew")} {msg("passwordNew")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input <input
type="password" type="password"
id="password-new" id="password-new"
name="password-new" name="password-new"
autoFocus autoFocus
autoComplete="new-password" autoComplete="new-password"
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
/> />
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
{msg("passwordConfirm")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="password"
id="password-confirm"
name="password-confirm"
autoComplete="new-password"
className={cx(props.kcInputClass)}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
{isAppInitiatedAction && (
<div className="checkbox">
<label>
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
{msgStr("logoutOtherSessions")}
</label>
</div>
)}
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
{isAppInitiatedAction ? ( <div className={cx(props.kcLabelWrapperClass)}>
<> <label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
{msg("passwordConfirm")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="password"
id="password-confirm"
name="password-confirm"
autoComplete="new-password"
className={cx(props.kcInputClass)}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
{isAppInitiatedAction && (
<div className="checkbox">
<label>
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
{msgStr("logoutOtherSessions")}
</label>
</div>
)}
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
<button )}
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} </div>
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)}
</div> </div>
</div> </form>
</form> }
} />
/> );
); }
}); );
export default LoginUpdatePassword; export default LoginUpdatePassword;

View File

@ -5,118 +5,130 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n } & KcProps) => { const LoginUpdateProfile = memo(
const { cx } = useCssAndCx(); ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext; const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("loginProfileTitle")}
headerNode={msg("loginProfileTitle")} formNode={
formNode={ <form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> {user.editUsernameAllowed && (
{user.editUsernameAllowed && ( <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("username")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
defaultValue={user.username ?? ""}
className={cx(props.kcInputClass)}
/>
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("username")} {msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} />
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("firstName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="username" id="firstName"
name="username" name="firstName"
defaultValue={user.username ?? ""} defaultValue={user.firstName ?? ""}
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
/> />
</div> </div>
</div> </div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}> <label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("email")} {msg("lastName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} /> <input
</div> type="text"
</div> id="lastName"
name="lastName"
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> defaultValue={user.lastName ?? ""}
<div className={cx(props.kcLabelWrapperClass)}> className={cx(props.kcInputClass)}
<label htmlFor="firstName" className={cx(props.kcLabelClass)}> />
{msg("firstName")} </div>
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
name="firstName"
defaultValue={user.firstName ?? ""}
className={cx(props.kcInputClass)}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="lastName" name="lastName" defaultValue={user.lastName ?? ""} className={cx(props.kcInputClass)} />
</div>
</div>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} />
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div className={cx(props.kcFormGroupClass)}>
{isAppInitiatedAction ? ( <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<> <div className={cx(props.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
<button )}
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} </div>
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)}
</div> </div>
</div> </form>
</form> }
} />
/> );
); }
}); );
export default LoginUpdateProfile; export default LoginUpdateProfile;

View File

@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginVerifyEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n } & KcProps) => { const LoginVerifyEmail = memo(
const { msg } = i18n; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msg } = i18n;
const { url, user } = kcContext; const { url, user } = kcContext;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={msg("emailVerifyTitle")}
headerNode={msg("emailVerifyTitle")} formNode={
formNode={ <>
<> <p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p> <p className="instruction">
<p className="instruction"> {msg("emailVerifyInstruction2")}
{msg("emailVerifyInstruction2")} <br />
<br /> <a href={url.loginAction}>{msg("doClickHere")}</a>
<a href={url.loginAction}>{msg("doClickHere")}</a> &nbsp;
&nbsp; {msg("emailVerifyInstruction3")}
{msg("emailVerifyInstruction3")} </p>
</p> </>
</> }
} />
/> );
); }
}); );
export default LoginVerifyEmail; export default LoginVerifyEmail;

View File

@ -5,58 +5,64 @@ import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LogoutConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n } & KcProps) => { const LogoutConfirm = memo(
const { url, client, logoutConfirm } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, client, logoutConfirm } = kcContext;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} displayMessage={false}
displayMessage={false} headerNode={msg("logoutConfirmTitle")}
headerNode={msg("logoutConfirmTitle")} formNode={
formNode={ <>
<> <div id="kc-logout-confirm" className="content-area">
<div id="kc-logout-confirm" className="content-area"> <p className="instruction">{msg("logoutConfirmHeader")}</p>
<p className="instruction">{msg("logoutConfirmHeader")}</p> <form className="form-actions" action={url.logoutConfirmAction} method="POST">
<form className="form-actions" action={url.logoutConfirmAction} method="POST"> <input type="hidden" name="session_code" value={logoutConfirm.code} />
<input type="hidden" name="session_code" value={logoutConfirm.code} /> <div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}> <div id="kc-form-options">
<div id="kc-form-options"> <div className={cx(props.kcFormOptionsWrapperClass)}></div>
<div className={cx(props.kcFormOptionsWrapperClass)}></div> </div>
</div> <div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <input
<input tabIndex={4}
tabIndex={4} className={cx(
className={cx( props.kcButtonClass,
props.kcButtonClass, props.kcButtonPrimaryClass,
props.kcButtonPrimaryClass, props.kcButtonBlockClass,
props.kcButtonBlockClass, props.kcButtonLargeClass
props.kcButtonLargeClass )}
)} name="confirmLogout"
name="confirmLogout" id="kc-logout"
id="kc-logout" type="submit"
type="submit" value={msgStr("doLogout")}
value={msgStr("doLogout")} />
/> </div>
</div> </div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div> </div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div> </div>
</div> </>
</> }
} />
/> );
); }
}); );
export default LogoutConfirm; export default LogoutConfirm;

View File

@ -5,154 +5,165 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Register; i18n: I18n } & KcProps) => { const Register = memo(
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.Register; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("registerTitle")}
headerNode={msg("registerTitle")} formNode={
formNode={ <form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post"> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("firstName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
className={cx(props.kcInputClass)}
name="firstName"
defaultValue={register.formData.firstName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(props.kcInputClass)}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(props.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("username")} {msg("firstName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="username" id="firstName"
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
name="username" name="firstName"
defaultValue={register.formData.username ?? ""} defaultValue={register.formData.firstName ?? ""}
autoComplete="username"
/> />
</div> </div>
</div> </div>
)}
{passwordRequired && ( <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<> <div className={cx(props.kcLabelWrapperClass)}>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> <label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(props.kcInputClass)}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(props.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("password")} {msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input <input
type="password" type="text"
id="password" id="username"
className={cx(props.kcInputClass)} className={cx(props.kcInputClass)}
name="password" name="username"
autoComplete="new-password" defaultValue={register.formData.username ?? ""}
autoComplete="username"
/> />
</div> </div>
</div> </div>
)}
<div {passwordRequired && (
className={cx( <>
props.kcFormGroupClass, <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass) <div className={cx(props.kcLabelWrapperClass)}>
)} <label htmlFor="password" className={cx(props.kcLabelClass)}>
> {msg("password")}
<div className={cx(props.kcLabelWrapperClass)}> </label>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}> </div>
{msg("passwordConfirm")} <div className={cx(props.kcInputWrapperClass)}>
</label> <input
type="password"
id="password"
className={cx(props.kcInputClass)}
name="password"
autoComplete="new-password"
/>
</div>
</div> </div>
<div
className={cx(
props.kcFormGroupClass,
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass)
)}
>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
{msg("passwordConfirm")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
</div>
</div>
</>
)}
{recaptchaRequired && (
<div className="form-group">
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" /> <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div> </div>
</div> </div>
</> )}
)} <div className={cx(props.kcFormGroupClass)}>
{recaptchaRequired && ( <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className="form-group"> <div className={cx(props.kcFormOptionsWrapperClass)}>
<div className={cx(props.kcInputWrapperClass)}> <span>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div> <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div> </div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={cx(
type="submit" props.kcButtonClass,
value={msgStr("doRegister")} props.kcButtonPrimaryClass,
/> props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
value={msgStr("doRegister")}
/>
</div>
</div> </div>
</div> </form>
</form> }
} />
/> );
); }
}); );
export default Register; export default Register;

View File

@ -6,62 +6,73 @@ import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => { const RegisterUserProfile = memo(
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props_
}: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx, css } = useCssAndCx(); const { cx, css } = useCssAndCx();
const props = useMemo( const props = useMemo(
() => ({ () => ({
...props_, ...props_,
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 })) "kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 }))
}), }),
[cx, css] [cx, css]
); );
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
displayMessage={messagesPerField.exists("global")} displayMessage={messagesPerField.exists("global")}
displayRequiredFields={true} displayRequiredFields={true}
doFetchDefaultThemeResources={true} headerNode={msg("registerTitle")}
headerNode={msg("registerTitle")} formNode={
formNode={ <form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post"> <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} /> {recaptchaRequired && (
{recaptchaRequired && ( <div className="form-group">
<div className="form-group"> <div className={cx(props.kcInputWrapperClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} /> </div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
value={msgStr("doRegister")}
disabled={!isFomSubmittable}
/>
</div> </div>
</div> </div>
)} </form>
<div className={cx(props.kcFormGroupClass)}> }
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> />
<div className={cx(props.kcFormOptionsWrapperClass)}> );
<span> }
<a href={url.loginUrl}>{msg("backToLogin")}</a> );
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
type="submit"
value={msgStr("doRegister")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
});
export default RegisterUserProfile; export default RegisterUserProfile;

View File

@ -54,55 +54,61 @@ export function useDownloadTerms(params: {
}, []); }, []);
} }
const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Terms; i18n: I18n } & KcProps) => { const Terms = memo(
const { msg, msgStr } = i18n; ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.Terms; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msg, msgStr } = i18n;
useRerenderOnStateChange(evtTermMarkdown); useRerenderOnStateChange(evtTermMarkdown);
const { cx } = useCssAndCx(); const { cx } = useCssAndCx();
const { url } = kcContext; const { url } = kcContext;
if (evtTermMarkdown.state === undefined) { if (evtTermMarkdown.state === undefined) {
return null; return null;
}
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
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={cx(
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonLargeClass
)}
name="accept"
id="kc-accept"
type="submit"
value={msgStr("doAccept")}
/>
<input
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
name="cancel"
id="kc-decline"
type="submit"
value={msgStr("doDecline")}
/>
</form>
<div className="clearfix" />
</>
}
/>
);
} }
);
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
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={cx(
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonLargeClass
)}
name="accept"
id="kc-accept"
type="submit"
value={msgStr("doAccept")}
/>
<input
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
name="cancel"
id="kc-decline"
type="submit"
value={msgStr("doDecline")}
/>
</form>
<div className="clearfix" />
</>
}
/>
);
});
export default Terms; export default Terms;

View File

@ -6,66 +6,72 @@ import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const UpdateUserProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n } & KcProps) => { const UpdateUserProfile = memo(
const { cx } = useCssAndCx(); ({
kcContext,
i18n,
doFetchDefaultThemeResources = true,
...props
}: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { url, isAppInitiatedAction } = kcContext; const { url, isAppInitiatedAction } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
doFetchDefaultThemeResources={true} headerNode={msg("loginProfileTitle")}
headerNode={msg("loginProfileTitle")} formNode={
formNode={ <form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}></div> <div className={cx(props.kcFormOptionsWrapperClass)}></div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
{isAppInitiatedAction ? ( {isAppInitiatedAction ? (
<> <>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
type="submit"
value={msgStr("doSubmit")}
/>
<button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit" type="submit"
value={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/> />
<button )}
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} </div>
type="submit"
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
)}
</div> </div>
</div> </form>
</form> }
} />
/> );
); }
}); );
export default UpdateUserProfile; export default UpdateUserProfile;