mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console,phrases): add support to update sign-in identifiers in user details form (#3828)
* feat(console,phrases): add support to update sign-in identifiers in user details form * chore: add changeset
This commit is contained in:
parent
1b6bb9b1cf
commit
497d5b5262
40 changed files with 239 additions and 96 deletions
9
.changeset/three-terms-confess.md
Normal file
9
.changeset/three-terms-confess.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
"@logto/console": minor
|
||||
"@logto/phrases": minor
|
||||
"@logto/schemas": patch
|
||||
---
|
||||
|
||||
Support updating sign-in identifiers in user details form
|
||||
- Admin can now update user sign-in identifiers (username, email, phone number) in the user details form in user management.
|
||||
- Other trivial improvements and fixes, e.g. input field placeholder, error handling, etc.
|
|
@ -81,6 +81,7 @@
|
|||
"jest-transformer-svg": "^2.0.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"ky": "^0.33.0",
|
||||
"libphonenumber-js": "^1.9.49",
|
||||
"lint-staged": "^13.0.0",
|
||||
"nanoid": "^4.0.0",
|
||||
"overlayscrollbars": "^2.0.2",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { jsonObjectGuard } from '@logto/schemas';
|
||||
import { emailRegEx, usernameRegEx } from '@logto/core-kit';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { 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';
|
||||
|
@ -12,8 +14,10 @@ import FormField from '@/components/FormField';
|
|||
import TextInput from '@/components/TextInput';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { safeParseJson } from '@/utils/json';
|
||||
import { safeParseJsonObject } from '@/utils/json';
|
||||
import { parsePhoneNumber } from '@/utils/phone';
|
||||
import { uriValidator } from '@/utils/validator';
|
||||
|
||||
import type { UserDetailsForm, UserDetailsOutletContext } from '../types';
|
||||
|
@ -24,7 +28,7 @@ import UserSocialIdentities from './components/UserSocialIdentities';
|
|||
function UserSettings() {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
||||
const { show } = useConfirmModal();
|
||||
const { user, isDeleting, onUserUpdated } = useOutletContext<UserDetailsOutletContext>();
|
||||
|
||||
const userFormData = userDetailsParser.toLocalForm(user);
|
||||
|
@ -35,7 +39,6 @@ function UserSettings() {
|
|||
control,
|
||||
reset,
|
||||
formState: { isSubmitting, errors, isDirty },
|
||||
getValues,
|
||||
} = useForm<UserDetailsForm>({ defaultValues: userFormData });
|
||||
|
||||
const {
|
||||
|
@ -48,35 +51,42 @@ function UserSettings() {
|
|||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
const { identities, id: userId } = user;
|
||||
const { customData: inputCustomData, username, primaryEmail, primaryPhone } = formData;
|
||||
|
||||
const { customData: inputCustomData, name, avatar } = formData;
|
||||
if (!username && !primaryEmail && !primaryPhone && Object.keys(identities).length === 0) {
|
||||
const [result] = await show({
|
||||
ModalContent: t('user_details.warning_no_sign_in_identifier'),
|
||||
type: 'confirm',
|
||||
});
|
||||
|
||||
const parseResult = safeParseJson(inputCustomData);
|
||||
|
||||
if (!parseResult.success) {
|
||||
toast.error(parseResult.error);
|
||||
|
||||
return;
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const guardResult = jsonObjectGuard.safeParse(parseResult.data);
|
||||
const parseResult = safeParseJsonObject(inputCustomData);
|
||||
|
||||
if (!guardResult.success) {
|
||||
if (!parseResult.success) {
|
||||
toast.error(t('user_details.custom_data_invalid'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: Partial<User> = {
|
||||
name,
|
||||
avatar,
|
||||
customData: guardResult.data,
|
||||
...formData,
|
||||
primaryPhone: parsePhoneNumber(primaryPhone),
|
||||
customData: parseResult.data,
|
||||
};
|
||||
|
||||
const updatedUser = await api.patch(`api/users/${user.id}`, { json: payload }).json<User>();
|
||||
reset(userDetailsParser.toLocalForm(updatedUser));
|
||||
onUserUpdated(updatedUser);
|
||||
toast.success(t('general.saved'));
|
||||
try {
|
||||
const updatedUser = await api.patch(`api/users/${userId}`, { json: payload }).json<User>();
|
||||
reset(userDetailsParser.toLocalForm(updatedUser));
|
||||
onUserUpdated(updatedUser);
|
||||
toast.success(t('general.saved'));
|
||||
} catch {
|
||||
// Do nothing since we only show error toasts, which is handled in the useApi hook
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -92,23 +102,45 @@ function UserSettings() {
|
|||
description="user_details.settings_description"
|
||||
learnMoreLink={getDocumentationUrl('/docs/references/users')}
|
||||
>
|
||||
{getValues('primaryEmail') && (
|
||||
<FormField title="user_details.field_email">
|
||||
<TextInput readOnly {...register('primaryEmail')} />
|
||||
</FormField>
|
||||
)}
|
||||
{getValues('primaryPhone') && (
|
||||
<FormField title="user_details.field_phone">
|
||||
<TextInput readOnly {...register('primaryPhone')} />
|
||||
</FormField>
|
||||
)}
|
||||
{getValues('username') && (
|
||||
<FormField title="user_details.field_username">
|
||||
<TextInput readOnly {...register('username')} />
|
||||
</FormField>
|
||||
)}
|
||||
<FormField title="user_details.field_name">
|
||||
<TextInput {...register('name')} />
|
||||
<TextInput {...register('name')} placeholder={t('users.placeholder_name')} />
|
||||
</FormField>
|
||||
<FormField title="user_details.field_email">
|
||||
<TextInput
|
||||
{...register('primaryEmail', {
|
||||
pattern: { value: emailRegEx, message: t('errors.email_pattern_error') },
|
||||
})}
|
||||
error={errors.primaryEmail?.message}
|
||||
placeholder={t('users.placeholder_email')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="user_details.field_phone">
|
||||
<TextInput
|
||||
{...register('primaryPhone', {
|
||||
validate: (value) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
const parsed = trySafe(() => parsePhoneNumberWithError(value));
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
parsed?.isValid() || t('errors.phone_pattern_error')
|
||||
);
|
||||
},
|
||||
})}
|
||||
error={errors.primaryPhone?.message}
|
||||
placeholder={t('users.placeholder_phone')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="user_details.field_username">
|
||||
<TextInput
|
||||
{...register('username', {
|
||||
pattern: { value: usernameRegEx, message: t('errors.username_pattern_error') },
|
||||
})}
|
||||
error={errors.username?.message}
|
||||
placeholder={t('users.placeholder_username')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="user_details.field_avatar">
|
||||
<TextInput
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import { formatToInternationalPhoneNumber } from '@/utils/phone';
|
||||
|
||||
import type { UserDetailsForm } from './types';
|
||||
|
||||
export const userDetailsParser = {
|
||||
toLocalForm: (data: User): UserDetailsForm => {
|
||||
const { primaryEmail, primaryPhone, username, name, avatar, customData } = data;
|
||||
const parsedPhoneNumber = conditional(
|
||||
primaryPhone && formatToInternationalPhoneNumber(primaryPhone)
|
||||
);
|
||||
|
||||
return {
|
||||
primaryEmail: primaryEmail ?? '',
|
||||
primaryPhone: primaryPhone ?? '',
|
||||
primaryPhone: parsedPhoneNumber ?? primaryPhone ?? '',
|
||||
username: username ?? '',
|
||||
name: name ?? '',
|
||||
avatar: avatar ?? '',
|
||||
|
|
|
@ -89,14 +89,18 @@ function CreateForm({ onClose, onCreate }: Props) {
|
|||
Object.entries(userData).filter(([, value]) => Boolean(value))
|
||||
);
|
||||
|
||||
const createdUser = await api.post('api/users', { json: payload }).json<User>();
|
||||
try {
|
||||
const createdUser = await api.post('api/users', { json: payload }).json<User>();
|
||||
|
||||
setCreatedUserInfo({
|
||||
user: createdUser,
|
||||
password,
|
||||
});
|
||||
setCreatedUserInfo({
|
||||
user: createdUser,
|
||||
password,
|
||||
});
|
||||
|
||||
onCreate();
|
||||
onCreate();
|
||||
} catch {
|
||||
// Do nothing since we only show error toasts, which is handled in the useApi hook
|
||||
}
|
||||
});
|
||||
|
||||
return createdUserInfo ? (
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import { jsonGuard, type Json, jsonObjectGuard, type JsonObject } from '@logto/schemas';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const safeParseJson = (
|
||||
jsonString: string
|
||||
): { success: true; data: unknown } | { success: false; error: string } => {
|
||||
): { success: true; data: Json } | { success: false; error: string } => {
|
||||
try {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const data = JSON.parse(jsonString) as unknown;
|
||||
const data = jsonGuard.parse(JSON.parse(jsonString));
|
||||
|
||||
return { success: true, data };
|
||||
} catch {
|
||||
return { success: false, error: t('admin_console.errors.invalid_json_format') };
|
||||
}
|
||||
};
|
||||
|
||||
export const safeParseJsonObject = (
|
||||
jsonString: string
|
||||
): { success: true; data: JsonObject } | { success: false; error: string } => {
|
||||
try {
|
||||
const data = jsonObjectGuard.parse(JSON.parse(jsonString));
|
||||
|
||||
return { success: true, data };
|
||||
} catch {
|
||||
|
|
|
@ -1,2 +1,28 @@
|
|||
export const parsePhoneNumber = (phone: string) =>
|
||||
phone.replace(/[ ()-]/g, '').replace(/\+/g, '00');
|
||||
import { parsePhoneNumberWithError } from 'libphonenumber-js';
|
||||
|
||||
/**
|
||||
* Parse phone number to number string.
|
||||
* E.g. +1 (650) 253-0000 -> 16502530000
|
||||
*/
|
||||
export const parsePhoneNumber = (phone: string) => {
|
||||
try {
|
||||
return parsePhoneNumberWithError(phone).number.slice(1);
|
||||
} catch {
|
||||
console.error(`Invalid phone number: ${phone}`);
|
||||
return phone;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse phone number to readable international format.
|
||||
* E.g. 16502530000 -> +1 650 253 0000
|
||||
*/
|
||||
export const formatToInternationalPhoneNumber = (phone: string) => {
|
||||
try {
|
||||
const phoneNumber = phone.startsWith('+') ? phone : `+${phone}`;
|
||||
return parsePhoneNumberWithError(phoneNumber).formatInternational();
|
||||
} catch {
|
||||
console.error(`Invalid phone number: ${phone}`);
|
||||
return phone;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import { formatToInternationalPhoneNumber } from './phone';
|
||||
|
||||
const getUserIdentity = (user: User) => {
|
||||
const { primaryEmail, primaryPhone, username } = user;
|
||||
return primaryEmail ?? primaryPhone ?? username;
|
||||
const formattedPhoneNumber = conditional(
|
||||
primaryPhone && formatToInternationalPhoneNumber(primaryPhone)
|
||||
);
|
||||
return primaryEmail ?? formattedPhoneNumber ?? username;
|
||||
};
|
||||
|
||||
export const getUserTitle = (user: User) => user.name ?? getUserIdentity(user);
|
||||
|
|
|
@ -73,6 +73,8 @@ const user_details = {
|
|||
search: 'Nach Rollennamen, Beschreibung oder ID suchen',
|
||||
empty: 'Keine Rolle verfügbar',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'Der Benutzer muss mindestens einen der Anmelde-Identifikatoren (Benutzername, E-Mail, Telefonnummer oder soziales Konto) haben, um sich anzumelden. Sind Sie sicher, dass Sie fortfahren möchten?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -13,9 +13,10 @@ const users = {
|
|||
create_form_username: 'Benutzername',
|
||||
create_form_password: 'Passwort',
|
||||
create_form_name: 'Name',
|
||||
placeholder_email: 'ihremail@domain.com',
|
||||
placeholder_username: 'Ihr Benutzername',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'Max Mustermann',
|
||||
placeholder_email: 'max@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+49 (0)123 456789',
|
||||
unnamed: 'Unbenannt',
|
||||
search: 'Suche nach Name, E-Mail, Telefon oder Benutzername',
|
||||
check_user_detail: 'Benutzerdetails überprüfen',
|
||||
|
|
|
@ -71,6 +71,8 @@ const user_details = {
|
|||
search: 'Search by role name, description or ID',
|
||||
empty: 'No role available',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'User needs to have at least one of the sign-in identifiers (username, email, phone number or social) to sign in. Are you sure you want to continue?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: 'Username',
|
||||
create_form_password: 'Password',
|
||||
create_form_name: 'Full name',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: 'Your username',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'John/Jane Doe',
|
||||
placeholder_email: 'jdoe@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+1 (555) 555-5555',
|
||||
unnamed: 'Unnamed',
|
||||
search: 'Search by name, email, phone or username',
|
||||
check_user_detail: 'Check user detail',
|
||||
|
|
|
@ -73,6 +73,8 @@ const user_details = {
|
|||
search: 'Buscar por nombre de rol, descripción o ID',
|
||||
empty: 'No hay roles disponibles',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'El usuario necesita tener al menos uno de los identificadores de inicio de sesión (nombre de usuario, correo electrónico, número de teléfono o red social) para iniciar sesión. ¿Estás seguro/a de que quieres continuar?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: 'Nombre de usuario',
|
||||
create_form_password: 'Contraseña',
|
||||
create_form_name: 'Nombre completo',
|
||||
placeholder_email: 'tucorreo@dominio.com',
|
||||
placeholder_username: 'Tu nombre de usuario',
|
||||
placeholder_phone: '+51 912 345 678',
|
||||
placeholder_name: 'Fulano de Tal',
|
||||
placeholder_email: 'fulano@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+34 123 456 789',
|
||||
unnamed: 'Sin nombre',
|
||||
search: 'Buscar por nombre, correo electrónico, teléfono o nombre de usuario',
|
||||
check_user_detail: 'Ver detalles del usuario',
|
||||
|
|
|
@ -73,6 +73,8 @@ const user_details = {
|
|||
search: 'Recherche par nom de rôle, description ou ID',
|
||||
empty: 'Aucun rôle disponible',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
"L'utilisateur doit avoir au moins l'un des identifiants de connexion (nom d'utilisateur, e-mail, numéro de téléphone ou compte social) pour se connecter. Êtes-vous sûr(e) de vouloir continuer?",
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: "Nom d'utilisateur",
|
||||
create_form_password: 'Mot de passe',
|
||||
create_form_name: 'Nom complet',
|
||||
placeholder_email: 'votreemail@domaine.com',
|
||||
placeholder_username: "Votre nom d'utilisateur",
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'Monsieur/Madame Dupont',
|
||||
placeholder_email: 'mdupont@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+33 1 23 45 67 89',
|
||||
unnamed: 'Sans nom',
|
||||
search: "Rechercher par nom, email, téléphone ou nom d'utilisateur",
|
||||
check_user_detail: "Vérifier les détails de l'utilisateur",
|
||||
|
|
|
@ -73,6 +73,8 @@ const user_details = {
|
|||
search: 'Cerca per nome ruolo, descrizione o ID',
|
||||
empty: 'Nessun ruolo disponibile',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
"L'utente deve avere almeno uno degli identificatori di accesso (nome utente, email, numero di telefono, o social) per accedere. Sei sicuro di voler continuare?",
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: 'Nome utente',
|
||||
create_form_password: 'Password',
|
||||
create_form_name: 'Nome completo',
|
||||
placeholder_email: 'tuaemail@dominio.com',
|
||||
placeholder_username: 'Il tuo nome utente',
|
||||
placeholder_phone: '+39 123-456-7890',
|
||||
placeholder_name: 'Mario Rossi',
|
||||
placeholder_email: 'mario@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+39 02 1234567',
|
||||
unnamed: 'Senza nome',
|
||||
search: 'Cerca per nome, email, telefono o nome utente',
|
||||
check_user_detail: "Controlla i dettagli dell'utente",
|
||||
|
|
|
@ -71,6 +71,8 @@ const user_details = {
|
|||
search: 'ロール名、説明、またはIDで検索',
|
||||
empty: '利用可能な役割はありません',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'ユーザーは、サインインに少なくとも1つの識別子(ユーザー名、メールアドレス、電話番号、またはソーシャル)を持っている必要があります。続行してよろしいですか?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -13,9 +13,10 @@ const users = {
|
|||
create_form_username: 'ユーザー名',
|
||||
create_form_password: 'パスワード',
|
||||
create_form_name: 'フルネーム',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: 'あなたのユーザー名',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: '山田 太郎',
|
||||
placeholder_email: 'taroyamada@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+81 3-1234-5678',
|
||||
unnamed: '名前がありません',
|
||||
search: '名前、メール、電話、またはユーザー名で検索',
|
||||
check_user_detail: 'ユーザー詳細を確認する',
|
||||
|
|
|
@ -70,6 +70,8 @@ const user_details = {
|
|||
search: '역할 이름, 설명, ID로 검색',
|
||||
empty: '역할 없음',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'사용자는 로그인 식별자(사용자 이름, 이메일, 전화 번호 또는 소셜) 중 적어도 하나를 갖고 로그인해야 합니다. 계속 하시겠습니까?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -11,9 +11,10 @@ const users = {
|
|||
create_form_username: '사용자 이름',
|
||||
create_form_password: '비밀번호',
|
||||
create_form_name: '이름',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: '사용자 이름',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: '홍길동',
|
||||
placeholder_email: 'honggildong@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+82 2-1234-5678',
|
||||
unnamed: '이름없음',
|
||||
search: '이름, 이메일, 전화번호, ID로 검색',
|
||||
check_user_detail: '사용자 상세정보 확인',
|
||||
|
|
|
@ -70,6 +70,8 @@ const user_details = {
|
|||
search: 'Szukaj po nazwie roli, opisie lub ID',
|
||||
empty: 'Brak dostępnej roli',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'Aby się zalogować, użytkownik musi mieć co najmniej jeden z identyfikatorów logowania (nazwa użytkownika, e-mail, numer telefonu lub konto społecznościowe). Czy na pewno chcesz kontynuować?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -13,9 +13,10 @@ const users = {
|
|||
create_form_username: 'Nazwa użytkownika',
|
||||
create_form_password: 'Hasło',
|
||||
create_form_name: 'Imię i nazwisko',
|
||||
placeholder_email: 'twojemail@domena.com',
|
||||
placeholder_username: 'Twoja nazwa użytkownika',
|
||||
placeholder_phone: '+48 555-123-4567',
|
||||
placeholder_name: 'Jan Kowalski',
|
||||
placeholder_email: 'jkowalski@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+48 123 456 789',
|
||||
unnamed: 'Bez nazwy',
|
||||
search: 'Wyszukaj według nazwy, e-maila, numeru telefonu lub nazwy użytkownika',
|
||||
check_user_detail: 'Sprawdź szczegóły użytkownika',
|
||||
|
|
|
@ -71,6 +71,8 @@ const user_details = {
|
|||
search: 'Pesquisar por nome de função, descrição ou ID',
|
||||
empty: 'Nenhuma função disponível',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'O usuário precisa ter pelo menos um dos identificadores de login (nome de usuário, e-mail, número de telefone ou social) para fazer login. Tem certeza de que deseja continuar?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: 'Nome de usuário',
|
||||
create_form_password: 'Senha',
|
||||
create_form_name: 'Nome completo',
|
||||
placeholder_email: 'seuemail@dominio.com',
|
||||
placeholder_username: 'Seu nome de usuário',
|
||||
placeholder_phone: '+55 11 1234-5678',
|
||||
placeholder_name: 'João da Silva',
|
||||
placeholder_email: 'jsilva@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+55 (11) 1234-5678',
|
||||
unnamed: 'Sem nome',
|
||||
search: 'Busca por nome, e-mail, telefone ou nome de usuário',
|
||||
check_user_detail: 'Detalhes do usuário',
|
||||
|
|
|
@ -73,6 +73,8 @@ const user_details = {
|
|||
search: 'Pesquisar pelo nome, descrição ou ID da função',
|
||||
empty: 'Nenhuma função disponível',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'O utilizador precisa de ter pelo menos um dos identificadores de início de sessão (nome de utilizador, e-mail, número de telefone, ou social) para iniciar sessão. Tem a certeza de que quer continuar?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -12,9 +12,10 @@ const users = {
|
|||
create_form_username: 'Utilizador',
|
||||
create_form_password: 'Palavra-passe',
|
||||
create_form_name: 'Nome completo',
|
||||
placeholder_email: 'seuemail@dominio.com',
|
||||
placeholder_username: 'Seu nome de utilizador',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'Zé Ninguém',
|
||||
placeholder_email: 'zninguem@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+351 21 234 5678',
|
||||
unnamed: 'Sem nome',
|
||||
search: 'Procurar por nome, email, telefone ou nome de utilizador',
|
||||
check_user_detail: 'Ver detalhes do utilizador',
|
||||
|
|
|
@ -71,6 +71,8 @@ const user_details = {
|
|||
search: 'Поиск по названию роли, описанию или ID',
|
||||
empty: 'Нет доступных ролей',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'Пользователь должен иметь хотя бы один из идентификаторов входа (имя пользователя, электронная почта, номер телефона или социальная сеть), чтобы войти. Вы уверены, что хотите продолжить?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -13,9 +13,10 @@ const users = {
|
|||
create_form_username: 'Имя пользователя',
|
||||
create_form_password: 'Пароль',
|
||||
create_form_name: 'Полное имя',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: 'Ваше имя пользователя',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'Иван Иванов',
|
||||
placeholder_email: 'ivan@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+7 (123) 456-78-90',
|
||||
unnamed: 'Без имени',
|
||||
search: 'Поиск по имени, электронной почте, телефону или имени пользователя',
|
||||
check_user_detail: 'Просмотреть информацию о пользователе',
|
||||
|
|
|
@ -71,6 +71,8 @@ const user_details = {
|
|||
search: 'Rol adına, açıklamasına veya Kimliğine göre arama yapın',
|
||||
empty: 'Uygun rol yok',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'Kullanıcının giriş yapmak için en az bir oturum açma kimliği (kullanıcı adı, e-posta, telefon numarası, veya sosyal) olması gerekiyor. Devam etmek istediğinizden emin misiniz?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -13,9 +13,10 @@ const users = {
|
|||
create_form_username: 'Kullanıcı Adı',
|
||||
create_form_password: 'Şifre',
|
||||
create_form_name: 'Ad Soyad',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: 'Kullanıcı adınız',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: 'Ali Veli',
|
||||
placeholder_email: 'aveli@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+90 212 345 67 89',
|
||||
unnamed: 'İsimsiz',
|
||||
search: 'İsim, email, telefon veya kullanıcı adına göre arama',
|
||||
check_user_detail: 'Kullanıcı detaylarını kontrol et',
|
||||
|
|
|
@ -67,6 +67,8 @@ const user_details = {
|
|||
search: '按角色名称、描述或 ID 搜索',
|
||||
empty: '无可用角色',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'用户需要至少拥有一个登录标识(用户名、邮箱、手机号或社交账户)才能登录。确定要继续吗?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -11,9 +11,10 @@ const users = {
|
|||
create_form_username: '用户名',
|
||||
create_form_password: '密码',
|
||||
create_form_name: '姓名',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: '你的用户名',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: '李雷',
|
||||
placeholder_email: 'lilei@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+86 123 4567 8901',
|
||||
unnamed: '未命名',
|
||||
search: '按姓名、电子邮件、电话或用户名搜索',
|
||||
check_user_detail: '查看用户详情',
|
||||
|
|
|
@ -67,6 +67,8 @@ const user_details = {
|
|||
search: '按角色名稱、描述或 ID 搜索',
|
||||
empty: '無可用角色',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'用戶需要至少擁有一個登錄標識(用戶名、電子郵件、電話號碼或社交帳號)才能登錄。確定要繼續嗎?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -11,9 +11,10 @@ const users = {
|
|||
create_form_username: '用戶名',
|
||||
create_form_password: '密碼',
|
||||
create_form_name: '姓名',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: '你的用戶名',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: '無名氏',
|
||||
placeholder_email: 'moumingsi@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+852 9123 4567',
|
||||
unnamed: '未命名',
|
||||
search: '按姓名、電子郵件、電話或用戶名搜索',
|
||||
check_user_detail: '查看用戶詳情',
|
||||
|
|
|
@ -67,6 +67,8 @@ const user_details = {
|
|||
search: '按角色名稱、描述或 ID 搜索',
|
||||
empty: '無可用角色',
|
||||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'使用者需要至少擁有一個登入標識(使用者名稱、電子郵件、電話號碼或社交帳號)才能登入。確定要繼續嗎?',
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -11,9 +11,10 @@ const users = {
|
|||
create_form_username: '用戶名',
|
||||
create_form_password: '密碼',
|
||||
create_form_name: '姓名',
|
||||
placeholder_email: 'youremail@domain.com',
|
||||
placeholder_username: '你的用戶名',
|
||||
placeholder_phone: '+1 555-123-4567',
|
||||
placeholder_name: '張三',
|
||||
placeholder_email: 'example@example.com',
|
||||
placeholder_username: 'user123',
|
||||
placeholder_phone: '+886 2 1234 5678',
|
||||
unnamed: '未命名',
|
||||
search: '按姓名、電子郵件、電話或用戶名搜索',
|
||||
check_user_detail: '查看用戶詳情',
|
||||
|
|
|
@ -7,14 +7,14 @@ export {
|
|||
configurableConnectorMetadataGuard,
|
||||
type ConfigurableConnectorMetadata,
|
||||
} from '@logto/connector-kit';
|
||||
export type { JsonObject } from '@withtyped/server';
|
||||
export type { Json, JsonObject } from '@withtyped/server';
|
||||
|
||||
/* === Commonly Used === */
|
||||
|
||||
// Copied from https://github.com/colinhacks/zod#json-type
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
|
||||
const jsonGuard: z.ZodType<Json> = z.lazy(() =>
|
||||
export const jsonGuard: z.ZodType<Json> = z.lazy(() =>
|
||||
z.union([literalSchema, z.array(jsonGuard), z.record(jsonGuard)])
|
||||
);
|
||||
|
||||
|
|
|
@ -2936,6 +2936,9 @@ importers:
|
|||
ky:
|
||||
specifier: ^0.33.0
|
||||
version: 0.33.0
|
||||
libphonenumber-js:
|
||||
specifier: ^1.9.49
|
||||
version: 1.9.49
|
||||
lint-staged:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
|
|
Loading…
Reference in a new issue