From 9f33d997b216b9e9a8dab03ae1c1ccd337045019 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Thu, 13 Jun 2024 22:44:17 +0800 Subject: [PATCH] refactor(console): allow view and update `user.profile` in settings --- .changeset/gold-bulldogs-draw.md | 6 +++ packages/console/src/consts/external-links.ts | 1 + .../pages/UserDetails/UserSettings/index.tsx | 46 ++++++++++++++----- .../console/src/pages/UserDetails/types.ts | 1 + .../console/src/pages/UserDetails/utils.ts | 3 +- .../translation/admin-console/user-details.ts | 4 ++ 6 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 .changeset/gold-bulldogs-draw.md diff --git a/.changeset/gold-bulldogs-draw.md b/.changeset/gold-bulldogs-draw.md new file mode 100644 index 00000000000..cee4878cdee --- /dev/null +++ b/.changeset/gold-bulldogs-draw.md @@ -0,0 +1,6 @@ +--- +"@logto/console": patch +"@logto/phrases": patch +--- + +view and update user's `profile` property in the user settings page diff --git a/packages/console/src/consts/external-links.ts b/packages/console/src/consts/external-links.ts index 8aab4552b77..170e4a2d4de 100644 --- a/packages/console/src/consts/external-links.ts +++ b/packages/console/src/consts/external-links.ts @@ -26,3 +26,4 @@ export const organizationRoleLink = '/docs/recipes/organizations/understand-how-it-works/#organization-role'; export const organizationPermissionLink = '/docs/recipes/organizations/understand-how-it-works/#organization-permission'; +export const profilePropertyReferenceLink = '/docs/references/users/#profile-1'; diff --git a/packages/console/src/pages/UserDetails/UserSettings/index.tsx b/packages/console/src/pages/UserDetails/UserSettings/index.tsx index bc08dbead1d..3af3ea74f7a 100644 --- a/packages/console/src/pages/UserDetails/UserSettings/index.tsx +++ b/packages/console/src/pages/UserDetails/UserSettings/index.tsx @@ -5,15 +5,17 @@ import { conditionalString, trySafe } from '@silverhand/essentials'; import { parsePhoneNumberWithError } from 'libphonenumber-js'; import { useForm, useController } from 'react-hook-form'; import { toast } from 'react-hot-toast'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useOutletContext } from 'react-router-dom'; import DetailsForm from '@/components/DetailsForm'; import FormCard from '@/components/FormCard'; import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal'; +import { profilePropertyReferenceLink } from '@/consts'; import CodeEditor from '@/ds-components/CodeEditor'; import FormField from '@/ds-components/FormField'; import TextInput from '@/ds-components/TextInput'; +import TextLink from '@/ds-components/TextLink'; import useApi from '@/hooks/use-api'; import { useConfirmModal } from '@/hooks/use-confirm-modal'; import useDocumentationUrl from '@/hooks/use-documentation-url'; @@ -44,9 +46,8 @@ function UserSettings() { formState: { isSubmitting, errors, isDirty }, } = useForm({ defaultValues: userFormData }); - const { - field: { onChange, value }, - } = useController({ name: 'customData', control }); + const { field: customData } = useController({ name: 'customData', control }); + const { field: profile } = useController({ name: 'profile', control }); const api = useApi(); @@ -56,7 +57,7 @@ function UserSettings() { return; } const { identities, id: userId } = user; - const { customData: inputCustomData, username, primaryEmail, primaryPhone } = formData; + const { customData, profile, username, primaryEmail, primaryPhone } = formData; if (!username && !primaryEmail && !primaryPhone && Object.keys(identities).length === 0) { const [result] = await show({ @@ -69,18 +70,23 @@ function UserSettings() { } } - const parseResult = safeParseJsonObject(inputCustomData); - - if (!parseResult.success) { + const parsedCustomData = safeParseJsonObject(customData); + if (!parsedCustomData.success) { toast.error(t('user_details.custom_data_invalid')); + return; + } + const parsedProfile = safeParseJsonObject(profile); + if (!parsedProfile.success) { + toast.error(t('user_details.profile_invalid')); return; } const payload: Partial = { ...formData, primaryPhone: conditionalString(primaryPhone && parsePhoneNumber(primaryPhone)), - customData: parseResult.data, + customData: parsedCustomData.data, + profile: parsedProfile.data, }; const updatedUser = await api.patch(`api/users/${userId}`, { json: payload }).json(); @@ -174,11 +180,29 @@ function UserSettings() { /> - + + + + ), + }} + > + {t('user_details.field_profile_tip')} + + } + > + diff --git a/packages/console/src/pages/UserDetails/types.ts b/packages/console/src/pages/UserDetails/types.ts index c5042160e75..b3bec78e7a5 100644 --- a/packages/console/src/pages/UserDetails/types.ts +++ b/packages/console/src/pages/UserDetails/types.ts @@ -7,6 +7,7 @@ export type UserDetailsForm = { name: string; avatar: string; customData: string; + profile: string; }; export type UserDetailsOutletContext = { diff --git a/packages/console/src/pages/UserDetails/utils.ts b/packages/console/src/pages/UserDetails/utils.ts index f6e32b82ff5..0ebef7869f0 100644 --- a/packages/console/src/pages/UserDetails/utils.ts +++ b/packages/console/src/pages/UserDetails/utils.ts @@ -6,7 +6,7 @@ import type { UserDetailsForm } from './types'; export const userDetailsParser = { toLocalForm: (data: UserProfileResponse): UserDetailsForm => { - const { primaryEmail, primaryPhone, username, name, avatar, customData } = data; + const { primaryEmail, primaryPhone, username, name, avatar, customData, profile } = data; const parsedPhoneNumber = conditional( primaryPhone && formatToInternationalPhoneNumber(primaryPhone) ); @@ -18,6 +18,7 @@ export const userDetailsParser = { name: name ?? '', avatar: avatar ?? '', customData: JSON.stringify(customData, null, 2), + profile: JSON.stringify(profile, null, 2), }; }, }; diff --git a/packages/phrases/src/locales/en/translation/admin-console/user-details.ts b/packages/phrases/src/locales/en/translation/admin-console/user-details.ts index 9bf9542ccbf..b3292e5fc8d 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/user-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/user-details.ts @@ -34,9 +34,13 @@ const user_details = { field_custom_data: 'Custom data', field_custom_data_tip: 'Additional user info not listed in the pre-defined user properties, such as user-preferred color and language.', + field_profile: 'Profile', + field_profile_tip: + "Additional OpenID Connect standard claims that are not included in user's properties. Note that all unknown properties will be stripped. Please refer to profile property reference for more information.", field_connectors: 'Social connections', field_sso_connectors: 'Enterprise connections', custom_data_invalid: 'Custom data must be a valid JSON object', + profile_invalid: 'Profile must be a valid JSON object', connectors: { connectors: 'Connectors', user_id: 'User ID',