Merge pull request #436 from answerdev/feat/1.1.1/ui

Feat/1.1.1/UI
This commit is contained in:
haitao.jarvis 2023-07-11 15:25:08 +08:00 committed by GitHub
commit d02c0a9a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 225 additions and 30 deletions

View File

@ -842,6 +842,9 @@ ui:
change_btn_name: Change email
msg:
empty: Cannot be empty.
resend_email:
url_label: Are you sure you want to resend the activation email?
url_text: You can also give the activation link above to the user.
login:
login_to_continue: Log in to continue
info_sign: Don't have an account? <1>Sign up</1>
@ -1000,6 +1003,7 @@ ui:
flag_success: Thanks for flagging.
forbidden_operate_self: Forbidden to operate on yourself
review: Your revision will show after review.
sent_success: Sent successfully
related_question:
title: Related Questions
answers: answers
@ -1094,6 +1098,7 @@ ui:
answer: Answer
comment: Comment
refresh: Refresh
resend: Resend
search:
title: Search Results
keywords: Keywords
@ -1405,6 +1410,31 @@ ui:
title: Change user role to...
btn_cancel: Cancel
btn_submit: Submit
new_password_modal:
title: Set new password
form:
fields:
password:
label: Password
text: The user will be logged out and need to login again.
msg: Password must be at 8-32 characters in length.
btn_cancel: Cancel
btn_submit: Submit
user_modal:
title: Add new user
form:
fields:
display_name:
label: Display Name
msg: Display Name must be at 3-30 characters in length.
email:
label: Email
msg: Email is not valid.
password:
label: Password
msg: Password must be at 8-32 characters in length.
btn_cancel: Cancel
btn_submit: Submit
users:
title: Users
name: Name
@ -1434,33 +1464,6 @@ ui:
change_role: Change role
show_logs: Show logs
add_user: Add user
new_password_modal:
title: Set new password
form:
fields:
password:
label: Password
text: The user will be logged out and need to login again.
msg: Password must be at 8-32 characters in length.
btn_cancel: Cancel
btn_submit: Submit
user_modal:
title: Add new user
form:
fields:
display_name:
label: Display Name
msg: Display Name must be at 3-30 characters in length.
email:
label: Email
msg: Email is not valid.
password:
label: Password
msg: Password must be at 8-32 characters in length.
btn_cancel: Cancel
btn_submit: Submit
questions:
page_title: Questions
normal: Normal

View File

@ -10,6 +10,7 @@ import useChangePasswordModal from './useChangePasswordModal';
import usePageTags from './usePageTags';
import useLoginRedirect from './useLoginRedirect';
import usePromptWithUnload from './usePrompt';
import useActivationEmailModal from './useActivationEmailModal';
export {
useTagModal,
@ -24,4 +25,5 @@ export {
usePageTags,
useLoginRedirect,
usePromptWithUnload,
useActivationEmailModal,
};

View File

@ -0,0 +1,149 @@
import { useLayoutEffect, useState, useRef } from 'react';
import { Modal, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import ReactDOM from 'react-dom/client';
import type * as Type from '@/common/interface';
import { SchemaForm, JSONSchema, UISchema, initFormData } from '@/components';
import { handleFormError } from '@/utils';
import { getUserActivation, postUserActivation } from '@/services';
import { useToast } from '@/hooks';
const div = document.createElement('div');
const root = ReactDOM.createRoot(div);
interface IProps {
title?: string;
onConfirm?: (formData: any) => Promise<any>;
}
const useChangePasswordModal = (props: IProps = {}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'inactive',
});
const { title = t('btn_name') } = props;
const [visible, setVisibleState] = useState(false);
const userId = useRef('');
const isLoading = useRef(false);
const Toast = useToast();
const schema: JSONSchema = {
title: t('btn_name'),
properties: {
activationUrl: {
type: 'string',
title: t('resend_email.url_label'),
description: t('resend_email.url_text'),
},
},
};
const uiSchema: UISchema = {
activationUrl: {
'ui:options': {
readOnly: true,
},
},
};
const [formData, setFormData] = useState<Type.FormDataType>(
initFormData(schema),
);
const formRef = useRef<{
validator: () => Promise<boolean>;
}>(null);
const getActivationUrl = () => {
return getUserActivation(userId.current).then((resp) => {
if (resp?.activation_url) {
setFormData({
...formData,
activationUrl: {
value: resp.activation_url,
isInvalid: false,
errorMsg: '',
},
});
}
});
};
const onClose = () => {
setVisibleState(false);
userId.current = '';
setFormData(initFormData(schema));
};
const onShow = async (user_id: string) => {
if (!user_id) {
return;
}
userId.current = user_id;
await getActivationUrl();
setVisibleState(true);
};
const handleSubmit = async (event) => {
event.preventDefault();
event.stopPropagation();
isLoading.current = true;
postUserActivation(userId.current)
.then(() => {
Toast.onShow({
msg: t('sent_success', { keyPrefix: 'toast' }),
variant: 'success',
});
onClose();
})
.catch((err) => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
}
})
.finally(() => {
isLoading.current = false;
});
};
const handleOnChange = (data) => {
setFormData(data);
};
useLayoutEffect(() => {
root.render(
<Modal show={visible} title={title} onHide={onClose}>
<Modal.Header closeButton>
<Modal.Title as="h5">{title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<SchemaForm
ref={formRef}
schema={schema}
uiSchema={uiSchema}
formData={formData}
onChange={handleOnChange}
hiddenSubmit
/>
</Modal.Body>
<Modal.Footer>
<Button variant="link" onClick={() => onClose()}>
{t('cancel', { keyPrefix: 'btns' })}
</Button>
<Button
disabled={isLoading.current}
variant="primary"
onClick={handleSubmit}>
{t('resend', { keyPrefix: 'btns' })}
</Button>
</Modal.Footer>
</Modal>,
);
});
return {
onClose,
onShow,
};
};
export default useChangePasswordModal;

View File

@ -17,7 +17,7 @@ interface IProps {
}
const useChangePasswordModal = (props: IProps = {}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'admin.users.new_password_modal',
keyPrefix: 'admin.new_password_modal',
});
const { title = t('title'), onConfirm } = props;

View File

@ -18,7 +18,7 @@ interface IProps {
}
const useAddUserModal = (props: IProps = {}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'admin.users.user_modal',
keyPrefix: 'admin.user_modal',
});
const { title = t('title'), onConfirm } = props;

View File

@ -19,6 +19,7 @@ import {
useChangeModal,
useChangeUserRoleModal,
useChangePasswordModal,
useActivationEmailModal,
useToast,
} from '@/hooks';
import {
@ -119,6 +120,8 @@ const Users: FC = () => {
},
});
const activationEmailModal = useActivationEmailModal();
const handleAction = (type, user) => {
const { user_id, status, role_id, username } = user;
if (username === currentUser.username) {
@ -128,6 +131,7 @@ const Users: FC = () => {
});
return;
}
if (type === 'status') {
changeModal.onShow({
id: user_id,
@ -141,9 +145,14 @@ const Users: FC = () => {
role_id,
});
}
if (type === 'password') {
changePasswordModal.onShow(user_id);
}
if (type === 'activation') {
activationEmailModal.onShow(user_id);
}
};
const handleFilter = (e) => {
@ -160,16 +169,21 @@ const Users: FC = () => {
}, [ucAgent]);
const showAddUser =
!ucAgent?.enabled || (ucAgent?.enabled && adminUcAgent?.allow_create_user);
const showActionPassword =
!ucAgent?.enabled ||
(ucAgent?.enabled && adminUcAgent?.allow_update_user_password);
const showActionRole =
!ucAgent?.enabled ||
(ucAgent?.enabled && adminUcAgent?.allow_update_user_role);
const showActionStatus =
!ucAgent?.enabled ||
(ucAgent?.enabled && adminUcAgent?.allow_update_user_status);
const showAction = showActionPassword || showActionRole || showActionStatus;
return (
<>
<h3 className="mb-4">{t('title')}</h3>
@ -228,6 +242,8 @@ const Users: FC = () => {
</thead>
<tbody className="align-middle">
{data?.list.map((user) => {
const showActionActivation = user.status === 'inactive';
return (
<tr key={user.user_id}>
<td>
@ -267,7 +283,8 @@ const Users: FC = () => {
</span>
</td>
)}
{curFilter !== 'deleted' && showAction ? (
{curFilter !== 'deleted' &&
(showAction || showActionActivation) ? (
<td className="text-end">
<Dropdown>
<Dropdown.Toggle variant="link" className="no-toggle">
@ -292,6 +309,12 @@ const Users: FC = () => {
{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>

View File

@ -44,3 +44,21 @@ export const updateUserPassword = (params: {
}) => {
return request.put('/answer/admin/api/user/password', params);
};
export const getUserActivation = (userId: string) => {
const apiUrl = `/answer/admin/api/user/activation`;
return request.get<{
activation_url: string;
}>(apiUrl, {
params: {
user_id: userId,
},
});
};
export const postUserActivation = (userId: string) => {
const apiUrl = `/answer/admin/api/user/activation`;
return request.post(apiUrl, {
user_id: userId,
});
};