From 8cda770ce92260152077b4bc994b4dcaaa0b252a Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 10 Jan 2023 14:02:17 +0800 Subject: [PATCH] feat(console): assign users to a role (#2883) --- .../RoleUsersTransfer/SourceUsersBox.tsx | 5 +- .../components/RoleUsersTransfer/index.tsx | 10 +- .../components/AssignUsersModal/index.tsx | 97 +++++++++++++++++++ .../src/pages/RoleDetails/RoleUsers/index.tsx | 17 +++- .../components/CreateRoleModal/index.tsx | 19 +++- .../de/translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../en/translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../fr/translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../ko/translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + .../translation/admin-console/general.ts | 1 + .../translation/admin-console/role-details.ts | 5 + 21 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 packages/console/src/pages/RoleDetails/RoleUsers/components/AssignUsersModal/index.tsx diff --git a/packages/console/src/components/RoleUsersTransfer/SourceUsersBox.tsx b/packages/console/src/components/RoleUsersTransfer/SourceUsersBox.tsx index 973d225f6..9cb858a43 100644 --- a/packages/console/src/components/RoleUsersTransfer/SourceUsersBox.tsx +++ b/packages/console/src/components/RoleUsersTransfer/SourceUsersBox.tsx @@ -16,6 +16,7 @@ import UserAvatar from '../UserAvatar'; import * as styles from './index.module.scss'; type Props = { + roleId: string; onAddUser: (user: User) => void; onRemoveUser: (user: User) => void; selectedUsers: User[]; @@ -24,7 +25,7 @@ type Props = { const pageSize = 20; const searchDelay = 500; -const SourceUsersBox = ({ selectedUsers, onAddUser, onRemoveUser }: Props) => { +const SourceUsersBox = ({ roleId, selectedUsers, onAddUser, onRemoveUser }: Props) => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const [pageIndex, setPageIndex] = useState(1); const [keyword, setKeyword] = useState(''); @@ -43,7 +44,7 @@ const SourceUsersBox = ({ selectedUsers, onAddUser, onRemoveUser }: Props) => { }, []); const { data } = useSWR<[User[], number], RequestError>( - `/api/users?page=${pageIndex}&page_size=${pageSize}&hideAdminUser=true${conditionalString( + `/api/users?excludeRoleId=${roleId}&page=${pageIndex}&page_size=${pageSize}&hideAdminUser=true${conditionalString( keyword && `&search=${encodeURIComponent(`%${keyword}%`)}` )}` ); diff --git a/packages/console/src/components/RoleUsersTransfer/index.tsx b/packages/console/src/components/RoleUsersTransfer/index.tsx index 8d169f141..93cb0d967 100644 --- a/packages/console/src/components/RoleUsersTransfer/index.tsx +++ b/packages/console/src/components/RoleUsersTransfer/index.tsx @@ -5,11 +5,12 @@ import TargetUsersBox from './TargetUsersBox'; import * as styles from './index.module.scss'; type Props = { + roleId: string; value: User[]; onChange: (value: User[]) => void; }; -const RoleUsersTransfer = ({ value, onChange }: Props) => { +const RoleUsersTransfer = ({ roleId, value, onChange }: Props) => { const onAddUser = (user: User) => { onChange([user, ...value]); }; @@ -20,7 +21,12 @@ const RoleUsersTransfer = ({ value, onChange }: Props) => { return (
- +
diff --git a/packages/console/src/pages/RoleDetails/RoleUsers/components/AssignUsersModal/index.tsx b/packages/console/src/pages/RoleDetails/RoleUsers/components/AssignUsersModal/index.tsx new file mode 100644 index 000000000..efcb9148a --- /dev/null +++ b/packages/console/src/pages/RoleDetails/RoleUsers/components/AssignUsersModal/index.tsx @@ -0,0 +1,97 @@ +import type { 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 FormField from '@/components/FormField'; +import ModalLayout from '@/components/ModalLayout'; +import RoleUsersTransfer from '@/components/RoleUsersTransfer'; +import useApi from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; + +type Props = { + roleId: string; + isRemindSkip?: boolean; + onClose: (success?: boolean) => void; +}; + +const AssignUsersModal = ({ roleId, isRemindSkip = false, onClose }: Props) => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const [isLoading, setIsLoading] = useState(false); + const [users, setUsers] = useState([]); + + const api = useApi(); + + const handleAssign = async () => { + if (users.length === 0) { + return; + } + + setIsLoading(true); + + try { + await api.post(`/api/roles/${roleId}/users`, { + json: { userIds: users.map(({ id }) => id) }, + }); + toast.success(t('role_details.users.users_assigned')); + onClose(true); + } finally { + setIsLoading(false); + } + }; + + return ( + { + onClose(); + }} + > + +
@@ -128,7 +130,7 @@ const RoleUsers = () => { title="role_details.users.assign_button" type="outline" onClick={() => { - // TODO @xiaoyijun assign users to role + setIsAssignModalOpen(true); }} /> ), @@ -147,6 +149,17 @@ const RoleUsers = () => { {t('role_details.users.delete_description')} )} + {isAssignModalOpen && ( + { + if (success) { + void mutate(); + } + setIsAssignModalOpen(false); + }} + /> + )} ); }; diff --git a/packages/console/src/pages/Roles/components/CreateRoleModal/index.tsx b/packages/console/src/pages/Roles/components/CreateRoleModal/index.tsx index e06200543..9d219a482 100644 --- a/packages/console/src/pages/Roles/components/CreateRoleModal/index.tsx +++ b/packages/console/src/pages/Roles/components/CreateRoleModal/index.tsx @@ -1,9 +1,11 @@ import type { Role } from '@logto/schemas'; +import { useState } from 'react'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import ReactModal from 'react-modal'; import { useNavigate } from 'react-router-dom'; +import AssignUsersModal from '@/pages/RoleDetails/RoleUsers/components/AssignUsersModal'; import * as modalStyles from '@/scss/modal.module.scss'; import type { Props as CreateRoleFormProps } from '../CreateRoleForm'; @@ -17,10 +19,11 @@ const CreateRoleModal = ({ onClose }: Props) => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const navigate = useNavigate(); + const [createdRole, setCreatedRole] = useState(); + const onCreateFormClose: CreateRoleFormProps['onClose'] = (createdRole?: Role) => { if (createdRole) { - // TODO @xiaoyijun open assigning role to users modal - navigate(`/roles/${createdRole.id}`, { replace: true }); + setCreatedRole(createdRole); toast.success(t('roles.role_created', { name: createdRole.name })); return; @@ -37,7 +40,17 @@ const CreateRoleModal = ({ onClose }: Props) => { overlayClassName={modalStyles.overlay} onRequestClose={onClose} > - + {createdRole ? ( + { + navigate(`/roles/${createdRole.id}`, { replace: true }); + }} + /> + ) : ( + + )} ); }; diff --git a/packages/phrases/src/locales/de/translation/admin-console/general.ts b/packages/phrases/src/locales/de/translation/admin-console/general.ts index 849423912..0f4c5e60f 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/general.ts @@ -42,6 +42,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} of {{total, number}}', // UNTRANSLATED learn_more: 'Learn more', // UNTRANSLATED tab_errors: '{{count, number}} errors', // UNTRANSLATED + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/de/translation/admin-console/role-details.ts b/packages/phrases/src/locales/de/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/en/translation/admin-console/general.ts b/packages/phrases/src/locales/en/translation/admin-console/general.ts index 809110051..f2a80eb22 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/general.ts @@ -41,6 +41,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} of {{total, number}}', learn_more: 'Learn more', tab_errors: '{{count, number}} errors', + skip_for_now: 'Skip for now', }; export default general; diff --git a/packages/phrases/src/locales/en/translation/admin-console/role-details.ts b/packages/phrases/src/locales/en/translation/admin-console/role-details.ts index 1cb0ba30f..857d0bf43 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', + assign_subtitle: 'Assign users to the role', + assign_users_field: 'Assign users', + confirm_assign: 'Assign users', + users_assigned: 'The selected users were successfully assigned to this role!', }, }; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/general.ts b/packages/phrases/src/locales/fr/translation/admin-console/general.ts index 584c7a2f9..1b2b213c9 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/general.ts @@ -42,6 +42,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} of {{total, number}}', // UNTRANSLATED learn_more: 'Learn more', // UNTRANSLATED tab_errors: '{{count, number}} errors', // UNTRANSLATED + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/role-details.ts b/packages/phrases/src/locales/fr/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/general.ts b/packages/phrases/src/locales/ko/translation/admin-console/general.ts index a0a0aecb4..a0746db57 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/general.ts @@ -41,6 +41,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} / {{total, number}}', learn_more: '더 알아보기', tab_errors: '{{count, number}} 오류', + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/role-details.ts b/packages/phrases/src/locales/ko/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts index a640d2e5f..1eafd774e 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts @@ -42,6 +42,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} de {{total, number}}', learn_more: 'Saber mais', tab_errors: '{{count, number}} erros', + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/role-details.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts index 43f1eefc0..50e5db538 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts @@ -41,6 +41,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} of {{total, number}}', // UNTRANSLATED learn_more: 'Learn more', // UNTRANSLATED tab_errors: '{{count, number}} errors', // UNTRANSLATED + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/role-details.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts index 14ae39c06..5b484782c 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts @@ -42,6 +42,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} of {{total, number}}', // UNTRANSLATED learn_more: 'Learn more', // UNTRANSLATED tab_errors: '{{count, number}} errors', // UNTRANSLATED + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/role-details.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/role-details.ts index ab5f2bc31..26ab14e2e 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, }; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts index 5f53bfeed..ae77d9fa8 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts @@ -41,6 +41,7 @@ const general = { page_info: '{{min, number}}-{{max, number}} 共 {{total, number}} 条', // UNTRANSLATED learn_more: 'Learn more', // UNTRANSLATED tab_errors: '{{count, number}} errors', // UNTRANSLATED + skip_for_now: 'Skip for now', // UNTRANSLATED }; export default general; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/role-details.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/role-details.ts index a54ff09f3..2e499ce35 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/role-details.ts @@ -35,6 +35,11 @@ const role_details = { delete_description: 'It will remain in your user pool but lose the authorization for this role.', // UNTRANSLATED deleted: 'The user {{name}} has been successfully deleted from this role.', // UNTRANSLATED + assign_title: 'Assign users', // UNTRANSLATED + assign_subtitle: 'Assign users to the role', // UNTRANSLATED + assign_users_field: 'Assign users', // UNTRANSLATED + confirm_assign: 'Assign users', // UNTRANSLATED + users_assigned: 'The selected users were successfully assigned to this role!', // UNTRANSLATED }, };