Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(console): allow view and update user.profile in settings #6020

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-bulldogs-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@logto/console": patch
"@logto/phrases": patch
---

view and update user's `profile` property in the user settings page
1 change: 1 addition & 0 deletions packages/console/src/consts/external-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
46 changes: 35 additions & 11 deletions packages/console/src/pages/UserDetails/UserSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();

Expand All @@ -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({
Expand All @@ -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>();
Expand Down Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions packages/console/src/pages/UserDetails/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type UserDetailsForm = {
name: string;
avatar: string;
customData: string;
profile: string;
};

export type UserDetailsOutletContext = {
Expand Down
3 changes: 2 additions & 1 deletion packages/console/src/pages/UserDetails/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand All @@ -18,6 +18,7 @@ export const userDetailsParser = {
name: name ?? '',
avatar: avatar ?? '',
customData: JSON.stringify(customData, null, 2),
profile: JSON.stringify(profile, null, 2),
};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading