mirror of https://gitee.com/answerdev/answer.git
Merge pull request #524 from answerdev/feat/1.2.0/ui
feat: simplify adjustment of user activity status
This commit is contained in:
commit
927b68fb83
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
<Modal show={show} onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('delete_user.title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>{t('delete_user.content')}</p>
|
||||
<div className="text-danger mb-2">
|
||||
{t('delete_user.remove')} {t('optional', { keyPrefix: 'form' })}
|
||||
</div>
|
||||
<Form>
|
||||
<Form.Group controlId="delete_user">
|
||||
<Form.Check type="checkbox" id="delete_user">
|
||||
<Form.Check.Input
|
||||
type="checkbox"
|
||||
checked={checkVal}
|
||||
onChange={(e) => {
|
||||
setCheckVal(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<Form.Check.Label htmlFor="delete_user">
|
||||
<span>{t('delete_user.label')}</span>
|
||||
<br />
|
||||
<span className="small text-secondary">
|
||||
{t('delete_user.text')}
|
||||
</span>
|
||||
</Form.Check.Label>
|
||||
</Form.Check>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="link" onClick={handleClose}>
|
||||
{t('cancel', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
<Button variant="danger" onClick={() => onDelete(checkVal)}>
|
||||
{t('delete', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteUserModal;
|
|
@ -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 (
|
||||
<td className="text-end">
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="link" className="no-toggle">
|
||||
<Icon name="three-dots-vertical" title={t('action')} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{showActionPassword ? (
|
||||
<Dropdown.Item onClick={() => handleAction('password')}>
|
||||
{t('set_new_password')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{showActionRole ? (
|
||||
<Dropdown.Item onClick={() => handleAction('role')}>
|
||||
{t('change_role')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{userData.status === 'inactive' ? (
|
||||
<Dropdown.Item onClick={() => handleAction('activation')}>
|
||||
{t('btn_name', { keyPrefix: 'inactive' })}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{showActionStatus && userData.status !== 'deleted' ? (
|
||||
<>
|
||||
<Dropdown.Divider />
|
||||
{userData.status === 'inactive' && (
|
||||
<Dropdown.Item onClick={() => handleAction('active')}>
|
||||
{t('active', { keyPrefix: 'btns' })}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{userData.status === 'normal' && (
|
||||
<Dropdown.Item onClick={() => handleAction('deactivate')}>
|
||||
{t('deactivate', { keyPrefix: 'btns' })}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{userData.status === 'normal' && (
|
||||
<Dropdown.Item onClick={() => handleAction('suspend')}>
|
||||
{t('suspend', { keyPrefix: 'btns' })}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{userData.status === 'suspended' && (
|
||||
<Dropdown.Item onClick={() => handleAction('unsuspend')}>
|
||||
{t('unsuspend', { keyPrefix: 'btns' })}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item onClick={() => handleAction('delete')}>
|
||||
{t('delete', { keyPrefix: 'btns' })}
|
||||
</Dropdown.Item>
|
||||
</>
|
||||
) : null}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserOperation;
|
|
@ -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 = () => {
|
|||
</thead>
|
||||
<tbody className="align-middle">
|
||||
{data?.list.map((user) => {
|
||||
const showActionActivation = user.status === 'inactive';
|
||||
|
||||
return (
|
||||
<tr key={user.user_id}>
|
||||
<td>
|
||||
|
@ -285,40 +242,16 @@ const Users: FC = () => {
|
|||
</td>
|
||||
)}
|
||||
{curFilter !== 'deleted' &&
|
||||
(showAction || showActionActivation) ? (
|
||||
<td className="text-end">
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="link" className="no-toggle">
|
||||
<Icon name="three-dots-vertical" title={t('action')} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{showActionPassword ? (
|
||||
<Dropdown.Item
|
||||
onClick={() => handleAction('password', user)}>
|
||||
{t('set_new_password')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{showActionStatus ? (
|
||||
<Dropdown.Item
|
||||
onClick={() => handleAction('status', user)}>
|
||||
{t('change_status')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{showActionRole ? (
|
||||
<Dropdown.Item
|
||||
onClick={() => handleAction('role', user)}>
|
||||
{t('change_role')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{showActionActivation ? (
|
||||
<Dropdown.Item
|
||||
onClick={() => handleAction('activation', user)}>
|
||||
{t('btn_name', { keyPrefix: 'inactive' })}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</td>
|
||||
(showAction || user.status === 'inactive') ? (
|
||||
<UserOperate
|
||||
userData={user}
|
||||
showActionPassword={showActionPassword}
|
||||
showActionRole={showActionRole}
|
||||
showActionStatus={showActionStatus}
|
||||
currentUser={currentUser}
|
||||
refreshUsers={refreshUsers}
|
||||
showDeleteModal={changeDeleteUserModalState}
|
||||
/>
|
||||
) : null}
|
||||
</tr>
|
||||
);
|
||||
|
@ -333,6 +266,17 @@ const Users: FC = () => {
|
|||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DeleteUserModal
|
||||
show={deleteUserModalState.show}
|
||||
onClose={() => {
|
||||
changeDeleteUserModalState({
|
||||
show: false,
|
||||
userId: '',
|
||||
});
|
||||
}}
|
||||
onDelete={(val) => handleDelete(val)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -82,9 +82,8 @@ const Personal: FC = () => {
|
|||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
||||
<Row>
|
||||
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
||||
<Col className="page-main flex-auto">
|
||||
<Overview
|
||||
visible={tabName === 'overview'}
|
||||
|
|
Loading…
Reference in New Issue