From a4adf7f40dd759e118a8355637679ed59fc4950e Mon Sep 17 00:00:00 2001 From: shuai Date: Wed, 13 Sep 2023 09:52:31 +0800 Subject: [PATCH] feat: simplify adjustment of user activity status --- i18n/en_US.yaml | 16 ++ .../components/DeleteUserModal/index.tsx | 57 +++++ .../Users/components/UserOperate/index.tsx | 194 ++++++++++++++++++ ui/src/pages/Admin/Users/index.tsx | 164 +++++---------- ui/src/pages/Users/Personal/index.tsx | 3 +- 5 files changed, 322 insertions(+), 112 deletions(-) create mode 100644 ui/src/pages/Admin/Users/components/DeleteUserModal/index.tsx create mode 100644 ui/src/pages/Admin/Users/components/UserOperate/index.tsx diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 8ba8392c..6295fd9b 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1119,6 +1119,10 @@ ui: comment: Comment refresh: Refresh resend: Resend + deactivate: Deactivate + active: Active + suspend: Suspend + unsuspend: Unsuspend search: title: Search Results keywords: Keywords @@ -1498,6 +1502,18 @@ ui: change_role: Change role show_logs: Show logs add_user: Add user + deactivate_user: + title: Deactivate user + content: An inactive user must re-validate their email. + delete_user: + title: Delete this user + content: Are you sure you want to delete this user? This is permanent! + remove: Remove their content + label: Remove all questions, answers, comments, etc. + text: Don’t check this if you wish to only delete the user’s account. + suspend_user: + title: Suspend this user + content: A suspended user can't log in. questions: page_title: Questions normal: Normal diff --git a/ui/src/pages/Admin/Users/components/DeleteUserModal/index.tsx b/ui/src/pages/Admin/Users/components/DeleteUserModal/index.tsx new file mode 100644 index 00000000..fa0b30d0 --- /dev/null +++ b/ui/src/pages/Admin/Users/components/DeleteUserModal/index.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +const DeleteUserModal = ({ show, onClose, onDelete }) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); + const [checkVal, setCheckVal] = useState(false); + + const handleClose = () => { + onClose(); + setCheckVal(false); + }; + + return ( + + + {t('delete_user.title')} + + +

{t('delete_user.content')}

+
+ {t('delete_user.remove')} {t('optional', { keyPrefix: 'form' })} +
+
+ + + { + setCheckVal(e.target.checked); + }} + /> + + {t('delete_user.label')} +
+ + {t('delete_user.text')} + +
+
+
+
+
+ + + + +
+ ); +}; + +export default DeleteUserModal; diff --git a/ui/src/pages/Admin/Users/components/UserOperate/index.tsx b/ui/src/pages/Admin/Users/components/UserOperate/index.tsx new file mode 100644 index 00000000..3b0ec90e --- /dev/null +++ b/ui/src/pages/Admin/Users/components/UserOperate/index.tsx @@ -0,0 +1,194 @@ +import { Dropdown } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import { Modal, Icon } from '@/components'; +import { + useChangeUserRoleModal, + useChangePasswordModal, + useActivationEmailModal, + useToast, +} from '@/hooks'; +import { updateUserPassword, changeUserStatus } from '@/services'; + +interface Props { + showActionPassword?: boolean; + showActionStatus?: boolean; + showActionRole?: boolean; + currentUser; + refreshUsers: () => void; + showDeleteModal: (val) => void; + userData; +} + +const UserOperation = ({ + showActionPassword, + showActionStatus, + showActionRole, + currentUser, + refreshUsers, + showDeleteModal, + userData, +}: Props) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); + const Toast = useToast(); + + const changeUserRoleModal = useChangeUserRoleModal({ + callback: refreshUsers, + }); + const changePasswordModal = useChangePasswordModal({ + onConfirm: (rd) => { + return new Promise((resolve, reject) => { + updateUserPassword(rd) + .then(() => { + Toast.onShow({ + msg: t('update_password', { keyPrefix: 'toast' }), + variant: 'success', + }); + resolve(true); + }) + .catch((e) => { + reject(e); + }); + }); + }, + }); + + const activationEmailModal = useActivationEmailModal(); + + const postUserStatus = (statusType) => { + changeUserStatus({ + user_id: userData.user_id, + status: statusType, + }).then(() => { + refreshUsers?.(); + // onClose(); + }); + }; + + const handleAction = (type) => { + const { user_id, role_id, username } = userData; + if (username === currentUser.username) { + Toast.onShow({ + msg: t('forbidden_operate_self', { keyPrefix: 'toast' }), + variant: 'warning', + }); + return; + } + + if (type === 'role') { + changeUserRoleModal.onShow({ + id: user_id, + role_id, + }); + } + + if (type === 'password') { + changePasswordModal.onShow(user_id); + } + + if (type === 'activation') { + activationEmailModal.onShow(user_id); + } + + if (type === 'deactivate') { + // cons + Modal.confirm({ + title: t('deactivate_user.title'), + content: t('deactivate_user.content'), + cancelBtnVariant: 'link', + confirmBtnVariant: 'danger', + cancelText: t('cancel', { keyPrefix: 'btns' }), + confirmText: t('deactivate', { keyPrefix: 'btns' }), + onConfirm: () => { + // active -> inactive + postUserStatus('inactive'); + }, + }); + } + + if (type === 'suspend') { + // cons + Modal.confirm({ + title: t('suspend_user.title'), + content: t('suspend_user.content'), + cancelBtnVariant: 'link', + cancelText: t('cancel', { keyPrefix: 'btns' }), + confirmBtnVariant: 'danger', + confirmText: t('suspend', { keyPrefix: 'btns' }), + onConfirm: () => { + // active -> suspended + postUserStatus('suspended'); + }, + }); + } + + if (type === 'active' || type === 'unsuspend') { + // to normal + postUserStatus('normal'); + } + + if (type === 'delete') { + showDeleteModal({ + show: true, + userId: userData.user_id, + }); + } + }; + + return ( + + + + + + + {showActionPassword ? ( + handleAction('password')}> + {t('set_new_password')} + + ) : null} + {showActionRole ? ( + handleAction('role')}> + {t('change_role')} + + ) : null} + {userData.status === 'inactive' ? ( + handleAction('activation')}> + {t('btn_name', { keyPrefix: 'inactive' })} + + ) : null} + {showActionStatus && userData.status !== 'deleted' ? ( + <> + + {userData.status === 'inactive' && ( + handleAction('active')}> + {t('active', { keyPrefix: 'btns' })} + + )} + {userData.status === 'normal' && ( + handleAction('deactivate')}> + {t('deactivate', { keyPrefix: 'btns' })} + + )} + {userData.status === 'normal' && ( + handleAction('suspend')}> + {t('suspend', { keyPrefix: 'btns' })} + + )} + {userData.status === 'suspended' && ( + handleAction('unsuspend')}> + {t('unsuspend', { keyPrefix: 'btns' })} + + )} + handleAction('delete')}> + {t('delete', { keyPrefix: 'btns' })} + + + ) : null} + + + + ); +}; + +export default UserOperation; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index 70eaa545..51f5456a 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState } from 'react'; -import { Form, Table, Dropdown, Button, Stack } from 'react-bootstrap'; +import { Form, Table, Button, Stack } from 'react-bootstrap'; import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -11,27 +11,22 @@ import { BaseUserCard, Empty, QueryGroup, - Icon, } from '@/components'; import * as Type from '@/common/interface'; -import { - useUserModal, - useChangeModal, - useChangeUserRoleModal, - useChangePasswordModal, - useActivationEmailModal, - useToast, -} from '@/hooks'; +import { useUserModal } from '@/hooks'; import { useQueryUsers, addUsers, - updateUserPassword, getAdminUcAgent, AdminUcAgent, + changeUserStatus, } from '@/services'; import { loggedUserInfoStore, userCenterStore } from '@/stores'; import { formatCount } from '@/utils'; +import DeleteUserModal from './components/DeleteUserModal'; +import UserOperate from './components/UserOperate'; + const UserFilterKeys: Type.UserFilterBy[] = [ 'all', 'staff', @@ -50,7 +45,10 @@ const bgMap = { const PAGE_SIZE = 10; const Users: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); - + const [deleteUserModalState, setDeleteUserModalState] = useState({ + show: false, + userId: '', + }); const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const curFilter = urlSearchParams.get('filter') || UserFilterKeys[0]; const curPage = Number(urlSearchParams.get('page') || '1'); @@ -63,7 +61,7 @@ const Users: FC = () => { allow_update_user_password: true, allow_update_user_role: true, }); - const Toast = useToast(); + const { data, isLoading, @@ -78,13 +76,6 @@ const Users: FC = () => { ? { staff: true } : { status: curFilter }), }); - const changeModal = useChangeModal({ - callback: refreshUsers, - }); - - const changeUserRoleModal = useChangeUserRoleModal({ - callback: refreshUsers, - }); const userModal = useUserModal({ onConfirm: (userModel) => { @@ -102,58 +93,6 @@ const Users: FC = () => { }); }, }); - const changePasswordModal = useChangePasswordModal({ - onConfirm: (rd) => { - return new Promise((resolve, reject) => { - updateUserPassword(rd) - .then(() => { - Toast.onShow({ - msg: t('update_password', { keyPrefix: 'toast' }), - variant: 'success', - }); - resolve(true); - }) - .catch((e) => { - reject(e); - }); - }); - }, - }); - - const activationEmailModal = useActivationEmailModal(); - - const handleAction = (type, user) => { - const { user_id, status, role_id, username } = user; - if (username === currentUser.username) { - Toast.onShow({ - msg: t('forbidden_operate_self', { keyPrefix: 'toast' }), - variant: 'warning', - }); - return; - } - - if (type === 'status') { - changeModal.onShow({ - id: user_id, - type: status, - }); - } - - if (type === 'role') { - changeUserRoleModal.onShow({ - id: user_id, - role_id, - }); - } - - if (type === 'password') { - changePasswordModal.onShow(user_id); - } - - if (type === 'activation') { - activationEmailModal.onShow(user_id); - } - }; const handleFilter = (e) => { urlSearchParams.set('query', e.target.value); @@ -167,9 +106,30 @@ const Users: FC = () => { }); } }, [ucAgent]); + + const changeDeleteUserModalState = (modalData: { + show: boolean; + userId: string; + }) => { + setDeleteUserModalState(modalData); + }; + + const handleDelete = (val) => { + changeUserStatus({ + user_id: deleteUserModalState.userId, + status: 'deleted', + remove_all_content: val, + }).then(() => { + changeDeleteUserModalState({ + show: false, + userId: '', + }); + refreshUsers(); + }); + }; + const showAddUser = !ucAgent?.enabled || (ucAgent?.enabled && adminUcAgent?.allow_create_user); - const showActionPassword = !ucAgent?.enabled || (ucAgent?.enabled && adminUcAgent?.allow_update_user_password); @@ -181,7 +141,6 @@ const Users: FC = () => { const showActionStatus = !ucAgent?.enabled || (ucAgent?.enabled && adminUcAgent?.allow_update_user_status); - const showAction = showActionPassword || showActionRole || showActionStatus; return ( @@ -242,8 +201,6 @@ const Users: FC = () => { {data?.list.map((user) => { - const showActionActivation = user.status === 'inactive'; - return ( @@ -285,40 +242,16 @@ const Users: FC = () => { )} {curFilter !== 'deleted' && - (showAction || showActionActivation) ? ( - - - - - - - {showActionPassword ? ( - handleAction('password', user)}> - {t('set_new_password')} - - ) : null} - {showActionStatus ? ( - handleAction('status', user)}> - {t('change_status')} - - ) : null} - {showActionRole ? ( - handleAction('role', user)}> - {t('change_role')} - - ) : null} - {showActionActivation ? ( - handleAction('activation', user)}> - {t('btn_name', { keyPrefix: 'inactive' })} - - ) : null} - - - + (showAction || user.status === 'inactive') ? ( + ) : null} ); @@ -333,6 +266,17 @@ const Users: FC = () => { pageSize={PAGE_SIZE} /> + + { + changeDeleteUserModalState({ + show: false, + userId: '', + }); + }} + onDelete={(val) => handleDelete(val)} + /> ); }; diff --git a/ui/src/pages/Users/Personal/index.tsx b/ui/src/pages/Users/Personal/index.tsx index 29bbee36..700b7741 100644 --- a/ui/src/pages/Users/Personal/index.tsx +++ b/ui/src/pages/Users/Personal/index.tsx @@ -82,9 +82,8 @@ const Personal: FC = () => { )} - + -