mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(core,console,shared): use getUserDisplayName function (#4977)
This commit is contained in:
parent
dcc226b5d9
commit
7c09ac850f
14 changed files with 45 additions and 34 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { ServiceConnector } from '@logto/connector-kit';
|
import { ServiceConnector } from '@logto/connector-kit';
|
||||||
import { emailRegEx, phoneInputRegEx } from '@logto/core-kit';
|
import { emailRegEx, phoneInputRegEx } from '@logto/core-kit';
|
||||||
import { ConnectorType } from '@logto/schemas';
|
import { ConnectorType } from '@logto/schemas';
|
||||||
|
import { parsePhoneNumber } from '@logto/shared/universal';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
import { useForm, useFormContext } from 'react-hook-form';
|
||||||
|
@ -13,7 +14,6 @@ import { Tooltip } from '@/ds-components/Tip';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import { onKeyDownHandler } from '@/utils/a11y';
|
import { onKeyDownHandler } from '@/utils/a11y';
|
||||||
import { trySubmitSafe } from '@/utils/form';
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
import { parsePhoneNumber } from '@/utils/phone';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { User } from '@logto/schemas';
|
import type { User } from '@logto/schemas';
|
||||||
|
import { getUserDisplayName, formatToInternationalPhoneNumber } from '@logto/shared/universal';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -6,7 +7,6 @@ import { useTranslation } from 'react-i18next';
|
||||||
import DefaultAvatar from '@/assets/images/default-avatar.svg';
|
import DefaultAvatar from '@/assets/images/default-avatar.svg';
|
||||||
import ImageWithErrorFallback from '@/ds-components/ImageWithErrorFallback';
|
import ImageWithErrorFallback from '@/ds-components/ImageWithErrorFallback';
|
||||||
import { Tooltip } from '@/ds-components/Tip';
|
import { Tooltip } from '@/ds-components/Tip';
|
||||||
import { formatToInternationalPhoneNumber } from '@/utils/phone';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ function UserAvatar({ className, size = 'medium', user, hasTooltip = false }: Pr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameToDisplay = (name ?? username ?? primaryEmail)?.toLocaleUpperCase();
|
const nameToDisplay = getUserDisplayName({ name, username, primaryEmail })?.toLocaleUpperCase();
|
||||||
|
|
||||||
const color = conditional(
|
const color = conditional(
|
||||||
nameToDisplay &&
|
nameToDisplay &&
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { emailRegEx, usernameRegEx } from '@logto/core-kit';
|
import { emailRegEx, usernameRegEx } from '@logto/core-kit';
|
||||||
import type { User } from '@logto/schemas';
|
import type { User } from '@logto/schemas';
|
||||||
|
import { parsePhoneNumber } from '@logto/shared/universal';
|
||||||
import { trySafe } from '@silverhand/essentials';
|
import { trySafe } from '@silverhand/essentials';
|
||||||
import { parsePhoneNumberWithError } from 'libphonenumber-js';
|
import { parsePhoneNumberWithError } from 'libphonenumber-js';
|
||||||
import { useForm, useController } from 'react-hook-form';
|
import { useForm, useController } from 'react-hook-form';
|
||||||
|
@ -18,7 +19,6 @@ import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||||
import { trySubmitSafe } from '@/utils/form';
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
import { safeParseJsonObject } from '@/utils/json';
|
import { safeParseJsonObject } from '@/utils/json';
|
||||||
import { parsePhoneNumber } from '@/utils/phone';
|
|
||||||
import { uriValidator } from '@/utils/validator';
|
import { uriValidator } from '@/utils/validator';
|
||||||
|
|
||||||
import { type UserDetailsForm, type UserDetailsOutletContext } from '../types';
|
import { type UserDetailsForm, type UserDetailsOutletContext } from '../types';
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { User } from '@logto/schemas';
|
import type { User } from '@logto/schemas';
|
||||||
|
import { formatToInternationalPhoneNumber } from '@logto/shared/universal';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
|
||||||
import { formatToInternationalPhoneNumber } from '@/utils/phone';
|
|
||||||
|
|
||||||
import type { UserDetailsForm } from './types';
|
import type { UserDetailsForm } from './types';
|
||||||
|
|
||||||
export const userDetailsParser = {
|
export const userDetailsParser = {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { emailRegEx, phoneInputRegEx, usernameRegEx } from '@logto/core-kit';
|
import { emailRegEx, phoneInputRegEx, usernameRegEx } from '@logto/core-kit';
|
||||||
import type { CreateUser, User } from '@logto/schemas';
|
import type { CreateUser, User } from '@logto/schemas';
|
||||||
|
import { parsePhoneNumber } from '@logto/shared/universal';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
@ -17,7 +18,6 @@ import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||||
import * as modalStyles from '@/scss/modal.module.scss';
|
import * as modalStyles from '@/scss/modal.module.scss';
|
||||||
import { trySubmitSafe } from '@/utils/form';
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
import { generateRandomPassword } from '@/utils/password';
|
import { generateRandomPassword } from '@/utils/password';
|
||||||
import { parsePhoneNumber } from '@/utils/phone';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import type { User } from '@logto/schemas';
|
import type { User } from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { getUserDisplayName } from '@logto/shared/universal';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
import { formatToInternationalPhoneNumber } from './phone';
|
export const getUserTitle = (user?: User): string =>
|
||||||
|
(user ? getUserDisplayName(user) : undefined) ?? t('admin_console.users.unnamed');
|
||||||
|
|
||||||
const getSecondaryUserInfo = (user?: User) => {
|
export const getUserSubtitle = (user?: User) => {
|
||||||
const { primaryEmail, primaryPhone, username } = user ?? {};
|
if (!user?.name) {
|
||||||
const formattedPhoneNumber = conditional(
|
return;
|
||||||
primaryPhone && formatToInternationalPhoneNumber(primaryPhone)
|
}
|
||||||
);
|
|
||||||
return primaryEmail ?? formattedPhoneNumber ?? username;
|
const { username, primaryEmail, primaryPhone } = user;
|
||||||
|
|
||||||
|
return getUserDisplayName({ username, primaryEmail, primaryPhone });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserTitle = (user?: User) =>
|
|
||||||
user?.name ?? getSecondaryUserInfo(user) ?? t('admin_console.users.unnamed');
|
|
||||||
|
|
||||||
export const getUserSubtitle = (user?: User) =>
|
|
||||||
conditional(user?.name && getSecondaryUserInfo(user));
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default function adminUserMfaVerificationsRoutes<T extends AuthedRouter>(
|
||||||
const secret = generateTotpSecret();
|
const secret = generateTotpSecret();
|
||||||
const service = ctx.URL.hostname;
|
const service = ctx.URL.hostname;
|
||||||
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
||||||
const keyUri = authenticator.keyuri(user, service, secret);
|
const keyUri = authenticator.keyuri(user ?? 'Unnamed User', service, secret);
|
||||||
await addUserMfaVerification(id, { type: MfaFactor.TOTP, secret });
|
await addUserMfaVerification(id, { type: MfaFactor.TOTP, secret });
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
type: MfaFactor.TOTP,
|
type: MfaFactor.TOTP,
|
||||||
|
|
|
@ -152,7 +152,7 @@ export default function additionalRoutes<T extends IRouterParamContext>(
|
||||||
name = null,
|
name = null,
|
||||||
} = await parseUserProfile(tenant, profileVerifiedInteraction);
|
} = await parseUserProfile(tenant, profileVerifiedInteraction);
|
||||||
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
||||||
const keyUri = authenticator.keyuri(user, service, secret);
|
const keyUri = authenticator.keyuri(user ?? 'Unnamed User', service, secret);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
secret,
|
secret,
|
||||||
|
@ -166,7 +166,7 @@ export default function additionalRoutes<T extends IRouterParamContext>(
|
||||||
const { accountId } = profileVerifiedInteraction;
|
const { accountId } = profileVerifiedInteraction;
|
||||||
const { username, primaryEmail, primaryPhone, name } = await findUserById(accountId);
|
const { username, primaryEmail, primaryPhone, name } = await findUserById(accountId);
|
||||||
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
const user = getUserDisplayName({ username, primaryEmail, primaryPhone, name });
|
||||||
const keyUri = authenticator.keyuri(user, service, secret);
|
const keyUri = authenticator.keyuri(user ?? 'Unnamed User', service, secret);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
secret,
|
secret,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
type WebAuthnVerificationPayload,
|
type WebAuthnVerificationPayload,
|
||||||
type VerifyMfaResult,
|
type VerifyMfaResult,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
|
import { getUserDisplayName } from '@logto/shared';
|
||||||
import {
|
import {
|
||||||
type GenerateRegistrationOptionsOpts,
|
type GenerateRegistrationOptionsOpts,
|
||||||
generateRegistrationOptions,
|
generateRegistrationOptions,
|
||||||
|
@ -31,14 +32,16 @@ export const generateWebAuthnRegistrationOptions = async ({
|
||||||
rpId,
|
rpId,
|
||||||
user,
|
user,
|
||||||
}: GenerateWebAuthnRegistrationOptionsParameters): Promise<WebAuthnRegistrationOptions> => {
|
}: GenerateWebAuthnRegistrationOptionsParameters): Promise<WebAuthnRegistrationOptions> => {
|
||||||
|
const { username, primaryEmail, primaryPhone, id, mfaVerifications } = user;
|
||||||
|
|
||||||
const options: GenerateRegistrationOptionsOpts = {
|
const options: GenerateRegistrationOptionsOpts = {
|
||||||
rpName: rpId,
|
rpName: rpId,
|
||||||
rpID: rpId,
|
rpID: rpId,
|
||||||
userID: user.id,
|
userID: id,
|
||||||
userName: user.username ?? user.primaryEmail ?? user.primaryPhone ?? user.id,
|
userName: getUserDisplayName({ username, primaryEmail, primaryPhone }) ?? 'Unnamed User',
|
||||||
timeout: 60_000,
|
timeout: 60_000,
|
||||||
attestationType: 'none',
|
attestationType: 'none',
|
||||||
excludeCredentials: user.mfaVerifications
|
excludeCredentials: mfaVerifications
|
||||||
.filter(
|
.filter(
|
||||||
(verification): verification is MfaVerificationWebAuthn =>
|
(verification): verification is MfaVerificationWebAuthn =>
|
||||||
verification.type === MfaFactor.WebAuthn
|
verification.type === MfaFactor.WebAuthn
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"@silverhand/essentials": "^2.8.4",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"find-up": "^6.3.0",
|
"find-up": "^6.3.0",
|
||||||
|
"libphonenumber-js": "^1.9.49",
|
||||||
"nanoid": "^5.0.1",
|
"nanoid": "^5.0.1",
|
||||||
"slonik": "^30.0.0"
|
"slonik": "^30.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,4 @@ export * from './object.js';
|
||||||
export * from './ttl-cache.js';
|
export * from './ttl-cache.js';
|
||||||
export * from './id.js';
|
export * from './id.js';
|
||||||
export * from './user-display-name.js';
|
export * from './user-display-name.js';
|
||||||
|
export * from './phone.js';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
import { formatToInternationalPhoneNumber } from './phone.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user display name from multiple fields
|
* Get user display name from multiple fields
|
||||||
*/
|
*/
|
||||||
|
@ -7,10 +11,14 @@ export const getUserDisplayName = ({
|
||||||
primaryEmail,
|
primaryEmail,
|
||||||
primaryPhone,
|
primaryPhone,
|
||||||
}: {
|
}: {
|
||||||
name: string | null;
|
name?: string | null;
|
||||||
username: string | null;
|
username?: string | null;
|
||||||
primaryEmail: string | null;
|
primaryEmail?: string | null;
|
||||||
primaryPhone: string | null;
|
primaryPhone?: string | null;
|
||||||
}): string => {
|
}): string | undefined => {
|
||||||
return name ?? username ?? primaryEmail ?? primaryPhone ?? 'Unnamed User';
|
const formattedPhoneNumber = conditional(
|
||||||
|
primaryPhone && formatToInternationalPhoneNumber(primaryPhone)
|
||||||
|
);
|
||||||
|
|
||||||
|
return name ?? primaryEmail ?? formattedPhoneNumber ?? username ?? undefined;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3981,6 +3981,9 @@ importers:
|
||||||
find-up:
|
find-up:
|
||||||
specifier: ^6.3.0
|
specifier: ^6.3.0
|
||||||
version: 6.3.0
|
version: 6.3.0
|
||||||
|
libphonenumber-js:
|
||||||
|
specifier: ^1.9.49
|
||||||
|
version: 1.10.51
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
|
@ -15604,7 +15607,6 @@ packages:
|
||||||
|
|
||||||
/libphonenumber-js@1.10.51:
|
/libphonenumber-js@1.10.51:
|
||||||
resolution: {integrity: sha512-vY2I+rQwrDQzoPds0JeTEpeWzbUJgqoV0O4v31PauHBb/e+1KCXKylHcDnBMgJZ9fH9mErsEbROJY3Z3JtqEmg==}
|
resolution: {integrity: sha512-vY2I+rQwrDQzoPds0JeTEpeWzbUJgqoV0O4v31PauHBb/e+1KCXKylHcDnBMgJZ9fH9mErsEbROJY3Z3JtqEmg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/lightningcss-darwin-arm64@1.16.1:
|
/lightningcss-darwin-arm64@1.16.1:
|
||||||
resolution: {integrity: sha512-/J898YSAiGVqdybHdIF3Ao0Hbh2vyVVj5YNm3NznVzTSvkOi3qQCAtO97sfmNz+bSRHXga7ZPLm+89PpOM5gAg==}
|
resolution: {integrity: sha512-/J898YSAiGVqdybHdIF3Ao0Hbh2vyVVj5YNm3NznVzTSvkOi3qQCAtO97sfmNz+bSRHXga7ZPLm+89PpOM5gAg==}
|
||||||
|
|
Loading…
Reference in a new issue