diff --git a/src/.gitignore b/src/.gitignore index 3477cab..ae08d99 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -7,6 +7,8 @@ # admin/index.css # admin/KcAdminUi.tsx # account/assets/logo.svg +# account/personal-info/PersonalInfo.tsx +# account/root/PageNav.tsx # admin/assets/logo.svg # admin/i18n/messages_en.properties # admin/i18n/messages_zh_Hans.properties @@ -15,6 +17,7 @@ # email/messages/messages_en.properties # email/messages/messages_zh_CN.properties # email/messages/messages_zh_TW.properties +# account/components/page/Page.tsx # === Owned files end ===== # === @keycloakify/email-native v260007.0.0 === @@ -904,7 +907,6 @@ /account/i18n/messages_zh_Hans.properties /account/i18n/messages_zh_Hant.properties /account/organizations/Organizations.tsx -/account/personal-info/PersonalInfo.tsx /account/resources/EditTheResource.tsx /account/resources/PermissionRequest.tsx /account/resources/Resources.tsx @@ -914,7 +916,6 @@ /account/resources/ShareTheResource.tsx /account/root/header.module.css /account/root/Header.tsx -/account/root/PageNav.tsx /account/root/Root.tsx /account/utils/formatDate.ts /account/utils/joinPath.ts @@ -1074,4 +1075,3 @@ /account/assets/passkeys/zoho-light.svg /account/assets/passkeys/ztpass-light.png /account/components/datalist/EmptyRow.tsx -/account/components/page/Page.tsx diff --git a/src/account/components/page/Page.tsx b/src/account/components/page/Page.tsx new file mode 100644 index 0000000..9875960 --- /dev/null +++ b/src/account/components/page/Page.tsx @@ -0,0 +1,51 @@ +/** + * This file has been claimed for ownership from @keycloakify/keycloak-account-ui version 260700.0.3. + * To relinquish ownership and restore this file to its original content, run the following command: + * + * $ npx keycloakify own --path "account/components/page/Page.tsx" --revert + */ + +/* eslint-disable */ + +// @ts-nocheck + +import { + PageSection, + Text, + TextContent, + Title +} from "../../../shared/@patternfly/react-core"; +import { PropsWithChildren } from "react"; + +type PageProps = { + title: string; + description: string; +}; + +export const Page = ({ title, description, children }: PropsWithChildren) => { + return ( + <> + +
+
+ + NEXUS / Identity control + + + + {title} + + {description} + +
+ +
+
+ {children} + + ); +}; diff --git a/src/account/personal-info/PersonalInfo.tsx b/src/account/personal-info/PersonalInfo.tsx new file mode 100644 index 0000000..0c601bd --- /dev/null +++ b/src/account/personal-info/PersonalInfo.tsx @@ -0,0 +1,259 @@ +/** + * This file has been claimed for ownership from @keycloakify/keycloak-account-ui version 260700.0.3. + * To relinquish ownership and restore this file to its original content, run the following command: + * + * $ npx keycloakify own --path "account/personal-info/PersonalInfo.tsx" --revert + */ + +/* eslint-disable */ + +// @ts-nocheck + +import { + UserProfileFields, + beerify, + debeerify, + setUserProfileServerError, + useEnvironment +} from "../../shared/keycloak-ui-shared"; +import { + ActionGroup, + Alert, + AlertVariant, + Button, + ExpandableSection, + Form, + Spinner +} from "../../shared/@patternfly/react-core"; +import { ExternalLinkSquareAltIcon } from "../../shared/@patternfly/react-icons"; +import { TFunction } from "i18next"; +import { useState } from "react"; +import { ErrorOption, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +import { getPersonalInfo, getSupportedLocales, savePersonalInfo } from "../api/methods"; +import { UserProfileMetadata, UserRepresentation } from "../api/representations"; +import { Page } from "../components/page/Page"; +import { type AccountEnvironment } from ".."; +import type { TFuncKey } from "../i18n-type"; +import { useAccountAlerts } from "../utils/useAccountAlerts"; +import { usePromise } from "../utils/usePromise"; + +export const PersonalInfo = () => { + const { t } = useTranslation(); + const context = useEnvironment(); + const [userProfileMetadata, setUserProfileMetadata] = useState(); + const [supportedLocales, setSupportedLocales] = useState([]); + const form = useForm({ mode: "onChange" }); + const { handleSubmit, reset, setValue, setError } = form; + const profile = form.watch(); + const { addAlert } = useAccountAlerts(); + + usePromise( + signal => + Promise.all([ + getPersonalInfo({ signal, context }), + getSupportedLocales({ signal, context }) + ]), + ([personalInfo, supportedLocales]) => { + setUserProfileMetadata(personalInfo.userProfileMetadata); + setSupportedLocales(supportedLocales); + reset(personalInfo); + Object.entries(personalInfo.attributes || {}).forEach(([k, v]) => + setValue(`attributes[${beerify(k)}]`, v) + ); + } + ); + + const onSubmit = async (user: UserRepresentation) => { + try { + const attributes = Object.fromEntries( + Object.entries(user.attributes || {}).map(([k, v]) => [debeerify(k), v]) + ); + await savePersonalInfo(context, { ...user, attributes }); + const locale = attributes["locale"]?.toString(); + if (locale) { + window.dispatchEvent( + new CustomEvent("languageChanged", { detail: { language: locale } }) + ); + } + await context.keycloak.updateToken(); + addAlert(t("accountUpdatedMessage")); + } catch (error) { + addAlert(t("accountUpdatedError"), AlertVariant.danger); + + setUserProfileServerError( + { responseData: { errors: error as any } }, + (name: string | number, error: unknown) => + setError(name as string, error as ErrorOption), + ((key: TFuncKey, param?: object) => t(key, param as any)) as TFunction + ); + } + }; + + if (!userProfileMetadata) { + return ; + } + + const allFieldsReadOnly = () => + userProfileMetadata.attributes + .map(a => a.readOnly) + .reduce((p, c) => p && c, true); + + const { + updateEmailFeatureEnabled, + updateEmailActionEnabled, + isRegistrationEmailAsUsername, + isEditUserNameAllowed + } = context.environment.features; + + const displayName = + [profile.firstName, profile.lastName].filter(Boolean).join(" ").trim() || + profile.username || + t("personalInfo"); + const initials = + [profile.firstName, profile.lastName] + .filter(Boolean) + .map(value => value!.trim().charAt(0)) + .join("") + .slice(0, 2) + .toUpperCase() || displayName.slice(0, 2).toUpperCase(); + const profileLocale = + profile.attributes?.locale?.toString() || context.environment.locale; + + return ( + +
+