diff --git a/src/login/UserProfileFormFields.tsx b/src/login/UserProfileFormFields.tsx index f9edefb9..4d883b76 100644 --- a/src/login/UserProfileFormFields.tsx +++ b/src/login/UserProfileFormFields.tsx @@ -1,8 +1,7 @@ import { useEffect, Fragment } from "react"; import type { ClassKey } from "keycloakify/login/TemplateProps"; -import { clsx } from "keycloakify/tools/clsx"; import { useUserProfileForm, type KcContextLike, type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm"; -import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext"; +import type { Attribute } from "keycloakify/login/kcContext/KcContext"; import { assert } from "tsafe/assert"; import type { I18n } from "./i18n"; @@ -49,20 +48,9 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps) return ( <> {formFieldStates.map(({ attribute, displayableErrors, valueOrValues }) => { - const formGroupClassName = clsx( - getClassName("kcFormGroupClass"), - displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass") - ); - return ( - + {BeforeField !== undefined && ( )}
@@ -142,41 +130,11 @@ function GroupLabel(props: { groupNameRef: { current: string; }; - formGroupClassName: string; }) { - const { attribute, getClassName, i18n, groupNameRef, formGroupClassName } = props; + const { attribute, getClassName, i18n, groupNameRef } = props; const { advancedMsg } = i18n; - keycloak_prior_to_24: { - if (attribute.html5DataAnnotations !== undefined) { - break keycloak_prior_to_24; - } - - const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute as any as LegacyAttribute; - - return ( - <> - {group !== groupNameRef.current && (groupNameRef.current = group) !== "" && ( -
-
- -
- {groupDisplayDescription !== "" && ( -
- -
- )} -
- )} - - ); - } - if (attribute.group?.name !== groupNameRef.current) { groupNameRef.current = attribute.group?.name ?? ""; diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index 1dd2e286..89963108 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -193,7 +193,7 @@ export declare namespace KcContext { profile: { attributes: Attribute[]; attributesByName: Record; - html5DataAnnotations: Record; + html5DataAnnotations?: Record; }; url: { registrationAction: string; @@ -450,8 +450,8 @@ export declare namespace KcContext { export type UpdateUserProfile = Common & { pageId: "update-user-profile.ftl"; profile: { - attributes: LegacyAttribute[]; - attributesByName: Record; + attributes: Attribute[]; + attributesByName: Record; }; }; @@ -459,8 +459,8 @@ export declare namespace KcContext { pageId: "idp-review-user-profile.ftl"; profile: { context: "IDP_REVIEW"; - attributes: LegacyAttribute[]; - attributesByName: Record; + attributes: Attribute[]; + attributesByName: Record; }; }; @@ -517,7 +517,7 @@ export type Attribute = { name: string; displayDescription?: string; }; - html5DataAnnotations: { + html5DataAnnotations?: { kcNumberFormat?: string; kcNumberUnFormat?: string; }; @@ -599,13 +599,6 @@ export type Attribute = { | "photo"; }; -export type LegacyAttribute = Omit & { - group: string; - groupDisplayHeader?: string; - groupDisplayDescription?: string; - groupAnnotations: Record; -}; - export type Validators = Partial<{ length: Validators.DoIgnoreEmpty & Validators.Range; integer: Validators.DoIgnoreEmpty & Validators.Range; diff --git a/src/login/kcContext/register.tsx b/src/login/kcContext/register.tsx new file mode 100644 index 00000000..316c9bce --- /dev/null +++ b/src/login/kcContext/register.tsx @@ -0,0 +1,182 @@ +import { clsx } from "keycloakify/tools/clsx"; +import type { PageProps } from "keycloakify/login/pages/PageProps"; +import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function Register(props: PageProps, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + doUseDefaultCss, + classes + }); + + const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext; + + const { msg, msgStr } = i18n; + + return ( + + ); +} diff --git a/src/login/lib/useUserProfileForm.tsx b/src/login/lib/useUserProfileForm.tsx index a3eef6fa..2e84d34d 100644 --- a/src/login/lib/useUserProfileForm.tsx +++ b/src/login/lib/useUserProfileForm.tsx @@ -68,7 +68,7 @@ export type KcContextLike = { messagesPerField: Pick; profile: { attributes: Attribute[]; - html5DataAnnotations: Record; + html5DataAnnotations?: Record; }; passwordRequired: boolean; realm: { registrationEmailAsUsername: boolean }; @@ -107,8 +107,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy usePrepareTemplate({ "styles": [], - // NOTE: The ?? {} is for compat with Keycloak version prior to 24 - "scripts": Object.keys(kcContext.profile.html5DataAnnotations ?? {}) + "scripts": Object.keys(kcContext.profile?.html5DataAnnotations ?? {}) .filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it. .map(key => ({ "isModule": true, @@ -126,7 +125,69 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy const attributesWithPassword = useMemo(() => { const attributesWithPassword: Attribute[] = []; - for (const attribute of kcContext.profile.attributes) { + const attributes = (() => { + retrocompat_patch: { + if ("profile" in kcContext && "attributes" in kcContext.profile && kcContext.profile.attributes.length !== 0) { + break retrocompat_patch; + } + + kcContext.profile = { + "attributes": (["firstName", "lastName", "email", "username"] as const) + .filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername)) + .map(name => + id({ + "name": name, + "displayName": id<`\${${MessageKey}}`>(`\${${name}}`), + "required": true, + "value": (kcContext as any).register.formData[name] ?? "", + "html5DataAnnotations": {}, + "readOnly": false, + "validators": {}, + "annotations": {}, + "autocomplete": (() => { + switch (name) { + case "email": + return "email"; + case "username": + return "username"; + default: + return undefined; + } + })() + }) + ), + "html5DataAnnotations": {} + }; + } + + return kcContext.profile.attributes; + })(); + + for (const attribute_pre_group_patch of attributes) { + const attribute = (() => { + if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") { + const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } = + attribute_pre_group_patch as Attribute & { + group: string; + groupDisplayHeader?: string; + groupDisplayDescription?: string; + groupAnnotations: Record; + }; + + return id({ + ...rest, + "group": { + "name": group, + "displayHeader": groupDisplayHeader, + "displayDescription": groupDisplayDescription, + "html5DataAnnotations": {} + } + }); + } + + return attribute_pre_group_patch; + })(); + attributesWithPassword.push(attribute); add_password_and_password_confirm: { @@ -191,7 +252,6 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy apply_formatters: { const { attribute } = formFieldState; - // NOTE: The `?? {}` is for compat with Keycloak version prior to 24 const { kcNumberFormat } = attribute.html5DataAnnotations ?? {}; if (kcNumberFormat === undefined) { @@ -407,7 +467,6 @@ function useGetErrors(params: { kcContext: Pick