Make the project compile

This commit is contained in:
garronej 2023-03-18 06:14:05 +01:00
parent 5615d62032
commit babffd1fe6
45 changed files with 1323 additions and 1329 deletions

View File

@ -10,7 +10,7 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src && yarn grant-exec-perms && yarn copy-files dist/",
"build:test": "rimraf dist_test/ && tsc -p src/test && yarn copy-files dist_test/",
"build:test": "rimraf dist_test/ && tsc -p test && yarn copy-files dist_test/",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"copy-files": "copyfiles -u 1 src/**/*.ftl",
"pretest": "yarn build:test",

View File

@ -1,32 +1,33 @@
import React, { lazy, Suspense } from "react";
import { lazy, Suspense } from "react";
import { __unsafe_useI18n as useI18n } from "./i18n";
import DefaultTemplate from "./Template";
import type { KcContextBase } from "./kcContext/KcContextBase";
import type { PageProps } from "./KcProps";
import type { PageProps } from "keycloakify/pages/PageProps";
import type { I18nBase } from "./i18n";
import type { SetOptional } from "./tools/SetOptional";
const Login = lazy(() => import("./pages/Login"));
const Register = lazy(() => import("./pages/Register"));
const RegisterUserProfile = lazy(() => import("./pages/RegisterUserProfile"));
const Info = lazy(() => import("./pages/Info"));
const Error = lazy(() => import("./pages/Error"));
const LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
const LoginVerifyEmail = lazy(() => import("./pages/LoginVerifyEmail"));
const Terms = lazy(() => import("./pages/Terms"));
const LoginOtp = lazy(() => import("./pages/LoginOtp"));
const LoginPassword = lazy(() => import("./pages/LoginPassword"));
const LoginUsername = lazy(() => import("./pages/LoginUsername"));
const WebauthnAuthenticate = lazy(() => import("./pages/WebauthnAuthenticate"));
const LoginUpdatePassword = lazy(() => import("./pages/LoginUpdatePassword"));
const LoginUpdateProfile = lazy(() => import("./pages/LoginUpdateProfile"));
const LoginIdpLinkConfirm = lazy(() => import("./pages/LoginIdpLinkConfirm"));
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));
const LoginIdpLinkEmail = lazy(() => import("./pages/LoginIdpLinkEmail"));
const LoginConfigTotp = lazy(() => import("./pages/LoginConfigTotp"));
const LogoutConfirm = lazy(() => import("./pages/LogoutConfirm"));
const UpdateUserProfile = lazy(() => import("./pages/UpdateUserProfile"));
const IdpReviewUserProfile = lazy(() => import("./pages/IdpReviewUserProfile"));
const DefaultTemplate = lazy(() => import("keycloakify/Template"));
const Login = lazy(() => import("keycloakify/pages/Login"));
const Register = lazy(() => import("keycloakify/pages/Register"));
const RegisterUserProfile = lazy(() => import("keycloakify/pages/RegisterUserProfile"));
const Info = lazy(() => import("keycloakify/pages/Info"));
const Error = lazy(() => import("keycloakify/pages/Error"));
const LoginResetPassword = lazy(() => import("keycloakify/pages/LoginResetPassword"));
const LoginVerifyEmail = lazy(() => import("keycloakify/pages/LoginVerifyEmail"));
const Terms = lazy(() => import("keycloakify/pages/Terms"));
const LoginOtp = lazy(() => import("keycloakify/pages/LoginOtp"));
const LoginPassword = lazy(() => import("keycloakify/pages/LoginPassword"));
const LoginUsername = lazy(() => import("keycloakify/pages/LoginUsername"));
const WebauthnAuthenticate = lazy(() => import("keycloakify/pages/WebauthnAuthenticate"));
const LoginUpdatePassword = lazy(() => import("keycloakify/pages/LoginUpdatePassword"));
const LoginUpdateProfile = lazy(() => import("keycloakify/pages/LoginUpdateProfile"));
const LoginIdpLinkConfirm = lazy(() => import("keycloakify/pages/LoginIdpLinkConfirm"));
const LoginPageExpired = lazy(() => import("keycloakify/pages/LoginPageExpired"));
const LoginIdpLinkEmail = lazy(() => import("keycloakify/pages/LoginIdpLinkEmail"));
const LoginConfigTotp = lazy(() => import("keycloakify/pages/LoginConfigTotp"));
const LogoutConfirm = lazy(() => import("keycloakify/pages/LogoutConfirm"));
const UpdateUserProfile = lazy(() => import("keycloakify/pages/UpdateUserProfile"));
const IdpReviewUserProfile = lazy(() => import("keycloakify/pages/IdpReviewUserProfile"));
export default function KcApp(props_: SetOptional<PageProps<KcContextBase, I18nBase>, "Template">) {
const { kcContext, i18n: userProvidedI18n, Template = DefaultTemplate, ...kcProps } = props_;

View File

@ -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 { useGetClassName } from "keycloakify/lib/getClassName";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
type KcContext = import("./kcContext/KcContextBase").KcContextBase.Common.Login;
import type { I18nBase as I18n } from "./i18n";

View File

@ -5,6 +5,10 @@ import type { I18nBase } from "keycloakify/i18n";
export type TemplateProps<KcContext extends KcContextBase.Common.Login, I18n extends I18nBase> = {
kcContext: KcContext;
i18n: I18n;
doUseDefaultCss: boolean;
classes?: Partial<Record<TemplateClassKey, string>>;
formNode: ReactNode;
displayInfo?: boolean;
displayMessage?: boolean;
displayRequiredFields?: boolean;
@ -12,10 +16,7 @@ export type TemplateProps<KcContext extends KcContextBase.Common.Login, I18n ext
showAnotherWayIfPresent?: boolean;
headerNode: ReactNode;
showUsernameNode?: ReactNode;
formNode: ReactNode;
infoNode?: ReactNode;
doUseDefaultCss: boolean;
classes?: Partial<Record<TemplateClassKey, string>>;
};
export type TemplateClassKey =

View File

@ -2,7 +2,6 @@ import type { LoginThemePageId, AccountThemePageId } from "../bin/keycloakify/ge
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import type { MessageKeyBase } from "../i18n";
import type { KcTemplateClassKey } from "../templates/LoginThemeTemplate";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
@ -289,7 +288,7 @@ export declare namespace KcContextBase {
export type WebauthnAuthenticator = {
credentialId: string;
transports: {
iconClass: KcTemplateClassKey;
iconClass: string;
displayNameProperties: MessageKeyBase[];
};
label: string;

View File

@ -4,7 +4,7 @@ import { ftlValuesGlobalName } from "../bin/keycloakify/ftlValuesGlobalName";
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string }> = [KcContextExtended] extends [never]
? KcContextBase
: AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>;
: AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common.Login, KcContextBase>;
export function getKcContextFromWindow<KcContextExtended extends { pageId: string } = never>(): ExtendsKcContextBase<KcContextExtended> | undefined {
return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName];

View File

@ -101,7 +101,7 @@ const attributes: Attribute[] = [
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
export const kcContextCommonMock: KcContextBase.Common = {
export const kcContextCommonMock: KcContextBase.Common.Login = {
"url": {
"loginAction": "#",
"resourcesPath": pathJoin(PUBLIC_URL, mockTestingResourcesPath),
@ -268,7 +268,7 @@ export const kcContextMocks: KcContextBase[] = [
"registrationDisabled": false
}),
...(() => {
const registerCommon: KcContextBase.RegisterCommon = {
const registerCommon: KcContextBase.RegisterUserProfile.CommonWithLegacy = {
...kcContextCommonMock,
"url": {
...loginUrl,

View File

@ -4,15 +4,19 @@ import { fallbackLanguageTag } from "../i18n";
import { useConst } from "../tools/useConst";
import { useConstCallback } from "../tools/useConstCallback";
import { assert } from "tsafe/assert";
import { KcContextBase as KcContext } from "../kcContext";
import { Evt } from "evt";
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
export type KcContextLike = {
pageId: KcContextBase["pageId"];
pageId: KcContext["pageId"];
locale?: {
currentLanguageTag: string;
};
};
assert<KcContextBase extends KcContextLike ? true : false>();
assert<KcContext extends KcContextLike ? true : false>();
/** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: {

View File

@ -0,0 +1,483 @@
import "keycloakify/tools/Array.prototype.every";
import { useMemo, useReducer, Fragment } from "react";
import { id } from "tsafe/id";
import type { MessageKeyBase } from "keycloakify/i18n";
import type { Attribute, Validators } from "keycloakify/kcContext";
import { useConstCallback } from "keycloakify/tools/useConstCallback";
import { emailRegexp } from "keycloakify/tools/emailRegExp";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
/**
* NOTE: The attributesWithPassword returned is actually augmented with
* artificial password related attributes only if kcContext.passwordRequired === true
*/
export function useFormValidation(params: {
kcContext: {
messagesPerField: Pick<KcContext.Common.Login["messagesPerField"], "existsError" | "get">;
profile: {
attributes: Attribute[];
};
passwordRequired?: boolean;
realm: { registrationEmailAsUsername: boolean };
};
/** NOTE: Try to avoid passing a new ref every render for better performances. */
passwordValidators?: Validators;
i18n: I18n;
}) {
const {
kcContext,
passwordValidators = {
"length": {
"ignore.empty.value": true,
"min": "4"
}
},
i18n
} = params;
const attributesWithPassword = useMemo(
() =>
!kcContext.passwordRequired
? kcContext.profile.attributes
: (() => {
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
return kcContext.profile.attributes.reduce<Attribute[]>(
(prev, curr) => [
...prev,
...(curr.name !== name
? [curr]
: [
curr,
id<Attribute>({
"name": "password",
"displayName": id<`\${${MessageKeyBase}}`>("${password}"),
"required": true,
"readOnly": false,
"validators": passwordValidators,
"annotations": {},
"groupAnnotations": {},
"autocomplete": "new-password"
}),
id<Attribute>({
"name": "password-confirm",
"displayName": id<`\${${MessageKeyBase}}`>("${passwordConfirm}"),
"required": true,
"readOnly": false,
"validators": {
"_compareToOther": {
"name": "password",
"ignore.empty.value": true,
"shouldBe": "equal",
"error-message": id<`\${${MessageKeyBase}}`>("${invalidPasswordConfirmMessage}")
}
},
"annotations": {},
"groupAnnotations": {},
"autocomplete": "new-password"
})
])
],
[]
);
})(),
[kcContext, passwordValidators]
);
const { getErrors } = useGetErrors({
"kcContext": {
"messagesPerField": kcContext.messagesPerField,
"profile": {
"attributes": attributesWithPassword
}
},
i18n
});
const initialInternalState = useMemo(
() =>
Object.fromEntries(
attributesWithPassword
.map(attribute => ({
attribute,
"errors": getErrors({
"name": attribute.name,
"fieldValueByAttributeName": Object.fromEntries(
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
)
})
}))
.map(({ attribute, errors }) => [
attribute.name,
{
"value": attribute.value ?? "",
errors,
"doDisplayPotentialErrorMessages": errors.length !== 0
}
])
),
[attributesWithPassword]
);
type InternalState = typeof initialInternalState;
const [formValidationInternalState, formValidationDispatch] = useReducer(
(
state: InternalState,
params:
| {
action: "update value";
name: string;
newValue: string;
}
| {
action: "focus lost";
name: string;
}
): InternalState => ({
...state,
[params.name]: {
...state[params.name],
...(() => {
switch (params.action) {
case "focus lost":
return { "doDisplayPotentialErrorMessages": true };
case "update value":
return {
"value": params.newValue,
"errors": getErrors({
"name": params.name,
"fieldValueByAttributeName": {
...state,
[params.name]: { "value": params.newValue }
}
})
};
}
})()
}
}),
initialInternalState
);
const formValidationState = useMemo(
() => ({
"fieldStateByAttributeName": Object.fromEntries(
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
name,
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
])
),
"isFormSubmittable": Object.entries(formValidationInternalState).every(
([name, { value, errors }]) =>
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
)
}),
[formValidationInternalState, attributesWithPassword]
);
return {
formValidationState,
formValidationDispatch,
attributesWithPassword
};
}
/** Expect to be used in a component wrapped within a <I18nProvider> */
function useGetErrors(params: {
kcContext: {
messagesPerField: Pick<KcContext.Common.Login["messagesPerField"], "existsError" | "get">;
profile: {
attributes: { name: string; value?: string; validators: Validators }[];
};
};
i18n: I18n;
}) {
const { kcContext, i18n } = params;
const {
messagesPerField,
profile: { attributes }
} = kcContext;
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
const { name, fieldValueByAttributeName } = params;
const { value } = fieldValueByAttributeName[name];
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
block: {
if (defaultValue !== value) {
break block;
}
let doesErrorExist: boolean;
try {
doesErrorExist = messagesPerField.existsError(name);
} catch {
break block;
}
if (!doesErrorExist) {
break block;
}
const errorMessageStr = messagesPerField.get(name);
return [
{
"validatorName": undefined,
errorMessageStr,
"errorMessage": <span key={0}>{errorMessageStr}</span>
}
];
}
const errors: {
errorMessage: JSX.Element;
errorMessageStr: string;
validatorName: keyof Validators | undefined;
}[] = [];
scope: {
const validatorName = "length";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (max !== undefined && value.length > parseInt(max)) {
const msgArgs = ["error-invalid-length-too-long", max] as const;
errors.push({
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs),
validatorName
});
}
if (min !== undefined && value.length < parseInt(min)) {
const msgArgs = ["error-invalid-length-too-short", min] as const;
errors.push({
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs),
validatorName
});
}
}
scope: {
const validatorName = "_compareToOther";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, name: otherName, shouldBe, "error-message": errorMessageKey } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
const { value: otherValue } = fieldValueByAttributeName[otherName];
const isValid = (() => {
switch (shouldBe) {
case "different":
return otherValue !== value;
case "equal":
return otherValue === value;
}
})();
if (isValid) {
break scope;
}
const msgArg = [
errorMessageKey ??
id<MessageKeyBase>(
(() => {
switch (shouldBe) {
case "equal":
return "shouldBeEqual";
case "different":
return "shouldBeDifferent";
}
})()
),
otherName,
name,
shouldBe
] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArg)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArg)
});
}
scope: {
const validatorName = "pattern";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (new RegExp(pattern).test(value)) {
break scope;
}
const msgArgs = [errorMessageKey ?? id<MessageKeyBase>("shouldMatchPattern"), pattern] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs)
});
}
scope: {
if ([...errors].reverse()[0]?.validatorName === "pattern") {
break scope;
}
const validatorName = "email";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (emailRegexp.test(value)) {
break scope;
}
const msgArgs = [id<MessageKeyBase>("invalidEmailMessage")] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
scope: {
const validatorName = "integer";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
const intValue = parseInt(value);
if (isNaN(intValue)) {
const msgArgs = ["mustBeAnInteger"] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
if (max !== undefined && intValue > parseInt(max)) {
const msgArgs = ["error-number-out-of-range-too-big", max] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
if (min !== undefined && intValue < parseInt(min)) {
const msgArgs = ["error-number-out-of-range-too-small", min] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
}
scope: {
const validatorName = "options";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
if (value === "") {
break scope;
}
if (validator.options.indexOf(value) >= 0) {
break scope;
}
const msgArgs = [id<MessageKeyBase>("notAValidOption")] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs)
});
}
//TODO: Implement missing validators.
return errors;
});
return { getErrors };
}

View File

@ -1,4 +1,5 @@
import { clsx } from "keycloakify/tools/clsx";
import { useConstCallback } from "keycloakify/tools/useConstCallback";
export function useGetClassName<ClassKey extends string>(params: {
defaultClasses?: Record<ClassKey, string | undefined>;
@ -6,9 +7,9 @@ export function useGetClassName<ClassKey extends string>(params: {
}) {
const { defaultClasses, classes } = params;
const getClassName = (classKey: ClassKey): string => {
const getClassName = useConstCallback((classKey: ClassKey): string => {
return clsx(classKey, defaultClasses?.[classKey], classes?.[classKey]);
};
});
return { getClassName };
}

View File

@ -1,10 +1,9 @@
import React from "react";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps } from "keycloakify/pages/PageProps";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function Error(props: PageProps<Extract<KcContextBase, { pageId: "error.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { message, client } = kcContext;
@ -12,7 +11,7 @@ export default function Error(props: PageProps<Extract<KcContextBase, { pageId:
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("errorTitle")}
formNode={

View File

@ -1,12 +1,18 @@
import React, { useState } from "react";
import { clsx } from "../tools/clsx";
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function IdpReviewUserProfile(props: PageProps<Extract<KcContextBase, { pageId: "idp-review-user-profile.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function IdpReviewUserProfile(props: PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { msg, msgStr } = i18n;
@ -16,23 +22,27 @@ export default function IdpReviewUserProfile(props: PageProps<Extract<KcContextB
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("loginIdpReviewProfileTitle")}
formNode={
<form id="kc-idp-review-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
<form id="kc-idp-review-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields
kcContext={kcContext}
onIsFormSubmittableValueChange={setIsFomSubmittable}
i18n={i18n}
getClassName={getClassName}
/>
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
value={msgStr("doSubmit")}

View File

@ -1,11 +1,10 @@
import React from "react";
import { assert } from "../tools/assert";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps } from "keycloakify/pages/PageProps";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function Info(props: PageProps<Extract<KcContextBase, { pageId: "info.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = i18n;
@ -15,7 +14,7 @@ export default function Info(props: PageProps<Extract<KcContextBase, { pageId: "
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={

View File

@ -1,12 +1,18 @@
import React, { useState, type FormEventHandler } from "react";
import { clsx } from "../tools/clsx";
import { useState, type FormEventHandler } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { useConstCallback } from "../tools/useConstCallback";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function Login(props: PageProps<Extract<KcContextBase, { pageId: "login.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
@ -30,21 +36,22 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ 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 && kcProps.kcContentWrapperClass)}>
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && getClassName("kcContentWrapperClass"))}>
<div
id="kc-form-wrapper"
className={clsx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
realm.password &&
social.providers && [getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass")]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
{(() => {
const label = !realm.loginWithEmailAllowed
? "username"
@ -56,13 +63,13 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
return (
<>
<label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
<label htmlFor={autoCompleteHelper} className={getClassName("kcLabelClass")}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={clsx(kcProps.kcInputClass)}
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.
@ -80,20 +87,20 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
);
})()}
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcFormGroupClass")}>
<label htmlFor="password" className={getClassName("kcLabelClass")}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="password"
type="password"
autoComplete="off"
/>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}>
<div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox">
@ -114,7 +121,7 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
</div>
)}
</div>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
@ -124,7 +131,7 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}>
<input
type="hidden"
id="id-hidden-input"
@ -138,10 +145,10 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="login"
id="kc-login"
@ -154,15 +161,18 @@ export default function Login(props: PageProps<Extract<KcContextBase, { pageId:
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
<div
id="kc-social-providers"
className={clsx(getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass"))}
>
<ul
className={clsx(
kcProps.kcFormSocialAccountListClass,
social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
getClassName("kcFormSocialAccountListClass"),
social.providers.length > 4 && getClassName("kcFormSocialAccountDoubleListClass")
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
<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>

View File

@ -1,17 +1,22 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase, { pageId: "login-config-totp.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const { msg, msgStr } = i18n;
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
const algToKeyUriAlg: Record<KcContext.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
"HmacSHA1": "SHA1",
"HmacSHA256": "SHA256",
"HmacSHA512": "SHA512"
@ -19,7 +24,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase,
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("loginTotpTitle")}
formNode={
<>
@ -91,26 +96,26 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase,
</li>
</ol>
<form action={url.loginAction} className={clsx(kcProps.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="totp" className={clsx(kcProps.kcLabelClass)}>
<form action={url.loginAction} className={getClassName("kcFormClass")} id="kc-totp-settings-form" method="post">
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcInputWrapperClass")}>
<label htmlFor="totp" className={getClassName("kcLabelClass")}>
{msg("authenticatorCode")}
</label>{" "}
<span className="required">*</span>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={clsx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
<span id="input-error-otp-code" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
{messagesPerField.get("totp")}
</span>
)}
@ -119,24 +124,24 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase,
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcInputWrapperClass")}>
<label htmlFor="userLabel" className={getClassName("kcLabelClass")}>
{msg("loginTotpDeviceName")}
</label>{" "}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={clsx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
<span id="input-error-otp-label" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
@ -147,17 +152,21 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase,
<>
<input
type="submit"
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonLargeClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonLargeClass"),
getClassName("kcButtonLargeClass")
)}
id="cancelTOTPBtn"
name="cancel-aia"
@ -169,7 +178,11 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContextBase,
) : (
<input
type="submit"
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContextBase, { pageId: "login-idp-link-confirm.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url, idpAlias } = kcContext;
@ -13,18 +18,18 @@ export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContextBa
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("confirmLinkIdpTitle")}
formNode={
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="submitAction"
id="updateProfile"
@ -35,10 +40,10 @@ export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContextBa
<button
type="submit"
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="submitAction"
id="linkAccount"

View File

@ -1,10 +1,9 @@
import React from "react";
import type { KcContextBase } from "../kcContext";
import type { PageProps } from "../KcProps";
import type { I18nBase } from "../i18n";
import type { KcContextBase as KcContext } from "keycloakify/kcContext";
import { type PageProps } from "keycloakify/pages/PageProps";
import type { I18nBase as I18n } from "keycloakify/i18n";
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContextBase, { pageId: "login-idp-link-email.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url, realm, brokerContext, idpAlias } = kcContext;
@ -12,7 +11,7 @@ export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContextBase
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("emailLinkIdpTitle", idpAlias)}
formNode={
<>

View File

@ -1,13 +1,19 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { headInsert } from "../tools/headInsert";
import { pathJoin } from "../bin/tools/pathJoin";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginOtp(props: PageProps<Extract<KcContextBase, { pageId: "login-otp.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { otpLogin, url } = kcContext;
@ -32,49 +38,49 @@ export default function LoginOtp(props: PageProps<Extract<KcContextBase, { pageI
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("doLogIn")}
formNode={
<form id="kc-otp-login-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-otp-login-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && (
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcInputWrapperClass")}>
{otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={clsx(kcProps.kcSelectOTPListClass)}>
<div key={otpCredential.id} className={getClassName("kcSelectOTPListClass")}>
<input type="hidden" value="${otpCredential.id}" />
<div className={clsx(kcProps.kcSelectOTPListItemClass)}>
<span className={clsx(kcProps.kcAuthenticatorOtpCircleClass)} />
<h2 className={clsx(kcProps.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
<div className={getClassName("kcSelectOTPListItemClass")}>
<span className={getClassName("kcAuthenticatorOtpCircleClass")} />
<h2 className={getClassName("kcSelectOTPItemHeadingClass")}>{otpCredential.userLabel}</h2>
</div>
</div>
))}
</div>
</div>
)}
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="otp" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="otp" className={getClassName("kcLabelClass")}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={clsx(kcProps.kcInputClass)} autoFocus />
<div className={getClassName("kcInputWrapperClass")}>
<input id="otp" name="otp" autoComplete="off" type="text" className={getClassName("kcInputClass")} autoFocus />
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="login"
id="kc-login"

View File

@ -1,10 +1,9 @@
import React from "react";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps } from "keycloakify/pages/PageProps";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginPageExpired(props: PageProps<Extract<KcContextBase, { pageId: "login-page-expired.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url } = kcContext;
@ -12,7 +11,7 @@ export default function LoginPageExpired(props: PageProps<Extract<KcContextBase,
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("pageExpiredTitle")}
formNode={

View File

@ -1,13 +1,19 @@
import React, { useState } from "react";
import { clsx } from "../tools/clsx";
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { useConstCallback } from "../tools/useConstCallback";
import type { FormEventHandler } from "react";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginPassword(props: PageProps<Extract<KcContextBase, { "pageId": "login-password.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginPassword(props: PageProps<Extract<KcContext, { "pageId": "login-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { realm, url, login } = kcContext;
@ -27,21 +33,21 @@ export default function LoginPassword(props: PageProps<Extract<KcContextBase, {
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form">
<div id="kc-form-wrapper">
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
<hr />
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
<label htmlFor="password" className={getClassName("kcLabelClass")}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="password"
type="password"
autoFocus={true}
@ -49,9 +55,9 @@ export default function LoginPassword(props: PageProps<Extract<KcContextBase, {
defaultValue={login.password ?? ""}
/>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}>
<div id="kc-form-options" />
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
@ -61,14 +67,14 @@ export default function LoginPassword(props: PageProps<Extract<KcContextBase, {
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="login"
id="kc-login"

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginResetPassword(props: PageProps<Extract<KcContextBase, { pageId: "login-reset-password.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url, realm, auth } = kcContext;
@ -13,14 +18,14 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContextBas
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("emailForgotTitle")}
formNode={
<form id="kc-reset-password-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
<form id="kc-reset-password-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="username" className={getClassName("kcLabelClass")}>
{!realm.loginWithEmailAllowed
? msg("username")
: !realm.registrationEmailAsUsername
@ -28,33 +33,33 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContextBas
: msg("email")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="username"
name="username"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
autoFocus
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
value={msgStr("doSubmit")}

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginUpdatePassword(props: PageProps<Extract<KcContextBase, { pageId: "login-update-password.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { msg, msgStr } = i18n;
@ -13,10 +18,10 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContextBa
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("updatePasswordTitle")}
formNode={
<form id="kc-passwd-update-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-passwd-update-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<input
type="text"
id="username"
@ -28,46 +33,54 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContextBa
/>
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="password-new" className={getClassName("kcLabelClass")}>
{msg("passwordNew")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="password"
id="password-new"
name="password-new"
autoFocus
autoComplete="new-password"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
/>
</div>
</div>
<div
className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass))}
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="password-confirm" className={getClassName("kcLabelClass")}>
{msg("passwordConfirm")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="password"
id="password-confirm"
name="password-confirm"
autoComplete="new-password"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
{isAppInitiatedAction && (
<div className="checkbox">
<label>
@ -79,16 +92,24 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContextBa
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
name="cancel-aia"
value="true"
@ -99,10 +120,10 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContextBa
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
defaultValue={msgStr("doSubmit")}

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginUpdateProfile(props: PageProps<Extract<KcContextBase, { pageId: "login-update-profile.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginUpdateProfile(props: PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { msg, msgStr } = i18n;
@ -13,89 +18,117 @@ export default function LoginUpdateProfile(props: PageProps<Extract<KcContextBas
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<form id="kc-update-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
{user.editUsernameAllowed && (
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("username", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="username" className={getClassName("kcLabelClass")}>
{msg("username")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="username"
name="username"
defaultValue={user.username ?? ""}
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
/>
</div>
</div>
)}
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="email" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("email", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="email" className={getClassName("kcLabelClass")}>
{msg("email")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={clsx(kcProps.kcInputClass)} />
<div className={getClassName("kcInputWrapperClass")}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={getClassName("kcInputClass")} />
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("firstName", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="firstName" className={getClassName("kcLabelClass")}>
{msg("firstName")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="firstName"
name="firstName"
defaultValue={user.firstName ?? ""}
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("lastName", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="lastName" className={getClassName("kcLabelClass")}>
{msg("lastName")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="lastName"
name="lastName"
defaultValue={user.lastName ?? ""}
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
name="cancel-aia"
value="true"
@ -106,10 +139,10 @@ export default function LoginUpdateProfile(props: PageProps<Extract<KcContextBas
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
defaultValue={msgStr("doSubmit")}

View File

@ -1,13 +1,19 @@
import React, { useState } from "react";
import { clsx } from "../tools/clsx";
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { useConstCallback } from "../tools/useConstCallback";
import type { FormEventHandler } from "react";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginUsername(props: PageProps<Extract<KcContextBase, { pageId: "login-username.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
@ -31,21 +37,22 @@ export default function LoginUsername(props: PageProps<Extract<KcContextBase, {
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ 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 && kcProps.kcContentWrapperClass)}>
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && getClassName("kcContentWrapperClass"))}>
<div
id="kc-form-wrapper"
className={clsx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
realm.password &&
social.providers && [getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass")]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
{!usernameHidden &&
(() => {
const label = !realm.loginWithEmailAllowed
@ -58,13 +65,13 @@ export default function LoginUsername(props: PageProps<Extract<KcContextBase, {
return (
<>
<label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
<label htmlFor={autoCompleteHelper} className={getClassName("kcLabelClass")}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={clsx(kcProps.kcInputClass)}
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.
@ -78,7 +85,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContextBase, {
);
})()}
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div className={clsx(getClassName("kcFormGroupClass"), getClassName("kcFormSettingClass"))}>
<div id="kc-form-options">
{realm.rememberMe && !usernameHidden && (
<div className="checkbox">
@ -100,14 +107,14 @@ export default function LoginUsername(props: PageProps<Extract<KcContextBase, {
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="login"
id="kc-login"
@ -120,15 +127,18 @@ export default function LoginUsername(props: PageProps<Extract<KcContextBase, {
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
<div
id="kc-social-providers"
className={clsx(getClassName("kcFormSocialAccountContentClass"), getClassName("kcFormSocialAccountClass"))}
>
<ul
className={clsx(
kcProps.kcFormSocialAccountListClass,
social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
getClassName("kcFormSocialAccountListClass"),
social.providers.length > 4 && getClassName("kcFormSocialAccountDoubleListClass")
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
<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>

View File

@ -1,10 +1,9 @@
import React from "react";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps } from "keycloakify/pages/PageProps";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LoginVerifyEmail(props: PageProps<Extract<KcContextBase, { pageId: "login-verify-email.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msg } = i18n;
@ -12,7 +11,7 @@ export default function LoginVerifyEmail(props: PageProps<Extract<KcContextBase,
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("emailVerifyTitle")}
formNode={

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function LogoutConfirm(props: PageProps<Extract<KcContextBase, { pageId: "logout-confirm.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url, client, logoutConfirm } = kcContext;
@ -13,7 +18,7 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContextBase, {
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("logoutConfirmTitle")}
formNode={
@ -22,18 +27,18 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContextBase, {
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
<input type="hidden" name="session_code" value={logoutConfirm.code} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options">
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
<div className={getClassName("kcFormOptionsWrapperClass")}></div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormGroupClass")}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
name="confirmLogout"
id="kc-logout"

View File

@ -1,159 +1,144 @@
import type { LazyExoticComponent } from "react";
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert";
import type { I18nBase } from "../i18n";
import type { ThemeType } from "../bin/keycloakify/generateFtl";
import type { Equals } from "tsafe";
import type { TemplateProps } from "../templates/TemplateProps";
export const defaultKcProps = {
"login": {
...defaultKcTemplateProps.login,
"kcLogoLink": "http://www.keycloak.org",
"kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid",
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"],
"kcFeedbackAreaClass": ["col-md-12"],
"kcLocaleClass": ["col-xs-12", "col-sm-1"],
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"],
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"],
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
"kcWebAuthnDefaultIcon": ["pficon", "pficon-key"],
"kcFormClass": ["form-horizontal"],
"kcFormGroupErrorClass": ["has-error"],
"kcLabelClass": ["control-label"],
"kcInputClass": ["form-control"],
"kcInputErrorMessageClass": ["pf-c-form__helper-text", "pf-m-error", "required", "kc-feedback-text"],
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormSettingClass": ["login-pf-settings"],
"kcTextareaClass": ["form-control"],
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"],
// user-profile grouping
"kcFormGroupHeader": ["pf-c-form__group"],
// css classes for form buttons main class used for all buttons
"kcButtonClass": ["btn"],
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
"kcButtonPrimaryClass": ["btn-primary"],
"kcButtonDefaultClass": ["btn-default"],
// classes defining size of the button
"kcButtonLargeClass": ["btn-lg"],
"kcButtonBlockClass": ["btn-block"],
// css classes for input
"kcInputLargeClass": ["input-lg"],
// css classes for form accessability
"kcSrOnlyClass": ["sr-only"],
// css classes for select-authenticator form
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
"kcSelectAuthListItemFillClass": ["pf-l-split__item", "pf-m-fill"],
"kcSelectAuthListItemIconPropertyClass": ["fa-2x", "select-auth-box-icon-properties"],
"kcSelectAuthListItemIconClass": ["pf-l-split__item", "select-auth-box-icon"],
"kcSelectAuthListItemTitle": ["select-auth-box-paragraph"],
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
"kcSelectAuthListItemDescriptionClass": ["list-view-pf-description"],
"kcSelectAuthListItemHeadingClass": ["list-group-item-heading"],
"kcSelectAuthListItemHelpTextClass": ["list-group-item-text"],
// css classes for the authenticators
"kcAuthenticatorDefaultClass": ["fa", "list-view-pf-icon-lg"],
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
//css classes for the OTP Login Form
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"],
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
"kcFormOptionsWrapperClass": []
},
"account": {
...defaultKcTemplateProps.account
}
};
assert<Equals<keyof typeof defaultKcProps, ThemeType>>();
assert<typeof defaultKcProps.login extends KcProps.Login ? true : false>();
assert<typeof defaultKcProps.account extends KcProps.Account ? true : false>();
import type { I18nBase } from "keycloakify/i18n";
import { type TemplateProps, type TemplateClassKey, defaultTemplateClasses } from "keycloakify/TemplateProps";
export type PageProps<KcContext, I18n extends I18nBase> = {
Template: LazyExoticComponent<(props: TemplateProps<any, any>) => JSX.Element | null>;
kcContext: KcContext;
i18n: I18n;
templateProps: Pick<TemplateProps<any, any>, "doUseDefaultCss" | "classes">;
classes?: Partial<
Record<
| "kcLogoLink"
| "kcLogoClass"
| "kcContainerClass"
| "kcContentClass"
| "kcFeedbackAreaClass"
| "kcLocaleClass"
| "kcAlertIconClasserror"
| "kcFormAreaClass"
| "kcFormSocialAccountListClass"
| "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon"
| "kcWebAuthnDefaultIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemFillClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcSelectAuthListItemIconPropertyClass"
| "kcSelectAuthListItemIconClass"
| "kcSelectAuthListItemTitle"
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass"
| "kcSelectOTPListClass"
| "kcSelectOTPListItemClass"
| "kcAuthenticatorOtpCircleClass"
| "kcSelectOTPItemHeadingClass"
| "kcFormOptionsWrapperClass",
string
>
>;
doUseDefaultCss: boolean;
classes?: Partial<Record<TemplateClassKey | ClassKey, string>>;
};
export type ClassKey =
| TemplateClassKey
| "kcLogoClass"
| "kcContainerClass"
| "kcContentClass"
| "kcFeedbackAreaClass"
| "kcLocaleClass"
| "kcAlertIconClasserror"
| "kcFormAreaClass"
| "kcFormSocialAccountListClass"
| "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon"
| "kcWebAuthnDefaultIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemFillClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcSelectAuthListItemIconPropertyClass"
| "kcSelectAuthListItemIconClass"
| "kcSelectAuthListItemTitle"
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass"
| "kcSelectOTPListClass"
| "kcSelectOTPListItemClass"
| "kcAuthenticatorOtpCircleClass"
| "kcSelectOTPItemHeadingClass"
| "kcFormOptionsWrapperClass";
export const defaultClasses: Record<ClassKey, string | undefined> = {
...defaultTemplateClasses,
"kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid",
"kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3",
"kcFeedbackAreaClass": "col-md-12",
"kcLocaleClass": "col-xs-12 col-sm-1",
"kcAlertIconClasserror": "pficon pficon-error-circle-o",
"kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
"kcFormSocialAccountListClass": "login-pf-social list-unstyled login-pf-social-all",
"kcFormSocialAccountDoubleListClass": "login-pf-social-double-col",
"kcFormSocialAccountListLinkClass": "login-pf-social-link",
"kcWebAuthnKeyIcon": "pficon pficon-key",
"kcWebAuthnDefaultIcon": "pficon pficon-key",
"kcFormClass": "form-horizontal",
"kcFormGroupErrorClass": "has-error",
"kcLabelClass": "control-label",
"kcInputClass": "form-control",
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text",
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormSettingClass": "login-pf-settings",
"kcTextareaClass": "form-control",
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
// user-profile grouping
"kcFormGroupHeader": "pf-c-form__group",
// css classes for form buttons main class used for all buttons
"kcButtonClass": "btn",
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
"kcButtonPrimaryClass": "btn-primary",
"kcButtonDefaultClass": "btn-default",
// classes defining size of the button
"kcButtonLargeClass": "btn-lg",
"kcButtonBlockClass": "btn-block",
// css classes for input
"kcInputLargeClass": "input-lg",
// css classes for form accessability
"kcSrOnlyClass": "sr-only",
// css classes for select-authenticator form
"kcSelectAuthListClass": "list-group list-view-pf",
"kcSelectAuthListItemClass": "list-group-item list-view-pf-stacked",
"kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill",
"kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties",
"kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon",
"kcSelectAuthListItemTitle": "select-auth-box-paragraph",
"kcSelectAuthListItemInfoClass": "list-view-pf-main-info",
"kcSelectAuthListItemLeftClass": "list-view-pf-left",
"kcSelectAuthListItemBodyClass": "list-view-pf-body",
"kcSelectAuthListItemDescriptionClass": "list-view-pf-description",
"kcSelectAuthListItemHeadingClass": "list-group-item-heading",
"kcSelectAuthListItemHelpTextClass": "list-group-item-text",
// css classes for the authenticators
"kcAuthenticatorDefaultClass": "fa list-view-pf-icon-lg",
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg",
"kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg",
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg",
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
//css classes for the OTP Login Form
"kcSelectOTPListClass": "card-pf card-pf-view card-pf-view-select card-pf-view-single-select",
"kcSelectOTPListItemClass": "card-pf-body card-pf-top-element",
"kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle",
"kcSelectOTPItemHeadingClass": "card-pf-title text-center",
"kcFormOptionsWrapperClass": undefined
};

View File

@ -1,11 +1,16 @@
import React from "react";
import { clsx } from "../tools/clsx";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { clsx } from "keycloakify/tools/clsx";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function Register(props: PageProps<Extract<KcContextBase, { pageId: "register.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function Register(props: PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
@ -13,55 +18,70 @@ export default function Register(props: PageProps<Extract<KcContextBase, { pageI
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("registerTitle")}
formNode={
<form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}>
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("firstName", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="firstName" className={getClassName("kcLabelClass")}>
{msg("firstName")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="firstName"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="firstName"
defaultValue={register.formData.firstName ?? ""}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("lastName", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="lastName" className={getClassName("kcLabelClass")}>
{msg("lastName")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="lastName"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="email" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("email", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="email" className={getClassName("kcLabelClass")}>
{msg("email")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="email"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
@ -69,17 +89,22 @@ export default function Register(props: PageProps<Extract<KcContextBase, { pageI
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
<div
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("username", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="username" className={getClassName("kcLabelClass")}>
{msg("username")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="text"
id="username"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="username"
defaultValue={register.formData.username ?? ""}
autoComplete="username"
@ -90,18 +115,21 @@ export default function Register(props: PageProps<Extract<KcContextBase, { pageI
{passwordRequired && (
<>
<div
className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}
className={clsx(
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("password", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="password" className={getClassName("kcLabelClass")}>
{msg("password")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<input
type="password"
id="password"
className={clsx(kcProps.kcInputClass)}
className={getClassName("kcInputClass")}
name="password"
autoComplete="new-password"
/>
@ -110,44 +138,44 @@ export default function Register(props: PageProps<Extract<KcContextBase, { pageI
<div
className={clsx(
kcProps.kcFormGroupClass,
messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass)
getClassName("kcFormGroupClass"),
messagesPerField.printIfExists("password-confirm", getClassName("kcFormGroupErrorClass"))
)}
>
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
<div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor="password-confirm" className={getClassName("kcLabelClass")}>
{msg("passwordConfirm")}
</label>
</div>
<div className={clsx(kcProps.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={clsx(kcProps.kcInputClass)} name="password-confirm" />
<div className={getClassName("kcInputWrapperClass")}>
<input type="password" id="password-confirm" className={getClassName("kcInputClass")} name="password-confirm" />
</div>
</div>
</>
)}
{recaptchaRequired && (
<div className="form-group">
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div>
</div>
)}
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
value={msgStr("doRegister")}

View File

@ -1,12 +1,18 @@
import React, { useState } from "react";
import { clsx } from "../tools/clsx";
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function RegisterUserProfile(props: PageProps<Extract<KcContextBase, { pageId: "register-user-profile.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
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;
@ -16,36 +22,41 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContextBa
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={messagesPerField.exists("global")}
displayRequiredFields={true}
headerNode={msg("registerTitle")}
formNode={
<form id="kc-register-form" className={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
<UserProfileFormFields
kcContext={kcContext}
onIsFormSubmittableValueChange={setIsFomSubmittable}
i18n={i18n}
getClassName={getClassName}
/>
{recaptchaRequired && (
<div className="form-group">
<div className={clsx(kcProps.kcInputWrapperClass)}>
<div className={getClassName("kcInputWrapperClass")}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
</div>
</div>
)}
<div className={clsx(kcProps.kcFormGroupClass)} style={{ "marginBottom": 30 }}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<div className={getClassName("kcFormGroupClass")} style={{ "marginBottom": 30 }}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
value={msgStr("doRegister")}

View File

@ -1,20 +1,19 @@
import React, { useEffect } from "react";
import { memoize } from "../tools/memoize";
import { clsx } from "../tools/clsx";
import { Evt } from "evt";
import { clsx } from "keycloakify/tools/clsx";
import { useRerenderOnStateChange } from "evt/hooks";
import { assert } from "tsafe/assert";
import { fallbackLanguageTag } from "../i18n";
import { useConst } from "../tools/useConst";
import { useConstCallback } from "../tools/useConstCallback";
import { Markdown } from "../tools/Markdown";
import type { Extends } from "tsafe";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import { evtTermMarkdown } from "keycloakify/lib/useDownloadTerms";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function Terms(props: PageProps<Extract<KcContextBase, { pageId: "terms.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { msg, msgStr } = i18n;
@ -28,7 +27,7 @@ export default function Terms(props: PageProps<Extract<KcContextBase, { pageId:
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("termsTitle")}
formNode={
@ -37,11 +36,11 @@ export default function Terms(props: PageProps<Extract<KcContextBase, { pageId:
<form className="form-actions" action={url.loginAction} method="POST">
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonClass,
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonClass"),
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
name="accept"
id="kc-accept"
@ -49,7 +48,7 @@ export default function Terms(props: PageProps<Extract<KcContextBase, { pageId:
value={msgStr("doAccept")}
/>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))}
name="cancel"
id="kc-decline"
type="submit"
@ -62,5 +61,3 @@ export default function Terms(props: PageProps<Extract<KcContextBase, { pageId:
/>
);
}
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);

View File

@ -1,12 +1,18 @@
import React, { useState } from "react";
import { clsx } from "../tools/clsx";
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function UpdateUserProfile(props: PageProps<Extract<KcContextBase, { pageId: "update-user-profile.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function UpdateUserProfile(props: PageProps<Extract<KcContext, { pageId: "update-user-profile.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { msg, msgStr } = i18n;
@ -16,27 +22,40 @@ export default function UpdateUserProfile(props: PageProps<Extract<KcContextBase
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<form id="kc-update-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields
kcContext={kcContext}
onIsFormSubmittableValueChange={setIsFomSubmittable}
i18n={i18n}
getClassName={getClassName}
/>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")}></div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
value={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
className={clsx(
getClassName("kcButtonClass"),
getClassName("kcButtonDefaultClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
name="cancel-aia"
value="true"
@ -48,10 +67,10 @@ export default function UpdateUserProfile(props: PageProps<Extract<KcContextBase
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
type="submit"
defaultValue={msgStr("doSubmit")}

View File

@ -1,14 +1,20 @@
import React, { useRef, useState } from "react";
import { clsx } from "../tools/clsx";
import { useRef, useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { MessageKeyBase } from "../i18n";
import { base64url } from "rfc4648";
import { useConstCallback } from "../tools/useConstCallback";
import type { PageProps } from "../KcProps";
import type { KcContextBase } from "../kcContext";
import type { I18nBase } from "../i18n";
import { type PageProps, defaultClasses } from "keycloakify/pages/PageProps";
import { useGetClassName } from "keycloakify/lib/useGetClassName";
import type { KcContextBase as KcContext } from "../kcContext";
import type { I18nBase as I18n } from "../i18n";
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextBase, { pageId: "webauthn-authenticate.ftl" }>, I18nBase>) {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
"defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
classes
});
const { url } = kcContext;
@ -89,10 +95,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("webauthn-login-title")}
formNode={
<div id="kc-form-webauthn" className={clsx(kcProps.kcFormClass)}>
<div id="kc-form-webauthn" className={getClassName("kcFormClass")}>
<form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
<input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
<input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
@ -101,10 +107,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
<input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
<input type="hidden" id="error" name="error" value={error} />
</form>
<div className={clsx(kcProps.kcFormGroupClass)}>
<div className={getClassName("kcFormGroupClass")}>
{authenticators &&
(() => (
<form id="authn_select" className={clsx(kcProps.kcFormClass)}>
<form id="authn_select" className={getClassName("kcFormClass")}>
{authenticators.authenticators.map(authenticator => (
<input
type="hidden"
@ -120,23 +126,28 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
(() => (
<>
{authenticators.authenticators.length > 1 && (
<p className={clsx(kcProps.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
<p className={getClassName("kcSelectAuthListItemTitle")}>{msg("webauthn-available-authenticators")}</p>
)}
<div className={clsx(kcProps.kcFormClass)}>
<div className={getClassName("kcFormClass")}>
{authenticators.authenticators.map(authenticator => (
<div id="kc-webauthn-authenticator" className={clsx(kcProps.kcSelectAuthListItemClass)}>
<div className={clsx(kcProps.kcSelectAuthListItemIconClass)}>
<div id="kc-webauthn-authenticator" className={getClassName("kcSelectAuthListItemClass")}>
<div className={getClassName("kcSelectAuthListItemIconClass")}>
<i
className={clsx(
kcProps[authenticator.transports.iconClass] ?? kcProps.kcWebAuthnDefaultIcon,
kcProps.kcSelectAuthListItemIconPropertyClass
(() => {
const className = getClassName(authenticator.transports.iconClass as any);
return className.includes(" ")
? className
: [className, getClassName("kcWebAuthnDefaultIcon")];
})(),
getClassName("kcSelectAuthListItemIconPropertyClass")
)}
/>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemBodyClass)}>
<div className={getClassName("kcSelectAuthListItemBodyClass")}>
<div
id="kc-webauthn-authenticator-label"
className={clsx(kcProps.kcSelectAuthListItemHeadingClass)}
className={getClassName("kcSelectAuthListItemHeadingClass")}
>
{authenticator.label}
</div>
@ -144,7 +155,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
{authenticator.transports && authenticator.transports.displayNameProperties.length && (
<div
id="kc-webauthn-authenticator-transport"
className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}
className={getClassName("kcSelectAuthListItemDescriptionClass")}
>
{authenticator.transports.displayNameProperties.map(
(transport: MessageKeyBase, index: number) => (
@ -159,18 +170,18 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
</div>
)}
<div className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}>
<div className={getClassName("kcSelectAuthListItemDescriptionClass")}>
<span id="kc-webauthn-authenticator-created-label">{msg("webauthn-createdAt-label")}</span>
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span>
</div>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemFillClass)} />
<div className={getClassName("kcSelectAuthListItemFillClass")} />
</div>
))}
</div>
</>
))()}
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-buttons" className={getClassName("kcFormButtonsClass")}>
<input
id="authenticateWebAuthnButton"
type="button"
@ -178,10 +189,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContextB
autoFocus={true}
value={msgStr("webauthn-doAuthenticate")}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
getClassName("kcButtonClass"),
getClassName("kcButtonPrimaryClass"),
getClassName("kcButtonBlockClass"),
getClassName("kcButtonLargeClass")
)}
/>
</div>

View File

@ -1,178 +0,0 @@
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert";
import type { I18nBase } from "../i18n";
import type { ThemeType } from "../bin/keycloakify/generateFtl";
import type { Equals } from "tsafe";
import type { LoginThemeTemplateProps } from "../templates/LoginThemeTemplate";
export namespace KcProps {
export type Login = KcPropsGeneric<
| KcTemplateClassKey.Login
| "kcLogoLink"
| "kcLogoClass"
| "kcContainerClass"
| "kcContentClass"
| "kcFeedbackAreaClass"
| "kcLocaleClass"
| "kcAlertIconClasserror"
| "kcFormAreaClass"
| "kcFormSocialAccountListClass"
| "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon"
| "kcWebAuthnDefaultIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemFillClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcSelectAuthListItemIconPropertyClass"
| "kcSelectAuthListItemIconClass"
| "kcSelectAuthListItemTitle"
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass"
| "kcSelectOTPListClass"
| "kcSelectOTPListItemClass"
| "kcAuthenticatorOtpCircleClass"
| "kcSelectOTPItemHeadingClass"
| "kcFormOptionsWrapperClass"
>;
export type Account = KcPropsGeneric<KcTemplateClassKey.Account>;
}
export const defaultKcProps = {
"login": {
...defaultKcTemplateProps.login,
"kcLogoLink": "http://www.keycloak.org",
"kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid",
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"],
"kcFeedbackAreaClass": ["col-md-12"],
"kcLocaleClass": ["col-xs-12", "col-sm-1"],
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"],
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"],
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
"kcWebAuthnDefaultIcon": ["pficon", "pficon-key"],
"kcFormClass": ["form-horizontal"],
"kcFormGroupErrorClass": ["has-error"],
"kcLabelClass": ["control-label"],
"kcInputClass": ["form-control"],
"kcInputErrorMessageClass": ["pf-c-form__helper-text", "pf-m-error", "required", "kc-feedback-text"],
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormSettingClass": ["login-pf-settings"],
"kcTextareaClass": ["form-control"],
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"],
// user-profile grouping
"kcFormGroupHeader": ["pf-c-form__group"],
// css classes for form buttons main class used for all buttons
"kcButtonClass": ["btn"],
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
"kcButtonPrimaryClass": ["btn-primary"],
"kcButtonDefaultClass": ["btn-default"],
// classes defining size of the button
"kcButtonLargeClass": ["btn-lg"],
"kcButtonBlockClass": ["btn-block"],
// css classes for input
"kcInputLargeClass": ["input-lg"],
// css classes for form accessability
"kcSrOnlyClass": ["sr-only"],
// css classes for select-authenticator form
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
"kcSelectAuthListItemFillClass": ["pf-l-split__item", "pf-m-fill"],
"kcSelectAuthListItemIconPropertyClass": ["fa-2x", "select-auth-box-icon-properties"],
"kcSelectAuthListItemIconClass": ["pf-l-split__item", "select-auth-box-icon"],
"kcSelectAuthListItemTitle": ["select-auth-box-paragraph"],
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
"kcSelectAuthListItemDescriptionClass": ["list-view-pf-description"],
"kcSelectAuthListItemHeadingClass": ["list-group-item-heading"],
"kcSelectAuthListItemHelpTextClass": ["list-group-item-text"],
// css classes for the authenticators
"kcAuthenticatorDefaultClass": ["fa", "list-view-pf-icon-lg"],
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
//css classes for the OTP Login Form
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"],
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
"kcFormOptionsWrapperClass": []
},
"account": {
...defaultKcTemplateProps.account
}
};
assert<Equals<keyof typeof defaultKcProps, ThemeType>>();
assert<typeof defaultKcProps.login extends KcProps.Login ? true : false>();
assert<typeof defaultKcProps.account extends KcProps.Account ? true : false>();
/** To use if you don't want any default */
export const allClearKcProps = {
"login": allPropertiesValuesToUndefined(defaultKcProps.login),
"account": allPropertiesValuesToUndefined(defaultKcProps.account)
};
assert<Equals<keyof typeof allClearKcProps, ThemeType>>();
export namespace PageProps {
export type Login<KcContext, I18n extends I18nBase> = {
kcContext: KcContext;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
LoginThemeTemplate: (props: LoginThemeTemplateProps<any, any>) => JSX.Element | null;
} & KcProps.Login;
export type Account<KcContext, I18n extends I18nBase> = {
kcContext: KcContext;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
AccountThemeTemplate: (props: LoginThemeTemplateProps<any, any>) => JSX.Element | null;
} & KcProps.Account;
}

View File

@ -1,38 +1,22 @@
import React, { useEffect, Fragment } from "react";
import type { KcProps } from "../../KcProps";
import { clsx } from "../../tools/clsx";
import type { I18nBase } from "../../i18n";
import type { Attribute } from "../../kcContext";
// If you are copy pasting this code in your theme project
// you can delete all the following import and replace them by
// import { useFormValidation } from "keycloakify/lib/pages/shared/UserProfileCommons";
// you can also delete the useFormValidation hooks and useGetErrors hooks, they shouldn't need
// to be modified.
import "../../tools/Array.prototype.every";
import { useMemo, useReducer } from "react";
import type { KcContextBase, Validators } from "../../kcContext";
import { useConstCallback } from "../../tools/useConstCallback";
import { emailRegexp } from "../../tools/emailRegExp";
import type { MessageKeyBase } from "../../i18n";
import { id } from "tsafe/id";
import { useEffect, Fragment } from "react";
import type { ClassKey } from "keycloakify/pages/PageProps";
import { clsx } from "keycloakify/tools/clsx";
import { useFormValidation } from "keycloakify/lib/useFormValidation";
import type { Attribute } from "keycloakify/kcContext";
import type { I18nBase as 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 {
@ -57,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>
@ -81,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;
@ -147,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}
@ -163,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
}}
@ -183,477 +170,3 @@ export function UserProfileFormFields({
</>
);
}
/**
* NOTE: The attributesWithPassword returned is actually augmented with
* artificial password related attributes only if kcContext.passwordRequired === true
*/
export function useFormValidation(params: {
kcContext: {
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
profile: {
attributes: Attribute[];
};
passwordRequired?: boolean;
realm: { registrationEmailAsUsername: boolean };
};
/** NOTE: Try to avoid passing a new ref every render for better performances. */
passwordValidators?: Validators;
i18n: I18nBase;
}) {
const {
kcContext,
passwordValidators = {
"length": {
"ignore.empty.value": true,
"min": "4"
}
},
i18n
} = params;
const attributesWithPassword = useMemo(
() =>
!kcContext.passwordRequired
? kcContext.profile.attributes
: (() => {
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
return kcContext.profile.attributes.reduce<Attribute[]>(
(prev, curr) => [
...prev,
...(curr.name !== name
? [curr]
: [
curr,
id<Attribute>({
"name": "password",
"displayName": id<`\${${MessageKeyBase}}`>("${password}"),
"required": true,
"readOnly": false,
"validators": passwordValidators,
"annotations": {},
"groupAnnotations": {},
"autocomplete": "new-password"
}),
id<Attribute>({
"name": "password-confirm",
"displayName": id<`\${${MessageKeyBase}}`>("${passwordConfirm}"),
"required": true,
"readOnly": false,
"validators": {
"_compareToOther": {
"name": "password",
"ignore.empty.value": true,
"shouldBe": "equal",
"error-message": id<`\${${MessageKeyBase}}`>("${invalidPasswordConfirmMessage}")
}
},
"annotations": {},
"groupAnnotations": {},
"autocomplete": "new-password"
})
])
],
[]
);
})(),
[kcContext, passwordValidators]
);
const { getErrors } = useGetErrors({
"kcContext": {
"messagesPerField": kcContext.messagesPerField,
"profile": {
"attributes": attributesWithPassword
}
},
i18n
});
const initialInternalState = useMemo(
() =>
Object.fromEntries(
attributesWithPassword
.map(attribute => ({
attribute,
"errors": getErrors({
"name": attribute.name,
"fieldValueByAttributeName": Object.fromEntries(
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
)
})
}))
.map(({ attribute, errors }) => [
attribute.name,
{
"value": attribute.value ?? "",
errors,
"doDisplayPotentialErrorMessages": errors.length !== 0
}
])
),
[attributesWithPassword]
);
type InternalState = typeof initialInternalState;
const [formValidationInternalState, formValidationDispatch] = useReducer(
(
state: InternalState,
params:
| {
action: "update value";
name: string;
newValue: string;
}
| {
action: "focus lost";
name: string;
}
): InternalState => ({
...state,
[params.name]: {
...state[params.name],
...(() => {
switch (params.action) {
case "focus lost":
return { "doDisplayPotentialErrorMessages": true };
case "update value":
return {
"value": params.newValue,
"errors": getErrors({
"name": params.name,
"fieldValueByAttributeName": {
...state,
[params.name]: { "value": params.newValue }
}
})
};
}
})()
}
}),
initialInternalState
);
const formValidationState = useMemo(
() => ({
"fieldStateByAttributeName": Object.fromEntries(
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
name,
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
])
),
"isFormSubmittable": Object.entries(formValidationInternalState).every(
([name, { value, errors }]) =>
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
)
}),
[formValidationInternalState, attributesWithPassword]
);
return {
formValidationState,
formValidationDispatch,
attributesWithPassword
};
}
/** Expect to be used in a component wrapped within a <I18nProvider> */
function useGetErrors(params: {
kcContext: {
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
profile: {
attributes: { name: string; value?: string; validators: Validators }[];
};
};
i18n: I18nBase;
}) {
const { kcContext, i18n } = params;
const {
messagesPerField,
profile: { attributes }
} = kcContext;
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
const { name, fieldValueByAttributeName } = params;
const { value } = fieldValueByAttributeName[name];
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
block: {
if (defaultValue !== value) {
break block;
}
let doesErrorExist: boolean;
try {
doesErrorExist = messagesPerField.existsError(name);
} catch {
break block;
}
if (!doesErrorExist) {
break block;
}
const errorMessageStr = messagesPerField.get(name);
return [
{
"validatorName": undefined,
errorMessageStr,
"errorMessage": <span key={0}>{errorMessageStr}</span>
}
];
}
const errors: {
errorMessage: JSX.Element;
errorMessageStr: string;
validatorName: keyof Validators | undefined;
}[] = [];
scope: {
const validatorName = "length";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (max !== undefined && value.length > parseInt(max)) {
const msgArgs = ["error-invalid-length-too-long", max] as const;
errors.push({
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs),
validatorName
});
}
if (min !== undefined && value.length < parseInt(min)) {
const msgArgs = ["error-invalid-length-too-short", min] as const;
errors.push({
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs),
validatorName
});
}
}
scope: {
const validatorName = "_compareToOther";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, name: otherName, shouldBe, "error-message": errorMessageKey } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
const { value: otherValue } = fieldValueByAttributeName[otherName];
const isValid = (() => {
switch (shouldBe) {
case "different":
return otherValue !== value;
case "equal":
return otherValue === value;
}
})();
if (isValid) {
break scope;
}
const msgArg = [
errorMessageKey ??
id<MessageKeyBase>(
(() => {
switch (shouldBe) {
case "equal":
return "shouldBeEqual";
case "different":
return "shouldBeDifferent";
}
})()
),
otherName,
name,
shouldBe
] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArg)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArg)
});
}
scope: {
const validatorName = "pattern";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (new RegExp(pattern).test(value)) {
break scope;
}
const msgArgs = [errorMessageKey ?? id<MessageKeyBase>("shouldMatchPattern"), pattern] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs)
});
}
scope: {
if ([...errors].reverse()[0]?.validatorName === "pattern") {
break scope;
}
const validatorName = "email";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
if (emailRegexp.test(value)) {
break scope;
}
const msgArgs = [id<MessageKeyBase>("invalidEmailMessage")] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
scope: {
const validatorName = "integer";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
if (ignoreEmptyValue && value === "") {
break scope;
}
const intValue = parseInt(value);
if (isNaN(intValue)) {
const msgArgs = ["mustBeAnInteger"] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
if (max !== undefined && intValue > parseInt(max)) {
const msgArgs = ["error-number-out-of-range-too-big", max] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
if (min !== undefined && intValue < parseInt(min)) {
const msgArgs = ["error-number-out-of-range-too-small", min] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
break scope;
}
}
scope: {
const validatorName = "options";
const validator = validators[validatorName];
if (validator === undefined) {
break scope;
}
if (value === "") {
break scope;
}
if (validator.options.indexOf(value) >= 0) {
break scope;
}
const msgArgs = [id<MessageKeyBase>("notAValidOption")] as const;
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs)
});
}
//TODO: Implement missing validators.
return errors;
});
return { getErrors };
}

View File

@ -14,7 +14,7 @@
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true
},
"exclude": ["./test", "./bin"],
"exclude": ["./bin"],
"references": [
{
"path": "./bin"

View File

@ -1,11 +1,11 @@
import { getKcContext } from "../../kcContext";
import type { KcContextBase } from "../../kcContext";
import type { ExtendsKcContextBase } from "../../kcContext";
import { getKcContext } from "../../src/kcContext";
import type { KcContextBase } from "../../src/kcContext";
import type { ExtendsKcContextBase } from "../../src/kcContext";
import { same } from "evt/tools/inDepth";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import { kcContextMocks, kcContextCommonMock } from "../../kcContext/kcContextMocks";
import { deepClone } from "../../tools/deepClone";
import { kcContextMocks, kcContextCommonMock } from "../../src/kcContext/kcContextMocks";
import { deepClone } from "../../src/tools/deepClone";
{
const authorizedMailDomains = ["example.com", "another-example.com", "*.yet-another-example.com", "*.example.com", "hello-world.com"];

View File

@ -5,7 +5,7 @@
"lib": ["es2015", "DOM", "ES2019.Object"],
"esModuleInterop": true,
"declaration": true,
"outDir": "../../dist_test",
"outDir": "../dist_test",
"sourceMap": true,
"newLine": "LF",
"noUnusedLocals": true,
@ -16,5 +16,5 @@
"jsx": "react",
"noFallthroughCasesInSwitch": true
},
"include": [".."]
"include": ["../src", "."]
}