fix: review ui split

This commit is contained in:
shuai
2024-03-01 18:08:08 +08:00
parent cf3cc8a827
commit cd8dd140c6
8 changed files with 444 additions and 60 deletions
+4 -1
View File
@@ -1161,6 +1161,7 @@ ui:
tag: Tag
post_lowercase: post
filter: Filter
ignore: Ignore
search:
title: Search Results
keywords: Keywords
@@ -1800,8 +1801,9 @@ ui:
edit_answer: Edit answer
edit_tag: Edit tag
empty: No review tasks left.
approve_tip: Do you approve this revision?
approve_this_type: Do you approve this {{ type }}?
suggest_type_edit: Suggest {{ type }} edit
flag_type: Flag {{ type }}
filter_label: Type
queued_post: Queued post
queued_user: Queued user
@@ -1809,6 +1811,7 @@ ui:
flagged_user: Flagged user
suggested_post_edit: Suggested post edit
suggested_tag_edit: Suggested tag edit
reputation: reputation
timeline:
undeleted: undeleted
deleted: deleted
@@ -0,0 +1,37 @@
import { useState } from 'react';
import { Dropdown, Button } from 'react-bootstrap';
import EditPostModal from '../EditPostModal';
const Index = () => {
const [showEditPostModal, setShowEditPostModal] = useState(false);
const handleEditPostModalState = () => {
setShowEditPostModal(!showEditPostModal);
};
return (
<div>
<Dropdown>
<Dropdown.Toggle
as={Button}
variant="outline-primary"
id="dropdown-basic">
Approve
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#/action-1">Deactivate user</Dropdown.Item>
<Dropdown.Item href="#/action-2">Suspend user</Dropdown.Item>
<Dropdown.Item href="#/action-3">Delete user</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<EditPostModal
visible={showEditPostModal}
handleClose={handleEditPostModalState}
/>
</div>
);
};
export default Index;
@@ -0,0 +1,4 @@
.edit-post-modal {
max-width: 746px;
width: 58.3333% !important;
}
@@ -0,0 +1,195 @@
import { FC, useState } from 'react';
import { Modal, Button, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { modifyQuestion } from '@/services';
import { useCaptchaModal } from '@/hooks';
import { Editor, TagSelector } from '@/components';
import { handleFormError } from '@/utils';
import type * as Type from '@/common/interface';
import './index.scss';
interface Props {
visible: boolean;
handleClose: () => void;
}
interface FormDataItem {
title: Type.FormValue<string>;
tags: Type.FormValue<Type.Tag[]>;
content: Type.FormValue<string>;
}
const Index: FC<Props> = ({ visible = false, handleClose }) => {
const initFormData = {
title: {
value: '',
isInvalid: false,
errorMsg: '',
},
tags: {
value: [],
isInvalid: false,
errorMsg: '',
},
content: {
value: '',
isInvalid: false,
errorMsg: '',
},
};
const { t } = useTranslation('translation', { keyPrefix: 'ask' });
const [formData, setFormData] = useState<FormDataItem>(initFormData);
const [focusEditor, setFocusEditor] = useState(false);
const editCaptcha = useCaptchaModal('edit');
const handleInput = (data: Partial<FormDataItem>) => {
setFormData({
...formData,
...data,
});
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();
const params: Type.QuestionParams = {
title: formData.title.value,
content: formData.content.value,
tags: formData.tags.value,
};
editCaptcha.check(() => {
const ep = {
...params,
id: '',
edit_summary: '',
};
const imgCode = editCaptcha.getCaptcha();
if (imgCode.verify) {
ep.captcha_code = imgCode.captcha_code;
ep.captcha_id = imgCode.captcha_id;
}
modifyQuestion(ep)
.then(async (res) => {
await editCaptcha.close();
console.log('res', res);
// navigate(pathFactory.questionLanding(qid, res?.url_title), {
// state: { isReview: res?.wait_for_review },
// });
})
.catch((err) => {
if (err.isError) {
editCaptcha.handleCaptchaError(err.list);
const data = handleFormError(err, formData);
setFormData({ ...data });
}
});
});
};
return (
<Modal
show={visible}
onHide={handleClose}
className="w-100"
dialogClassName="edit-post-modal">
<Modal.Header closeButton>
<Modal.Title>Edit post</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="title" className="mb-3">
<Form.Label>{t('form.fields.title.label')}</Form.Label>
<Form.Control
type="text"
value={formData.title.value}
isInvalid={formData.title.isInvalid}
onChange={(e) => {
handleInput({
title: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
});
}}
placeholder={t('form.fields.title.placeholder')}
autoFocus
contentEditable
/>
<Form.Control.Feedback type="invalid">
{formData.title.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="body">
<Form.Label>{t('form.fields.body.label')}</Form.Label>
<Form.Control
defaultValue={formData.content.value}
isInvalid={formData.content.isInvalid}
hidden
/>
<Editor
value={formData.content.value}
onChange={(value) => {
handleInput({
content: { value, errorMsg: '', isInvalid: false },
});
}}
className={classNames(
'form-control p-0',
focusEditor ? 'focus' : '',
)}
onFocus={() => {
setFocusEditor(true);
}}
onBlur={() => {
setFocusEditor(false);
}}
/>
<Form.Control.Feedback type="invalid">
{formData.content.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="tags" className="my-3">
<Form.Label>{t('form.fields.tags.label')}</Form.Label>
<Form.Control
defaultValue={JSON.stringify(formData.tags.value)}
isInvalid={formData.tags.isInvalid}
hidden
/>
<TagSelector
value={formData.tags.value}
onChange={(value) => {
handleInput({
tags: { value, errorMsg: '', isInvalid: false },
});
}}
showRequiredTag
maxTagLength={5}
/>
<Form.Control.Feedback type="invalid">
{formData.tags.errorMsg}
</Form.Control.Feedback>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
{t('close', { keyPrefix: 'btns' })}
</Button>
<Button variant="primary" onClick={handleClose}>
{t('submit', { keyPrefix: 'btns' })}
</Button>
</Modal.Footer>
</Modal>
);
};
export default Index;
@@ -0,0 +1,33 @@
import { useState } from 'react';
import { Card, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
const Index = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [checked, setValue] = useState(false);
return (
<Card>
<Card.Header>{t('filter', { keyPrefix: 'btns' })}</Card.Header>
<Card.Body>
<Form.Group>
<Form.Label>{t('filter_label')}</Form.Label>
<Form.Check
type="radio"
label="Queued post (99+)"
checked={checked}
onChange={(e) => setValue(e.target.checked)}
/>
<Form.Check
type="radio"
label="Queued post (199+)"
checked={checked}
onChange={(e) => setValue(e.target.checked)}
/>
</Form.Group>
</Card.Body>
</Card>
);
};
export default Index;
@@ -0,0 +1,103 @@
import { FC } from 'react';
import { Card, Badge } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { BaseUserCard, Tag, FormatTime, Avatar } from '@/components';
const tag = [
{
display_name: 'bug',
slug_name: 'bug',
original_text: '111',
recommend: true,
},
{
display_name: 'react',
slug_name: 'react',
original_text: '222',
reserved: true,
},
{
display_name: 'test',
slug_name: 'test',
original_text: '111',
recommend: false,
reserved: false,
},
];
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const objectType = 'question';
return (
<Card>
<Card.Header>{t('flag_type', { type: 'post' })}</Card.Header>
<Card.Body className="p-0">
<div className="p-3">
<h5 className="mb-3">
How do I test weather variable against multiple
</h5>
{objectType === 'question' && (
<div className="mb-4">
{tag?.map((item) => {
return (
<Tag key={item.slug_name} className="me-1" data={item} />
);
})}
</div>
)}
<div className="small font-monospace">
Python is a multi-paradigm, dynamically typed, multi-purpose
programming language. It is designed to be quick to learn,
understand, and use, and enforces a clean and uniform syntax. Please
note that Python 2 is officially out of support as of 2020-01-01.
For version-specific Python questions, add the [python-2.7] or
[python-3.x] tag. When using a Python variant library (e.g. Pandas,
NumPy), please include it in the tags.
</div>
<div className="d-flex align-items-center justify-content-between mt-4">
<Badge bg="success">normal</Badge>
<div className="d-flex align-items-center small">
<BaseUserCard
data={{
username: 'username',
display_name: 'username',
avatar: '',
reputation: 100,
}}
avatarSize="24"
/>
<FormatTime
time={1688107033}
className="text-secondary ms-1 flex-shrink-0"
preFix="answered"
/>
</div>
</div>
</div>
<div className="p-3 d-flex">
<Avatar
avatar=""
size="40"
searchStr="s=48"
alt=""
className="me-2"
/>
<div className="small">
<Link to="/test">
111 <span className="text-secondary">@111</span>
</Link>
<div className="mt-1">
I'm a web developer with in-depth experience in UI/UX design.
</div>
<div className="text-secondary mt-1">280 {t('reputation')}</div>
</div>
</div>
</Card.Body>
</Card>
);
};
export default Index;
+6
View File
@@ -0,0 +1,6 @@
import Filter from './Filter';
import ApproveDropdown from './ApproveDropdown';
import EditPostModal from './EditPostModal';
import FlagContent from './FlagContent';
export { Filter, ApproveDropdown, EditPostModal, FlagContent };
+62 -59
View File
@@ -18,7 +18,7 @@
*/
import { FC, useEffect, useState } from 'react';
import { Row, Col, Alert, Stack, Button, Card, Form } from 'react-bootstrap';
import { Row, Col, Alert, Stack, Button, Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -29,6 +29,8 @@ import { pathFactory } from '@/router/pathFactory';
import { scrollToDocTop } from '@/utils';
import type * as Type from '@/common/interface';
import { Filter, ApproveDropdown, FlagContent } from './components';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [isLoading, setIsLoading] = useState(false);
@@ -107,7 +109,6 @@ const Index: FC = () => {
setIsLoading(false);
});
};
let itemLink = '';
let itemId = '';
let editSummary = unreviewed_info?.reason;
@@ -138,6 +139,47 @@ const Index: FC = () => {
usePageTags({
title: t('review'),
});
let newData: Record<string, any> = {};
let oldData: Record<string, any> = {};
let diffOpts: Partial<{
showTitle: boolean;
showTagUrlSlug: boolean;
}> = {
showTitle: true,
showTagUrlSlug: true,
};
if (type === 'question' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
title: reviewInfo.title,
original_text: reviewInfo.content,
tags: reviewInfo.tags,
};
oldData = {
title: info.title,
original_text: info.content,
tags: info.tags,
};
}
if (type === 'answer' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
original_text: reviewInfo.content,
};
oldData = {
original_text: info.content,
};
}
if (type === 'tag' && info && reviewInfo) {
newData = {
original_text: reviewInfo.original_text,
};
oldData = {
original_text: info.content,
};
diffOpts = { showTitle: false, showTagUrlSlug: false };
}
return (
<Row className="pt-4 mb-5">
<h3 className="mb-4">{t('review')}</h3>
@@ -181,58 +223,18 @@ const Index: FC = () => {
#{itemId}
</Link>
</small>
{type === 'question' &&
info &&
reviewInfo &&
'content' in reviewInfo && (
<DiffContent
className="mt-2"
objectType={type}
oldData={{
title: info.title,
original_text: info.content,
tags: info.tags,
}}
newData={{
title: reviewInfo.title,
original_text: reviewInfo.content,
tags: reviewInfo.tags,
}}
/>
)}
{type === 'answer' &&
info &&
reviewInfo &&
'content' in reviewInfo && (
<DiffContent
className="mt-2"
objectType={type}
newData={{
original_text: reviewInfo.content,
}}
oldData={{
original_text: info.content,
}}
/>
)}
{type === 'tag' && info && reviewInfo && (
<DiffContent
className="mt-2"
objectType={type}
newData={{
original_text: reviewInfo.original_text,
}}
oldData={{
original_text: info.content,
}}
opts={{ showTitle: false, showTagUrlSlug: false }}
/>
)}
<DiffContent
className="mt-2"
objectType={type}
newData={newData}
oldData={oldData}
opts={diffOpts}
/>
</div>
</Card.Body>
<Card.Footer className="p-3">
<p>{t('approve_tip')}</p>
<p>{t('approve_this_type', { type: 'revision' })}</p>
<Stack direction="horizontal" gap={2}>
<Button
variant="outline-primary"
@@ -240,12 +242,19 @@ const Index: FC = () => {
onClick={handlingApprove}>
{t('approve', { keyPrefix: 'btns' })}
</Button>
<ApproveDropdown />
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingReject}>
{t('reject', { keyPrefix: 'btns' })}
</Button>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingReject}>
{t('ignore', { keyPrefix: 'btns' })}
</Button>
<Button
variant="outline-primary"
disabled={isLoading}
@@ -257,18 +266,12 @@ const Index: FC = () => {
</Card>
)}
{noTasks && <Empty>{t('empty')}</Empty>}
<FlagContent />
</Col>
<Col className="page-right-side mt-4 mt-xl-0">
<Card>
<Card.Header>{t('filter', { keyPrefix: 'btns' })}</Card.Header>
<Card.Body>
<Form.Group>
<Form.Label>{t('filter_label')}</Form.Label>
<Form.Check type="radio" label="Queued post (99+)" />
</Form.Group>
</Card.Body>
</Card>
<Filter />
</Col>
</Row>
);