mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
feat(console): organizations tab for user details
This commit is contained in:
parent
889ca18e66
commit
54fd771201
23 changed files with 223 additions and 0 deletions
|
@ -24,6 +24,7 @@ export enum UserDetailsTabs {
|
|||
Settings = 'settings',
|
||||
Roles = 'roles',
|
||||
Logs = 'logs',
|
||||
Organizations = 'organizations',
|
||||
}
|
||||
|
||||
export enum RoleDetailsTabs {
|
||||
|
|
|
@ -47,6 +47,7 @@ import TenantBasicSettings from '@/pages/TenantSettings/TenantBasicSettings';
|
|||
import TenantDomainSettings from '@/pages/TenantSettings/TenantDomainSettings';
|
||||
import UserDetails from '@/pages/UserDetails';
|
||||
import UserLogs from '@/pages/UserDetails/UserLogs';
|
||||
import UserOrganizations from '@/pages/UserDetails/UserOrganizations';
|
||||
import UserRoles from '@/pages/UserDetails/UserRoles';
|
||||
import UserSettings from '@/pages/UserDetails/UserSettings';
|
||||
import Users from '@/pages/Users';
|
||||
|
@ -130,6 +131,7 @@ function ConsoleContent() {
|
|||
<Route path={UserDetailsTabs.Settings} element={<UserSettings />} />
|
||||
<Route path={UserDetailsTabs.Roles} element={<UserRoles />} />
|
||||
<Route path={UserDetailsTabs.Logs} element={<UserLogs />} />
|
||||
<Route path={UserDetailsTabs.Organizations} element={<UserOrganizations />} />
|
||||
</Route>
|
||||
<Route
|
||||
path={`:userId/${UserDetailsTabs.Logs}/:logId`}
|
||||
|
|
|
@ -6,6 +6,7 @@ import useSWR from 'swr';
|
|||
import Plus from '@/assets/icons/plus.svg';
|
||||
import ActionsButton from '@/components/ActionsButton';
|
||||
import DateTime from '@/components/DateTime';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import UserPreview from '@/components/ItemPreview/UserPreview';
|
||||
import { defaultPageSize } from '@/consts';
|
||||
import Button from '@/ds-components/Button';
|
||||
|
@ -52,6 +53,7 @@ function Members({ organization }: Props) {
|
|||
return (
|
||||
<>
|
||||
<Table
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
pagination={{
|
||||
page,
|
||||
totalCount,
|
||||
|
|
|
@ -5,10 +5,12 @@ import { useTranslation } from 'react-i18next';
|
|||
import useSWR from 'swr';
|
||||
|
||||
import OrganizationIcon from '@/assets/icons/organization-preview.svg';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import ItemPreview from '@/components/ItemPreview';
|
||||
import ThemedIcon from '@/components/ThemedIcon';
|
||||
import { defaultPageSize } from '@/consts';
|
||||
import CopyToClipboard from '@/ds-components/CopyToClipboard';
|
||||
import Search from '@/ds-components/Search';
|
||||
import Table from '@/ds-components/Table';
|
||||
import { type RequestError } from '@/hooks/use-api';
|
||||
import AssignedEntities from '@/pages/Roles/components/AssignedEntities';
|
||||
|
@ -19,9 +21,11 @@ const pathname = '/organizations';
|
|||
const apiPathname = 'api/organizations';
|
||||
|
||||
function OrganizationsTable() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const { data: response, error } = useSWR<[OrganizationWithFeatured[], number], RequestError>(
|
||||
buildUrl(apiPathname, {
|
||||
q: keyword,
|
||||
showFeatured: '1',
|
||||
page: String(page),
|
||||
page_size: String(pageSize),
|
||||
|
@ -34,6 +38,7 @@ function OrganizationsTable() {
|
|||
return (
|
||||
<Table
|
||||
isLoading={isLoading}
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
rowGroups={[{ key: 'data', data }]}
|
||||
columns={[
|
||||
{
|
||||
|
@ -71,6 +76,21 @@ function OrganizationsTable() {
|
|||
pageSize,
|
||||
onChange: setPage,
|
||||
}}
|
||||
filter={
|
||||
<Search
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
placeholder={t('organization_details.search_user_placeholder')}
|
||||
onSearch={(value) => {
|
||||
setKeyword(value);
|
||||
setPage(1);
|
||||
}}
|
||||
onClearSearch={() => {
|
||||
setKeyword('');
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { type FieldValues, type FieldPath } from 'react-hook-form';
|
|||
|
||||
import CirclePlus from '@/assets/icons/circle-plus.svg';
|
||||
import Plus from '@/assets/icons/plus.svg';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import Button from '@/ds-components/Button';
|
||||
import Table from '@/ds-components/Table';
|
||||
import { type Column } from '@/ds-components/Table/types';
|
||||
|
@ -44,6 +45,7 @@ function TemplateTable<
|
|||
<>
|
||||
<Table
|
||||
hasBorder
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
isLoading={isLoading}
|
||||
className={styles.table}
|
||||
rowGroups={[
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.roles {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: _.unit(2);
|
||||
}
|
||||
|
||||
.rolesHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: _.unit(0.5);
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import { type OrganizationWithRoles } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import OrganizationIcon from '@/assets/icons/organization-preview.svg';
|
||||
import Tip from '@/assets/icons/tip.svg';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import ItemPreview from '@/components/ItemPreview';
|
||||
import ThemedIcon from '@/components/ThemedIcon';
|
||||
import CopyToClipboard from '@/ds-components/CopyToClipboard';
|
||||
import IconButton from '@/ds-components/IconButton';
|
||||
import Search from '@/ds-components/Search';
|
||||
import Table from '@/ds-components/Table';
|
||||
import Tag from '@/ds-components/Tag';
|
||||
import { ToggleTip } from '@/ds-components/Tip';
|
||||
import { type RequestError } from '@/hooks/use-api';
|
||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||
import { buildUrl } from '@/utils/url';
|
||||
|
||||
import { type UserDetailsOutletContext } from '../types';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
function UserOrganizations() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const {
|
||||
user: { id },
|
||||
} = useOutletContext<UserDetailsOutletContext>();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getPathname } = useTenantPathname();
|
||||
|
||||
// Since this API has no pagination (to align with ID token claims):
|
||||
// - We don't need to use the `page` state.
|
||||
// - We can perform frontend filtering.
|
||||
const { data: rawData, error } = useSWR<OrganizationWithRoles[], RequestError>(
|
||||
buildUrl(`api/users/${id}/organizations`, { showFeatured: '1' })
|
||||
);
|
||||
const isLoading = !rawData && !error;
|
||||
const data = rawData?.filter(({ name }) => name.toLowerCase().includes(keyword.toLowerCase()));
|
||||
|
||||
return (
|
||||
<Table
|
||||
isLoading={isLoading}
|
||||
rowIndexKey="id"
|
||||
rowGroups={[{ key: 'data', data }]}
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
columns={[
|
||||
{
|
||||
title: t('general.name'),
|
||||
dataIndex: 'name',
|
||||
render: ({ name, id }) => (
|
||||
<ItemPreview
|
||||
title={name}
|
||||
icon={<ThemedIcon for={OrganizationIcon} />}
|
||||
to={getPathname(`/organizations/${id}`)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('organizations.organization_id'),
|
||||
dataIndex: 'id',
|
||||
render: ({ id }) => <CopyToClipboard value={id} variant="text" />,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div className={styles.rolesHeader}>
|
||||
{t('organizations.organization_role_other')}
|
||||
<ToggleTip
|
||||
content={t('user_details.organization_roles_tooltip')}
|
||||
horizontalAlign="start"
|
||||
>
|
||||
<IconButton size="small">
|
||||
<Tip />
|
||||
</IconButton>
|
||||
</ToggleTip>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'roles',
|
||||
render: ({ organizationRoles }) => (
|
||||
<div className={styles.roles}>
|
||||
{organizationRoles.map(({ id, name }) => (
|
||||
<Tag key={id} variant="cell">
|
||||
{name}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
filter={
|
||||
<Search
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
placeholder={t('organization_details.search_user_placeholder')}
|
||||
onSearch={setKeyword}
|
||||
onClearSearch={() => {
|
||||
setKeyword('');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserOrganizations;
|
|
@ -222,6 +222,9 @@ function UserDetails() {
|
|||
<TabNavItem href={`/users/${data.id}/${UserDetailsTabs.Logs}`}>
|
||||
{t('user_details.tab_logs')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href={`/users/${data.id}/${UserDetailsTabs.Organizations}`}>
|
||||
{t('user_details.tab_organizations')}
|
||||
</TabNavItem>
|
||||
</TabNav>
|
||||
<Outlet
|
||||
context={
|
||||
|
|
|
@ -23,6 +23,8 @@ const user_details = {
|
|||
tab_roles: 'Rollen',
|
||||
tab_logs: 'Benutzer-Logs',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Jeder Benutzer hat ein Profil mit allen Benutzerinformationen. Es besteht aus Basisdaten, sozialen Identitäten und benutzerdefinierten Daten.',
|
||||
|
@ -103,6 +105,9 @@ const user_details = {
|
|||
},
|
||||
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?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -20,6 +20,7 @@ const user_details = {
|
|||
tab_settings: 'Settings',
|
||||
tab_roles: 'Roles',
|
||||
tab_logs: 'User logs',
|
||||
tab_organizations: 'Organizations',
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Each user has a profile containing all user information. It consists of basic data, social identities, and custom data.',
|
||||
|
@ -92,6 +93,8 @@ const user_details = {
|
|||
},
|
||||
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?',
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -23,6 +23,8 @@ const user_details = {
|
|||
tab_roles: 'Roles',
|
||||
tab_logs: 'Registros de usuario',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Cada usuario tiene un perfil que contiene toda la información del usuario. Consta de datos básicos, identidades sociales y datos personalizados.',
|
||||
|
@ -102,6 +104,9 @@ const user_details = {
|
|||
},
|
||||
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?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -23,6 +23,8 @@ const user_details = {
|
|||
tab_roles: 'Rôles',
|
||||
tab_logs: "Journaux de l'utilisateur",
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
"Chaque utilisateur possède un profil contenant toutes les informations le concernant. Il se compose de données de base, d'identités sociales et de données personnalisées.",
|
||||
|
@ -103,6 +105,9 @@ const user_details = {
|
|||
},
|
||||
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?",
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -23,6 +23,8 @@ const user_details = {
|
|||
tab_roles: 'Ruoli',
|
||||
tab_logs: 'Log utente',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
"Ogni utente ha un profilo contenente tutte le informazioni dell'utente. È composto da dati di base, identità sociali e dati personalizzati.",
|
||||
|
@ -103,6 +105,9 @@ const user_details = {
|
|||
},
|
||||
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?",
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: '役割',
|
||||
tab_logs: 'ユーザーログ',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'各ユーザーには、すべてのユーザー情報が含まれるプロファイルがあります。基本データ、ソーシャルアイデンティティ、およびカスタムデータで構成されています。',
|
||||
|
@ -99,6 +101,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'ユーザーは、サインインに少なくとも1つの識別子(ユーザー名、メールアドレス、電話番号、またはソーシャル)を持っている必要があります。続行してよろしいですか?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: '역할',
|
||||
tab_logs: '사용자 기록',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'각 사용자는 모든 사용자 정보를 포함하는 프로파일을 가지고 있어요. 프로파일은 기본 데이터, 소셜 ID, 사용자 정의 데이터로 구성되어 있어요.',
|
||||
|
@ -99,6 +101,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'사용자는 로그인 식별자(사용자 이름, 이메일, 전화 번호 또는 소셜) 중 적어도 하나를 갖고 로그인해야 합니다. 계속 하시겠습니까?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: 'Role',
|
||||
tab_logs: 'Logi użytkownika',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Każdy użytkownik ma profil zawierający wszystkie informacje o użytkowniku. Składa się on z podstawowych danych, tożsamości społecznościowych i niestandardowych danych.',
|
||||
|
@ -99,6 +101,9 @@ const user_details = {
|
|||
},
|
||||
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ć?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: 'Funções',
|
||||
tab_logs: 'Registros',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Cada usuário tem um perfil contendo todas as informações do usuário. Consiste em dados básicos, identidades sociais e dados personalizados.',
|
||||
|
@ -100,6 +102,9 @@ const user_details = {
|
|||
},
|
||||
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?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -23,6 +23,8 @@ const user_details = {
|
|||
tab_roles: 'Funções',
|
||||
tab_logs: 'Registos do utilizador',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Cada utilizador tem um perfil que contém todas as informações do utilizador. Consiste em dados básicos, identidades sociais e dados personalizados.',
|
||||
|
@ -102,6 +104,9 @@ const user_details = {
|
|||
},
|
||||
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 redes sociais) para iniciar sessão. Tem a certeza de que quer continuar?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: 'Роли',
|
||||
tab_logs: 'Журналы пользователя',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'У каждого пользователя есть профиль, содержащий всю информацию о пользователе. Он состоит из основных данных, социальных идентификаторов и пользовательских данных.',
|
||||
|
@ -100,6 +102,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'Пользователь должен иметь хотя бы один из идентификаторов входа (имя пользователя, электронная почта, номер телефона или социальная сеть), чтобы войти. Вы уверены, что хотите продолжить?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: 'Roller',
|
||||
tab_logs: 'Kullanıcı kayıtları',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'Her kullanıcının, temel veriler, sosyal kimlikler ve özel verilerden oluşan tüm kullanıcı bilgilerini içeren bir profil vardır.',
|
||||
|
@ -101,6 +103,9 @@ const user_details = {
|
|||
},
|
||||
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?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: '角色',
|
||||
tab_logs: '用户日志',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'每个用户都有一个包含所有用户信息的个人资料。它由基本数据、社交身份和自定义数据组成。',
|
||||
|
@ -95,6 +97,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'用户需要至少拥有一个登录标识(用户名、邮箱、手机号或社交账户)才能登录。确定要继续吗?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: '角色',
|
||||
tab_logs: '用戶日誌',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'每個用戶都有一個包含所有用戶信息的個人資料。它由基本數據、社交身份和自定義數據組成。',
|
||||
|
@ -95,6 +97,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'用戶需要至少擁有一個登錄標識(用戶名、電子郵件、電話號碼或社交帳號)才能登錄。確定要繼續嗎?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
|
@ -21,6 +21,8 @@ const user_details = {
|
|||
tab_roles: '角色',
|
||||
tab_logs: '用戶日誌',
|
||||
/** UNTRANSLATED */
|
||||
tab_organizations: 'Organizations',
|
||||
/** UNTRANSLATED */
|
||||
authentication: 'Authentication',
|
||||
authentication_description:
|
||||
'每個用戶都有一個包含所有用戶資訊的個人資料。它由基本數據、社交身份和自定義數據組成。',
|
||||
|
@ -95,6 +97,9 @@ const user_details = {
|
|||
},
|
||||
warning_no_sign_in_identifier:
|
||||
'使用者需要至少擁有一個登入標識(使用者名稱、電子郵件、電話號碼或社交帳號)才能登入。確定要繼續嗎?',
|
||||
/** UNTRANSLATED */
|
||||
organization_roles_tooltip:
|
||||
'Organization roles assigned to the current user in this organization.',
|
||||
};
|
||||
|
||||
export default Object.freeze(user_details);
|
||||
|
|
Loading…
Add table
Reference in a new issue