mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
Merge pull request #6020 from logto-io/gao-add-profile-in-user-settings
refactor(console): allow view and update `user.profile` in settings
This commit is contained in:
commit
e541716012
6 changed files with 49 additions and 12 deletions
6
.changeset/gold-bulldogs-draw.md
Normal file
6
.changeset/gold-bulldogs-draw.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@logto/console": patch
|
||||
"@logto/phrases": patch
|
||||
---
|
||||
|
||||
view and update user's `profile` property in the user settings page
|
|
@ -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';
|
||||
|
|
|
@ -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<UserDetailsForm>({ 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<User> = {
|
||||
...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<User>();
|
||||
|
@ -174,11 +180,29 @@ function UserSettings() {
|
|||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
isRequired
|
||||
title="user_details.field_custom_data"
|
||||
tip={t('user_details.field_custom_data_tip')}
|
||||
>
|
||||
<CodeEditor language="json" value={value} onChange={onChange} />
|
||||
<CodeEditor language="json" value={customData.value} onChange={customData.onChange} />
|
||||
</FormField>
|
||||
<FormField
|
||||
title="user_details.field_profile"
|
||||
tip={
|
||||
<Trans
|
||||
components={{
|
||||
a: (
|
||||
<TextLink
|
||||
href={getDocumentationUrl(profilePropertyReferenceLink)}
|
||||
targetBlank="noopener"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{t('user_details.field_profile_tip')}
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<CodeEditor language="json" value={profile.value} onChange={profile.onChange} />
|
||||
</FormField>
|
||||
</FormCard>
|
||||
</DetailsForm>
|
||||
|
|
|
@ -7,6 +7,7 @@ export type UserDetailsForm = {
|
|||
name: string;
|
||||
avatar: string;
|
||||
customData: string;
|
||||
profile: string;
|
||||
};
|
||||
|
||||
export type UserDetailsOutletContext = {
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 <a>profile property reference</a> 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',
|
||||
|
|
Loading…
Reference in a new issue