mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
feat(console): user roles page (#2860)
This commit is contained in:
parent
385966625e
commit
ac01933c0f
13 changed files with 240 additions and 3 deletions
|
@ -45,6 +45,7 @@ import ApiResourceSettings from './pages/ApiResourceDetails/ApiResourceSettings'
|
|||
import RolePermissions from './pages/RoleDetails/RolePermissions';
|
||||
import RoleSettings from './pages/RoleDetails/RoleSettings';
|
||||
import UserLogs from './pages/UserDetails/UserLogs';
|
||||
import UserRoles from './pages/UserDetails/UserRoles';
|
||||
import UserSettings from './pages/UserDetails/UserSettings';
|
||||
import { getBasename } from './utilities/router';
|
||||
|
||||
|
@ -101,6 +102,7 @@ const Main = () => {
|
|||
<Route path=":id" element={<UserDetails />}>
|
||||
<Route index element={<Navigate replace to={UserDetailsTabs.Settings} />} />
|
||||
<Route path={UserDetailsTabs.Settings} element={<UserSettings />} />
|
||||
<Route path={UserDetailsTabs.Roles} element={<UserRoles />} />
|
||||
<Route path={UserDetailsTabs.Logs} element={<UserLogs />}>
|
||||
<Route path=":logId" element={<AuditLogDetails />} />
|
||||
</Route>
|
||||
|
|
|
@ -16,6 +16,7 @@ export enum SignInExperiencePage {
|
|||
|
||||
export enum UserDetailsTabs {
|
||||
Settings = 'settings',
|
||||
Roles = 'roles',
|
||||
Logs = 'logs',
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.rolesTable {
|
||||
margin-bottom: _.unit(6);
|
||||
color: var(--color-text);
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
tbody {
|
||||
td {
|
||||
max-width: 100%;
|
||||
@include _.text-ellipsis;
|
||||
font: var(--font-body-medium);
|
||||
}
|
||||
}
|
||||
}
|
138
packages/console/src/pages/UserDetails/UserRoles/index.tsx
Normal file
138
packages/console/src/pages/UserDetails/UserRoles/index.tsx
Normal file
|
@ -0,0 +1,138 @@
|
|||
import type { Role } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Delete from '@/assets/images/delete.svg';
|
||||
import Plus from '@/assets/images/plus.svg';
|
||||
import Button from '@/components/Button';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import IconButton from '@/components/IconButton';
|
||||
import Search from '@/components/Search';
|
||||
import Table from '@/components/Table';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
|
||||
import type { UserDetailsOutletContext } from '../types';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const UserRoles = () => {
|
||||
const {
|
||||
user: { id: userId },
|
||||
} = useOutletContext<UserDetailsOutletContext>();
|
||||
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { data: roles, error, mutate } = useSWR<Role[], RequestError>(`/api/users/${userId}/roles`);
|
||||
|
||||
const isLoading = !roles && !error;
|
||||
|
||||
const [roleToBeDeleted, setRoleToBeDeleted] = useState<Role>();
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
const api = useApi();
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!roleToBeDeleted || isDeleting) {
|
||||
return;
|
||||
}
|
||||
setIsDeleting(true);
|
||||
|
||||
try {
|
||||
await api.delete(`/api/users/${userId}/roles/${roleToBeDeleted.id}`);
|
||||
toast.success(t('user_details.roles.deleted', { name: roleToBeDeleted.name }));
|
||||
await mutate();
|
||||
setRoleToBeDeleted(undefined);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
className={styles.rolesTable}
|
||||
isLoading={isLoading}
|
||||
rowGroups={[{ key: 'roles', data: roles }]}
|
||||
rowIndexKey="id"
|
||||
columns={[
|
||||
{
|
||||
title: t('user_details.roles.name_column'),
|
||||
dataIndex: 'name',
|
||||
colSpan: 6,
|
||||
render: ({ id, name }) => (
|
||||
<TextLink to={`/roles/${id}`} target="_blank">
|
||||
{name}
|
||||
</TextLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('user_details.roles.name_column'),
|
||||
dataIndex: 'description',
|
||||
colSpan: 9,
|
||||
render: ({ description }) => description,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
dataIndex: 'delete',
|
||||
colSpan: 1,
|
||||
render: (role) => (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setRoleToBeDeleted(role);
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
]}
|
||||
filter={
|
||||
<div className={styles.filter}>
|
||||
<Search />
|
||||
<Button
|
||||
title="user_details.roles.assign_button"
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<Plus />}
|
||||
onClick={() => {
|
||||
// TODO @xiaoyijun assign roles to user
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
placeholder={{
|
||||
content: (
|
||||
<Button
|
||||
title="user_details.roles.assign_button"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
// TODO @xiaoyijun assign roles to user
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
{roleToBeDeleted && (
|
||||
<ConfirmModal
|
||||
isOpen
|
||||
isLoading={isDeleting}
|
||||
confirmButtonText="general.delete"
|
||||
onCancel={() => {
|
||||
setRoleToBeDeleted(undefined);
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
{t('user_details.roles.delete_description')}
|
||||
</ConfirmModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserRoles;
|
|
@ -32,7 +32,8 @@ import { UserDetailsOutletContext } from './types';
|
|||
|
||||
const UserDetails = () => {
|
||||
const { pathname } = useLocation();
|
||||
const isOnLogsPage = pathname.endsWith(UserDetailsTabs.Logs);
|
||||
const isPageHasTable =
|
||||
pathname.endsWith(UserDetailsTabs.Roles) || pathname.endsWith(UserDetailsTabs.Logs);
|
||||
const { id } = useParams();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
|
||||
|
@ -67,7 +68,7 @@ const UserDetails = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(detailsStyles.container, isOnLogsPage && styles.resourceLayout)}>
|
||||
<div className={classNames(detailsStyles.container, isPageHasTable && styles.resourceLayout)}>
|
||||
<TextLink to="/users" icon={<Back />} className={styles.backLink}>
|
||||
{t('user_details.back_to_users')}
|
||||
</TextLink>
|
||||
|
@ -151,7 +152,10 @@ const UserDetails = () => {
|
|||
</Card>
|
||||
<TabNav>
|
||||
<TabNavItem href={`/users/${data.id}/${UserDetailsTabs.Settings}`}>
|
||||
{t('general.settings_nav')}
|
||||
{t('user_details.tab_settings')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href={`/users/${data.id}/${UserDetailsTabs.Roles}`}>
|
||||
{t('user_details.tab_roles')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href={`/users/${data.id}/${UserDetailsTabs.Logs}`}>
|
||||
{t('user_details.tab_logs')}
|
||||
|
|
|
@ -16,6 +16,8 @@ const user_details = {
|
|||
congratulations: 'Der Benutzer wurde erfolgreich zurückgesetzt',
|
||||
new_password: 'Neues Passwort:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: 'Benutzer-Logs',
|
||||
settings: 'Settings', // UNTRANSLATED
|
||||
settings_description:
|
||||
|
@ -40,6 +42,13 @@ const user_details = {
|
|||
'Du entfernst die bestehende <name/> Identität. Bist du sicher, dass du das tun willst?',
|
||||
},
|
||||
suspended: 'Suspended', // UNTRANSLATED
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -14,6 +14,8 @@ const user_details = {
|
|||
congratulations: 'This user has been reset',
|
||||
new_password: 'New password:',
|
||||
},
|
||||
tab_settings: 'Settings',
|
||||
tab_roles: 'Roles',
|
||||
tab_logs: 'User logs',
|
||||
settings: 'Settings',
|
||||
settings_description:
|
||||
|
@ -38,6 +40,13 @@ const user_details = {
|
|||
'You are removing the existing <name/> identity. Are you sure you want to do that?',
|
||||
},
|
||||
suspended: 'Suspended',
|
||||
roles: {
|
||||
name_column: 'Role',
|
||||
description_column: 'Description',
|
||||
assign_button: 'Assign Roles',
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -16,6 +16,8 @@ const user_details = {
|
|||
congratulations: 'Cet utilisateur a été réinitialisé',
|
||||
new_password: 'Nouveau mot de passe :',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: "Journaux de l'utilisateur",
|
||||
settings: 'Settings', // UNTRANSLATED
|
||||
settings_description:
|
||||
|
@ -40,6 +42,13 @@ const user_details = {
|
|||
"Vous supprimez l'identité existante <nom/>. Etes-vous sûr de vouloir faire ça ?",
|
||||
},
|
||||
suspended: 'Suspended', // UNTRANSLATED
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -14,6 +14,8 @@ const user_details = {
|
|||
congratulations: '해당 사용자의 비밀번호가 성공적으로 초기화 되었어요.',
|
||||
new_password: '새로운 비밀번호:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: '사용자 기록',
|
||||
settings: '설정',
|
||||
settings_description:
|
||||
|
@ -37,6 +39,13 @@ const user_details = {
|
|||
deletion_confirmation: '<name/> 신원을 삭제하려고 해요. 정말로 진행할까요?',
|
||||
},
|
||||
suspended: '정지됨',
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -14,6 +14,8 @@ const user_details = {
|
|||
congratulations: 'Este usuário foi redefinido',
|
||||
new_password: 'Nova senha:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: 'Logs',
|
||||
settings: 'Configurações',
|
||||
settings_description:
|
||||
|
@ -38,6 +40,13 @@ const user_details = {
|
|||
'Você está removendo a identidade <name/> existente. Você tem certeza que deseja fazer isso?',
|
||||
},
|
||||
suspended: 'Suspenso',
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -16,6 +16,8 @@ const user_details = {
|
|||
congratulations: 'Este utilizador foi redefinido',
|
||||
new_password: 'Nova password:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: 'Registros do utilizador',
|
||||
settings: 'Settings', // UNTRANSLATED
|
||||
settings_description:
|
||||
|
@ -40,6 +42,13 @@ const user_details = {
|
|||
'Está removendo a identidade <name/> existente. Tem a certeza que deseja fazer isso?',
|
||||
},
|
||||
suspended: 'Suspended', // UNTRANSLATED
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -14,6 +14,8 @@ const user_details = {
|
|||
congratulations: 'Bu kullanıcı sıfırlandı',
|
||||
new_password: 'Yeni şifre:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: 'Kullanıcı kayıtları',
|
||||
settings: 'Settings', // UNTRANSLATED
|
||||
settings_description:
|
||||
|
@ -38,6 +40,13 @@ const user_details = {
|
|||
'Mevcut <name/> kimliğini kaldırıyorsunuz. Bunu yapmak istediğinizden emin misiniz?',
|
||||
},
|
||||
suspended: 'Suspended', // UNTRANSLATED
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
|
@ -14,6 +14,8 @@ const user_details = {
|
|||
congratulations: '该用户已被重置',
|
||||
new_password: '新密码:',
|
||||
},
|
||||
tab_settings: 'Settings', // UNTRANSLATED
|
||||
tab_roles: 'Roles', // UNTRANSLATED
|
||||
tab_logs: '用户日志',
|
||||
settings: 'Settings', // UNTRANSLATED
|
||||
settings_description:
|
||||
|
@ -36,6 +38,13 @@ const user_details = {
|
|||
deletion_confirmation: '你在正要删除现有的 <name /> 身份,是否确认?',
|
||||
},
|
||||
suspended: '已禁用',
|
||||
roles: {
|
||||
name_column: 'Role', // UNTRANSLATED
|
||||
description_column: 'Description', // UNTRANSLATED
|
||||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default user_details;
|
||||
|
|
Loading…
Add table
Reference in a new issue