mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
feat(console): assign roles to user (#2896)
This commit is contained in:
parent
d3b9d46b6b
commit
64d2fa5a63
23 changed files with 559 additions and 8 deletions
|
@ -1,17 +1,18 @@
|
|||
import type { AdminConsoleKey } from '@logto/phrases';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
|
||||
import Close from '@/assets/images/close.svg';
|
||||
|
||||
import Card from '../Card';
|
||||
import CardTitle from '../CardTitle';
|
||||
import type DangerousRaw from '../DangerousRaw';
|
||||
import IconButton from '../IconButton';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
title: AdminConsoleKey;
|
||||
subtitle?: AdminConsoleKey;
|
||||
title: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
|
||||
subtitle?: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
onClose?: () => void;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font: var(--font-body-medium);
|
||||
padding: _.unit(2.5) _.unit(4);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-hover);
|
||||
}
|
||||
|
||||
.name {
|
||||
@include _.text-ellipsis;
|
||||
}
|
||||
|
||||
.count {
|
||||
flex-shrink: 0;
|
||||
margin-left: _.unit(2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import type { RoleResponse } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import { onKeyDownHandler } from '@/utilities/a11y';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
role: RoleResponse;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
};
|
||||
|
||||
const SourceRoleItem = ({ role, isSelected, onSelect }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { name, usersCount } = role;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.item}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={onKeyDownHandler(() => {
|
||||
onSelect();
|
||||
})}
|
||||
onClick={() => {
|
||||
onSelect();
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={false}
|
||||
onChange={() => {
|
||||
onSelect();
|
||||
}}
|
||||
/>
|
||||
<div className={styles.name}>{name}</div>
|
||||
<div className={styles.count}>
|
||||
({t('user_details.roles.assigned_user_count', { value: usersCount })})
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SourceRoleItem;
|
|
@ -0,0 +1,7 @@
|
|||
.search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
import type { RoleResponse } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Search from '@/assets/images/search.svg';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import TextInput from '@/components/TextInput';
|
||||
import useDebounce from '@/hooks/use-debounce';
|
||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||
import { buildUrl } from '@/utilities/url';
|
||||
|
||||
import SourceRoleItem from '../SourceRoleItem';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
selectedRoles: RoleResponse[];
|
||||
onChange: (value: RoleResponse[]) => void;
|
||||
};
|
||||
|
||||
const pageSize = 20;
|
||||
const searchDelay = 500;
|
||||
|
||||
const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(1);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const debounce = useDebounce();
|
||||
|
||||
const url = buildUrl('/api/roles', {
|
||||
excludeUserId: userId,
|
||||
page: String(pageIndex),
|
||||
page_size: String(pageSize),
|
||||
...conditional(keyword && { search: `%${keyword}%` }),
|
||||
});
|
||||
|
||||
const { data } = useSWR<[RoleResponse[], number]>(url);
|
||||
|
||||
const [dataSource = [], totalCount] = data ?? [];
|
||||
|
||||
const isRoleSelected = (role: RoleResponse) =>
|
||||
selectedRoles.findIndex(({ id }) => role.id === id) >= 0;
|
||||
|
||||
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
debounce(() => {
|
||||
setPageIndex(1);
|
||||
setKeyword(event.target.value);
|
||||
}, searchDelay);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={transferLayout.box}>
|
||||
<div className={transferLayout.boxTopBar}>
|
||||
<TextInput
|
||||
className={styles.search}
|
||||
icon={<Search className={styles.icon} />}
|
||||
placeholder={t('general.search_placeholder')}
|
||||
onChange={handleSearchInput}
|
||||
/>
|
||||
</div>
|
||||
<div className={transferLayout.boxContent}>
|
||||
{dataSource.map((role) => {
|
||||
const isSelected = isRoleSelected(role);
|
||||
|
||||
return (
|
||||
<SourceRoleItem
|
||||
key={role.id}
|
||||
role={role}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => {
|
||||
onChange(
|
||||
isSelected
|
||||
? selectedRoles.filter(({ id }) => id !== role.id)
|
||||
: [role, ...selectedRoles]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Pagination
|
||||
mode="pico"
|
||||
pageIndex={pageIndex}
|
||||
totalCount={totalCount}
|
||||
pageSize={pageSize}
|
||||
className={transferLayout.boxPagination}
|
||||
onChange={(page) => {
|
||||
setPageIndex(page);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SourceRolesBox;
|
|
@ -0,0 +1,33 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: _.unit(2.5) _.unit(4);
|
||||
font: var(--font-body-medium);
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-hover);
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
@include _.text-ellipsis;
|
||||
}
|
||||
|
||||
.count {
|
||||
flex-shrink: 0;
|
||||
margin-left: _.unit(2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import type { RoleResponse } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Close from '@/assets/images/close.svg';
|
||||
import IconButton from '@/components/IconButton';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
role: RoleResponse;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
const TargetRoleItem = ({ role, onDelete }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { name, usersCount } = role;
|
||||
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.name}>{name}</div>
|
||||
<div className={styles.count}>
|
||||
({t('user_details.roles.assigned_user_count', { value: usersCount })})
|
||||
</div>
|
||||
</div>
|
||||
<IconButton size="small" iconClassName={styles.icon} onClick={onDelete}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TargetRoleItem;
|
|
@ -0,0 +1,7 @@
|
|||
.icon {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.added {
|
||||
font: var(--font-label-large);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import type { RoleResponse } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||
|
||||
import TargetRoleItem from '../TargetRoleItem';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
selectedRoles: RoleResponse[];
|
||||
onChange: (value: RoleResponse[]) => void;
|
||||
};
|
||||
|
||||
const TargetRolesBox = ({ selectedRoles, onChange }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<div className={transferLayout.box}>
|
||||
<div className={transferLayout.boxTopBar}>
|
||||
<span className={styles.added}>
|
||||
{`${selectedRoles.length} `}
|
||||
{t('general.added')}
|
||||
</span>
|
||||
</div>
|
||||
<div className={transferLayout.boxContent}>
|
||||
{selectedRoles.map((role) => (
|
||||
<TargetRoleItem
|
||||
key={role.id}
|
||||
role={role}
|
||||
onDelete={() => {
|
||||
onChange(selectedRoles.filter(({ id }) => id !== role.id));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TargetRolesBox;
|
|
@ -0,0 +1,3 @@
|
|||
.rolesTransfer {
|
||||
height: 360px;
|
||||
}
|
24
packages/console/src/components/UserRolesTransfer/index.tsx
Normal file
24
packages/console/src/components/UserRolesTransfer/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import type { RoleResponse } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||
|
||||
import SourceRolesBox from './components/SourceRolesBox';
|
||||
import TargetRolesBox from './components/TargetRolesBox';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
value: RoleResponse[];
|
||||
onChange: (value: RoleResponse[]) => void;
|
||||
};
|
||||
|
||||
const UserRolesTransfer = ({ userId, value, onChange }: Props) => (
|
||||
<div className={classNames(transferLayout.container, styles.rolesTransfer)}>
|
||||
<SourceRolesBox userId={userId} selectedRoles={value} onChange={onChange} />
|
||||
<div className={transferLayout.verticalBar} />
|
||||
<TargetRolesBox selectedRoles={value} onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default UserRolesTransfer;
|
28
packages/console/src/hooks/use-debounce.ts
Normal file
28
packages/console/src/hooks/use-debounce.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const useDebounce = () => {
|
||||
const timerRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const clearTimer = () => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimer();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (callback: () => void, wait: number) => {
|
||||
clearTimer();
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
timerRef.current = setTimeout(() => {
|
||||
callback();
|
||||
clearTimer();
|
||||
}, wait);
|
||||
};
|
||||
};
|
||||
|
||||
export default useDebounce;
|
|
@ -0,0 +1,90 @@
|
|||
import type { RoleResponse, User } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import DangerousRaw from '@/components/DangerousRaw';
|
||||
import ModalLayout from '@/components/ModalLayout';
|
||||
import UserRolesTransfer from '@/components/UserRolesTransfer';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
onClose: (success?: boolean) => void;
|
||||
};
|
||||
|
||||
const AssignRolesModal = ({ user, onClose }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const userName = user.name ?? t('users.unnamed');
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [roles, setRoles] = useState<RoleResponse[]>([]);
|
||||
|
||||
const api = useApi();
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (isSubmitting || roles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await api.post(`/api/users/${user.id}/roles`, {
|
||||
json: { roleIds: roles.map(({ id }) => id) },
|
||||
});
|
||||
toast.success(t('user_details.roles.role_assigned'));
|
||||
onClose(true);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen
|
||||
shouldCloseOnEsc
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
onRequestClose={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<ModalLayout
|
||||
title={
|
||||
<DangerousRaw>{t('user_details.roles.assign_title', { name: userName })}</DangerousRaw>
|
||||
}
|
||||
subtitle={
|
||||
<DangerousRaw>{t('user_details.roles.assign_subtitle', { name: userName })}</DangerousRaw>
|
||||
}
|
||||
size="large"
|
||||
footer={
|
||||
<Button
|
||||
isLoading={isSubmitting}
|
||||
disabled={roles.length === 0}
|
||||
htmlType="submit"
|
||||
title="user_details.roles.confirm_assign"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={handleAssign}
|
||||
/>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<UserRolesTransfer
|
||||
userId={user.id}
|
||||
value={roles}
|
||||
onChange={(value) => {
|
||||
setRoles(value);
|
||||
}}
|
||||
/>
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssignRolesModal;
|
|
@ -17,12 +17,12 @@ import type { RequestError } from '@/hooks/use-api';
|
|||
import useApi from '@/hooks/use-api';
|
||||
|
||||
import type { UserDetailsOutletContext } from '../types';
|
||||
import AssignRolesModal from './components/AssignRolesModal';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const UserRoles = () => {
|
||||
const {
|
||||
user: { id: userId },
|
||||
} = useOutletContext<UserDetailsOutletContext>();
|
||||
const { user } = useOutletContext<UserDetailsOutletContext>();
|
||||
const { id: userId } = user;
|
||||
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
|
@ -30,6 +30,7 @@ const UserRoles = () => {
|
|||
|
||||
const isLoading = !roles && !error;
|
||||
|
||||
const [isAssignRolesModalOpen, setIsAssignRolesModalOpen] = useState(false);
|
||||
const [roleToBeDeleted, setRoleToBeDeleted] = useState<Role>();
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
|
@ -99,7 +100,7 @@ const UserRoles = () => {
|
|||
size="large"
|
||||
icon={<Plus />}
|
||||
onClick={() => {
|
||||
// TODO @xiaoyijun assign roles to user
|
||||
setIsAssignRolesModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -110,7 +111,7 @@ const UserRoles = () => {
|
|||
title="user_details.roles.assign_button"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
// TODO @xiaoyijun assign roles to user
|
||||
setIsAssignRolesModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
@ -131,6 +132,17 @@ const UserRoles = () => {
|
|||
{t('user_details.roles.delete_description')}
|
||||
</ConfirmModal>
|
||||
)}
|
||||
{isAssignRolesModalOpen && (
|
||||
<AssignRolesModal
|
||||
user={user}
|
||||
onClose={(success) => {
|
||||
if (success) {
|
||||
void mutate();
|
||||
}
|
||||
setIsAssignRolesModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
39
packages/console/src/scss/transfer.module.scss
Normal file
39
packages/console/src/scss/transfer.module.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.box {
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.boxTopBar {
|
||||
height: 52px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 _.unit(4);
|
||||
}
|
||||
|
||||
.boxContent {
|
||||
flex: 1 1 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.boxPagination {
|
||||
height: 40px;
|
||||
padding-right: _.unit(4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.verticalBar {
|
||||
@include _.vertical-bar;
|
||||
}
|
|
@ -48,6 +48,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles',
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}',
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles',
|
||||
assign_role_field: 'Assign roles',
|
||||
role_search_placeholder: 'Search by role name',
|
||||
added_text: '{{value, number}} added',
|
||||
assigned_user_count: '{{value, number}} users',
|
||||
confirm_assign: 'Assign roles',
|
||||
role_assigned: 'Successfully assigned role(s)',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -48,6 +48,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -45,6 +45,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -48,6 +48,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -44,6 +44,14 @@ const user_details = {
|
|||
assign_button: 'Assign Roles', // UNTRANSLATED
|
||||
delete_description: 'TBD', // UNTRANSLATED
|
||||
deleted: 'The role {{name}} has been successfully deleted from the user.', // UNTRANSLATED
|
||||
assign_title: 'Assign roles to {{name}}', // UNTRANSLATED
|
||||
assign_subtitle: 'Authorize {{name}} one or more roles', // UNTRANSLATED
|
||||
assign_role_field: 'Assign roles', // UNTRANSLATED
|
||||
role_search_placeholder: 'Search by role name', // UNTRANSLATED
|
||||
added_text: '{{value, number}} added', // UNTRANSLATED
|
||||
assigned_user_count: '{{value, number}} users', // UNTRANSLATED
|
||||
confirm_assign: 'Assign roles', // UNTRANSLATED
|
||||
role_assigned: 'Successfully assigned role(s)', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue