From c1b36b4d72fdfe45af23f14c65fc7ac4b34b926a Mon Sep 17 00:00:00 2001 From: biss Date: Tue, 4 Aug 2026 15:42:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9C=9F=E5=AE=9E=E8=B4=A6?= =?UTF-8?q?=E6=88=B7=E6=95=B0=E6=8D=AE=E9=A9=B1=E5=8A=A8=E7=9A=84=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E6=A1=A3=E6=A1=88=E5=8D=A1=20=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E8=B5=84=E6=96=99=E6=94=B9=E4=B8=BA=E2=80=9C=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E5=8D=A1=20+=20=E7=BC=96=E8=BE=91=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=E2=80=9D=E5=8F=8C=E6=A0=8F=E5=B8=83=E5=B1=80=20=E4=BE=A7?= =?UTF-8?q?=E6=A0=8F=E5=8A=A0=E5=85=A5=E8=AF=AD=E4=B9=89=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E3=80=81=E4=BA=A7=E5=93=81=E5=9F=9F=E6=A0=87=E9=A2=98=E5=92=8C?= =?UTF-8?q?=E6=9B=B4=E6=98=8E=E7=A1=AE=E7=9A=84=E9=80=89=E4=B8=AD=E7=8A=B6?= =?UTF-8?q?=E6=80=81=20=E9=A1=B5=E9=9D=A2=E9=A6=96=E5=B1=8F=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=BA=AB=E4=BB=BD=E6=8E=A7=E5=88=B6=E5=8F=B0=E5=B1=82?= =?UTF-8?q?=E7=BA=A7=E6=A0=87=E8=AF=86=20=E7=94=A8=E6=88=B7=E5=90=8D?= =?UTF-8?q?=E3=80=81=E9=82=AE=E7=AE=B1=E3=80=81=E5=A7=93=E5=90=8D=E3=80=81?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E4=BC=9A=E9=9A=8F=E8=A1=A8=E5=8D=95=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=AE=9E=E6=97=B6=E5=91=88=E7=8E=B0=20390px=20?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E8=87=AA=E5=8A=A8=E5=A0=86=E5=8F=A0?= =?UTF-8?q?=EF=BC=8C=E6=97=A0=E6=A8=AA=E5=90=91=E6=BA=A2=E5=87=BA=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=BB=84=E4=BB=B6=E5=B7=B2=E7=94=B1=20Keyclo?= =?UTF-8?q?akify=20=E6=AD=A3=E5=BC=8F=E8=AE=A4=E9=A2=86=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E7=BB=AD=E5=90=8C=E6=AD=A5=E4=B8=8D=E4=BC=9A=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.gitignore | 6 +- src/account/components/page/Page.tsx | 51 ++ src/account/personal-info/PersonalInfo.tsx | 259 ++++++ src/account/root/PageNav.tsx | 209 +++++ src/account/theme.css | 931 ++++++++++++++++++--- src/admin/index.css | 569 ++++++++++--- 6 files changed, 1763 insertions(+), 262 deletions(-) create mode 100644 src/account/components/page/Page.tsx create mode 100644 src/account/personal-info/PersonalInfo.tsx create mode 100644 src/account/root/PageNav.tsx 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 ( + +
+