mirror of https://gitee.com/answerdev/answer.git
feat: Complete **SPAM** blocking of critical data apis.
This commit is contained in:
parent
c9921b9284
commit
fa54cbc0d1
|
@ -9,6 +9,11 @@ export interface FormDataType {
|
|||
[prop: string]: FormValue;
|
||||
}
|
||||
|
||||
export interface FieldError {
|
||||
error_field: string;
|
||||
error_msg: string;
|
||||
}
|
||||
|
||||
export interface Paging {
|
||||
page: number;
|
||||
page_size?: number;
|
||||
|
@ -52,7 +57,7 @@ export interface TagInfo extends TagBase {
|
|||
main_tag_slug_name?: string;
|
||||
excerpt?;
|
||||
}
|
||||
export interface QuestionParams {
|
||||
export interface QuestionParams extends ImgCodeReq{
|
||||
title: string;
|
||||
url_title?: string;
|
||||
content: string;
|
||||
|
@ -68,7 +73,7 @@ export interface ListResult<T = any> {
|
|||
list: T[];
|
||||
}
|
||||
|
||||
export interface AnswerParams {
|
||||
export interface AnswerParams extends ImgCodeReq {
|
||||
content: string;
|
||||
html: string;
|
||||
question_id: string;
|
||||
|
@ -169,10 +174,29 @@ export interface PasswordResetReq extends ImgCodeReq {
|
|||
e_mail: string;
|
||||
}
|
||||
|
||||
export interface CheckImgReq {
|
||||
action: 'login' | 'e_mail' | 'find_pass' | 'modify_pass';
|
||||
export interface PasswordReplaceReq extends ImgCodeReq {
|
||||
code: string;
|
||||
pass: string;
|
||||
}
|
||||
|
||||
export interface CaptchaReq extends ImgCodeReq {
|
||||
verify: ImgCodeRes['verify'];
|
||||
}
|
||||
|
||||
export type CaptchaKey =
|
||||
| 'email'
|
||||
| 'password'
|
||||
| 'edit_userinfo'
|
||||
| 'question'
|
||||
| 'answer'
|
||||
| 'comment'
|
||||
| 'edit'
|
||||
| 'invitation_answer'
|
||||
| 'search'
|
||||
| 'report'
|
||||
| 'delete'
|
||||
| 'vote';
|
||||
|
||||
export interface SetNoticeReq {
|
||||
notice_switch: boolean;
|
||||
}
|
||||
|
@ -222,7 +246,7 @@ export interface AnswerItem {
|
|||
[prop: string]: any;
|
||||
}
|
||||
|
||||
export interface PostAnswerReq {
|
||||
export interface PostAnswerReq extends ImgCodeReq {
|
||||
content: string;
|
||||
html?: string;
|
||||
question_id: string;
|
||||
|
@ -425,7 +449,7 @@ export interface FollowParams {
|
|||
/**
|
||||
* @description search request params
|
||||
*/
|
||||
export interface SearchParams {
|
||||
export interface SearchParams extends ImgCodeReq {
|
||||
q: string;
|
||||
order: string;
|
||||
page: number;
|
||||
|
|
|
@ -6,9 +6,10 @@ import classNames from 'classnames';
|
|||
|
||||
import { Icon } from '@/components';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { useToast } from '@/hooks';
|
||||
import { useToast, useCaptchaModal } from '@/hooks';
|
||||
import { tryNormalLogged } from '@/utils/guard';
|
||||
import { bookmark, postVote } from '@/services';
|
||||
import * as Types from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
@ -36,6 +37,8 @@ const Index: FC<Props> = ({ className, data, source }) => {
|
|||
const { username = '' } = loggedUserInfoStore((state) => state.user);
|
||||
const toast = useToast();
|
||||
const { t } = useTranslation();
|
||||
const vCaptcha = useCaptchaModal('vote');
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setVotes(data.votesCount);
|
||||
|
@ -61,27 +64,39 @@ const Index: FC<Props> = ({ className, data, source }) => {
|
|||
return;
|
||||
}
|
||||
const isCancel = (type === 'up' && like) || (type === 'down' && hate);
|
||||
postVote(
|
||||
{
|
||||
object_id: data?.id,
|
||||
is_cancel: isCancel,
|
||||
},
|
||||
type,
|
||||
)
|
||||
.then((res) => {
|
||||
setVotes(res.votes);
|
||||
setLike(res.vote_status === 'vote_up');
|
||||
setHated(res.vote_status === 'vote_down');
|
||||
})
|
||||
.catch((err) => {
|
||||
const errMsg = err?.value;
|
||||
if (errMsg) {
|
||||
toast.onShow({
|
||||
msg: errMsg,
|
||||
variant: 'danger',
|
||||
});
|
||||
}
|
||||
});
|
||||
vCaptcha.check(() => {
|
||||
const imgCode: Types.ImgCodeReq = {
|
||||
captcha_id: undefined,
|
||||
captcha_code: undefined,
|
||||
};
|
||||
vCaptcha.resolveCaptchaReq(imgCode);
|
||||
postVote(
|
||||
{
|
||||
object_id: data?.id,
|
||||
is_cancel: isCancel,
|
||||
...imgCode,
|
||||
},
|
||||
type,
|
||||
)
|
||||
.then(async (res) => {
|
||||
await vCaptcha.close();
|
||||
setVotes(res.votes);
|
||||
setLike(res.vote_status === 'vote_up');
|
||||
setHated(res.vote_status === 'vote_down');
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.isError) {
|
||||
vCaptcha.handleCaptchaError(err.list);
|
||||
}
|
||||
const errMsg = err?.value;
|
||||
if (errMsg) {
|
||||
toast.onShow({
|
||||
msg: errMsg,
|
||||
variant: 'danger',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleBookmark = () => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { unionBy } from 'lodash';
|
|||
|
||||
import * as Types from '@/common/interface';
|
||||
import { Modal } from '@/components';
|
||||
import { usePageUsers, useReportModal } from '@/hooks';
|
||||
import { usePageUsers, useReportModal, useCaptchaModal } from '@/hooks';
|
||||
import {
|
||||
matchedUsers,
|
||||
parseUserInfo,
|
||||
|
@ -43,6 +43,11 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
|
||||
const reportModal = useReportModal();
|
||||
|
||||
const addCaptcha = useCaptchaModal('comment');
|
||||
const editCaptcha = useCaptchaModal('edit');
|
||||
const dCaptcha = useCaptchaModal('delete');
|
||||
const vCaptcha = useCaptchaModal('vote');
|
||||
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||
const scrollCallback = useCallback((el, co) => {
|
||||
if (pageIndex === 0 && co.comment_id === commentId) {
|
||||
|
@ -120,43 +125,67 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
};
|
||||
|
||||
if (item.type === 'edit') {
|
||||
return updateComment({
|
||||
...params,
|
||||
comment_id: item.comment_id,
|
||||
}).then((res) => {
|
||||
setComments(
|
||||
comments.map((comment) => {
|
||||
if (comment.comment_id === item.comment_id) {
|
||||
comment.showEdit = false;
|
||||
comment.parsed_text = res.parsed_text;
|
||||
comment.original_text = res.original_text;
|
||||
return editCaptcha.check(() => {
|
||||
const up = {
|
||||
...params,
|
||||
comment_id: item.comment_id,
|
||||
captcha_code: undefined,
|
||||
captcha_id: undefined,
|
||||
};
|
||||
editCaptcha.resolveCaptchaReq(up);
|
||||
|
||||
return updateComment(up)
|
||||
.then(async (res) => {
|
||||
await editCaptcha.close();
|
||||
setComments(
|
||||
comments.map((comment) => {
|
||||
if (comment.comment_id === item.comment_id) {
|
||||
comment.showEdit = false;
|
||||
comment.parsed_text = res.parsed_text;
|
||||
comment.original_text = res.original_text;
|
||||
}
|
||||
return comment;
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
editCaptcha.handleCaptchaError(err.list);
|
||||
}
|
||||
return comment;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
return addComment(params).then((res) => {
|
||||
if (item.type === 'reply') {
|
||||
const index = comments.findIndex(
|
||||
(comment) => comment.comment_id === item.comment_id,
|
||||
);
|
||||
comments[index].showReply = false;
|
||||
comments.splice(index + 1, 0, res);
|
||||
setComments([...comments]);
|
||||
} else {
|
||||
setComments([
|
||||
...comments.map((comment) => {
|
||||
if (comment.comment_id === item.comment_id) {
|
||||
comment.showReply = false;
|
||||
}
|
||||
return comment;
|
||||
}),
|
||||
res,
|
||||
]);
|
||||
}
|
||||
|
||||
setVisibleComment(false);
|
||||
return addCaptcha.check(() => {
|
||||
const req = {
|
||||
...params,
|
||||
captcha_code: undefined,
|
||||
captcha_id: undefined,
|
||||
};
|
||||
addCaptcha.resolveCaptchaReq(req);
|
||||
|
||||
return addComment(req).then((res) => {
|
||||
if (item.type === 'reply') {
|
||||
const index = comments.findIndex(
|
||||
(comment) => comment.comment_id === item.comment_id,
|
||||
);
|
||||
comments[index].showReply = false;
|
||||
comments.splice(index + 1, 0, res);
|
||||
setComments([...comments]);
|
||||
} else {
|
||||
setComments([
|
||||
...comments.map((comment) => {
|
||||
if (comment.comment_id === item.comment_id) {
|
||||
comment.showReply = false;
|
||||
}
|
||||
return comment;
|
||||
}),
|
||||
res,
|
||||
]);
|
||||
}
|
||||
|
||||
setVisibleComment(false);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -167,11 +196,23 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
confirmBtnVariant: 'danger',
|
||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||
onConfirm: () => {
|
||||
deleteComment(id).then(() => {
|
||||
if (pageIndex === 0) {
|
||||
mutate();
|
||||
}
|
||||
setComments(comments.filter((item) => item.comment_id !== id));
|
||||
dCaptcha.check(() => {
|
||||
const imgCode = { captcha_id: undefined, captcha_code: undefined };
|
||||
dCaptcha.resolveCaptchaReq(imgCode);
|
||||
|
||||
deleteComment(id, imgCode)
|
||||
.then(async () => {
|
||||
await dCaptcha.close();
|
||||
if (pageIndex === 0) {
|
||||
mutate();
|
||||
}
|
||||
setComments(comments.filter((item) => item.comment_id !== id));
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
dCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -182,24 +223,40 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
return;
|
||||
}
|
||||
|
||||
postVote(
|
||||
{
|
||||
object_id: id,
|
||||
is_cancel,
|
||||
},
|
||||
'up',
|
||||
).then(() => {
|
||||
setComments(
|
||||
comments.map((item) => {
|
||||
if (item.comment_id === id) {
|
||||
item.vote_count = is_cancel
|
||||
? item.vote_count - 1
|
||||
: item.vote_count + 1;
|
||||
item.is_vote = !is_cancel;
|
||||
vCaptcha.check(() => {
|
||||
const imgCode: Types.ImgCodeReq = {
|
||||
captcha_id: undefined,
|
||||
captcha_code: undefined,
|
||||
};
|
||||
vCaptcha.resolveCaptchaReq(imgCode);
|
||||
|
||||
postVote(
|
||||
{
|
||||
object_id: id,
|
||||
is_cancel,
|
||||
...imgCode,
|
||||
},
|
||||
'up',
|
||||
)
|
||||
.then(async () => {
|
||||
await vCaptcha.close();
|
||||
setComments(
|
||||
comments.map((item) => {
|
||||
if (item.comment_id === id) {
|
||||
item.vote_count = is_cancel
|
||||
? item.vote_count - 1
|
||||
: item.vote_count + 1;
|
||||
item.is_vote = !is_cancel;
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
vCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Modal } from '@/components';
|
||||
import { useReportModal, useToast } from '@/hooks';
|
||||
import { useReportModal, useToast, useCaptchaModal } from '@/hooks';
|
||||
import { QuestionOperationReq } from '@/common/interface';
|
||||
import Share from '../Share';
|
||||
import {
|
||||
|
@ -44,6 +44,7 @@ const Index: FC<IProps> = ({
|
|||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const reportModal = useReportModal();
|
||||
const dCaptcha = useCaptchaModal('delete');
|
||||
|
||||
const refreshQuestion = () => {
|
||||
callback?.('default');
|
||||
|
@ -77,14 +78,28 @@ const Index: FC<IProps> = ({
|
|||
confirmBtnVariant: 'danger',
|
||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||
onConfirm: () => {
|
||||
deleteQuestion({
|
||||
id: qid,
|
||||
}).then(() => {
|
||||
toast.onShow({
|
||||
msg: t('post_deleted', { keyPrefix: 'messages' }),
|
||||
variant: 'success',
|
||||
});
|
||||
callback?.('delete_question');
|
||||
dCaptcha.check(() => {
|
||||
const req = {
|
||||
id: qid,
|
||||
captcha_code: undefined,
|
||||
captcha_id: undefined,
|
||||
};
|
||||
dCaptcha.resolveCaptchaReq(req);
|
||||
|
||||
deleteQuestion(req)
|
||||
.then(async () => {
|
||||
await dCaptcha.close();
|
||||
toast.onShow({
|
||||
msg: t('post_deleted', { keyPrefix: 'messages' }),
|
||||
variant: 'success',
|
||||
});
|
||||
callback?.('delete_question');
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
dCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -98,15 +113,29 @@ const Index: FC<IProps> = ({
|
|||
confirmBtnVariant: 'danger',
|
||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||
onConfirm: () => {
|
||||
deleteAnswer({
|
||||
id: aid,
|
||||
}).then(() => {
|
||||
// refresh page
|
||||
toast.onShow({
|
||||
msg: t('tip_answer_deleted'),
|
||||
variant: 'success',
|
||||
});
|
||||
callback?.('all');
|
||||
dCaptcha.check(() => {
|
||||
const req = {
|
||||
id: aid,
|
||||
captcha_code: undefined,
|
||||
captcha_id: undefined,
|
||||
};
|
||||
dCaptcha.resolveCaptchaReq(req);
|
||||
|
||||
deleteAnswer(req)
|
||||
.then(async () => {
|
||||
await dCaptcha.close();
|
||||
// refresh page
|
||||
toast.onShow({
|
||||
msg: t('tip_answer_deleted'),
|
||||
variant: 'success',
|
||||
});
|
||||
callback?.('all');
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
dCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Col } from 'react-bootstrap';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { PicAuthCodeModal } from '@/components/Modal';
|
||||
import type { ImgCodeRes, ImgCodeReq, FormDataType } from '@/common/interface';
|
||||
import type { ImgCodeReq, FormDataType } from '@/common/interface';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { resendEmail, checkImgCode } from '@/services';
|
||||
import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants';
|
||||
import Storage from '@/utils/storage';
|
||||
import { resendEmail } from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { useCaptchaModal } from '@/hooks';
|
||||
|
||||
interface IProps {
|
||||
visible: boolean;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
const Index: React.FC<IProps> = ({ visible = false }) => {
|
||||
const Index: React.FC<IProps> = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'inactive' });
|
||||
const [isSuccess, setSuccess] = useState(false);
|
||||
const [showModal, setModalState] = useState(false);
|
||||
const { e_mail } = loggedUserInfoStore((state) => state.user);
|
||||
const [formData, setFormData] = useState<FormDataType>({
|
||||
captcha_code: {
|
||||
|
@ -27,75 +24,39 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
|
|||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
|
||||
const getImgCode = () => {
|
||||
checkImgCode({
|
||||
action: 'e_mail',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const emailCaptcha = useCaptchaModal('email');
|
||||
|
||||
const submit = (e?: any) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
let obj: ImgCodeReq = {};
|
||||
const submit = () => {
|
||||
let req: ImgCodeReq = {};
|
||||
const imgCode = emailCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
const code = Storage.get(CAPTCHA_CODE_STORAGE_KEY) || '';
|
||||
obj = {
|
||||
captcha_code: code,
|
||||
req = {
|
||||
captcha_code: imgCode.captcha_code,
|
||||
captcha_id: imgCode.captcha_id,
|
||||
};
|
||||
}
|
||||
resendEmail(obj)
|
||||
resendEmail(req)
|
||||
.then(() => {
|
||||
emailCaptcha.close();
|
||||
setSuccess(true);
|
||||
setModalState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
emailCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
const onSentEmail = () => {
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
if (!formData.captcha_code.value) {
|
||||
setFormData({
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: t('msg.empty'),
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
submit();
|
||||
const onSentEmail = (evt) => {
|
||||
evt.preventDefault();
|
||||
emailCaptcha.check(() => {
|
||||
submit();
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (params: FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
getImgCode();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Col md={6} className="mx-auto text-center">
|
||||
{isSuccess ? (
|
||||
|
@ -124,18 +85,6 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
|
|||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={submit}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import usePageTags from './usePageTags';
|
|||
import useLoginRedirect from './useLoginRedirect';
|
||||
import usePromptWithUnload from './usePrompt';
|
||||
import useActivationEmailModal from './useActivationEmailModal';
|
||||
import useCaptchaModal from './useCaptchaModal';
|
||||
|
||||
export {
|
||||
useTagModal,
|
||||
|
@ -26,4 +27,5 @@ export {
|
|||
useLoginRedirect,
|
||||
usePromptWithUnload,
|
||||
useActivationEmailModal,
|
||||
useCaptchaModal,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
import { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
||||
import { Modal, Form, Button, InputGroup } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import { Icon } from '@/components';
|
||||
import type {
|
||||
FormValue,
|
||||
ImgCodeRes,
|
||||
CaptchaKey,
|
||||
FieldError,
|
||||
ImgCodeReq,
|
||||
} from '@/common/interface';
|
||||
import { checkImgCode } from '@/services';
|
||||
|
||||
type SubmitCallback = {
|
||||
(): void;
|
||||
};
|
||||
|
||||
const Index = (captchaKey: CaptchaKey) => {
|
||||
const refRoot = useRef(null);
|
||||
if (refRoot.current === null) {
|
||||
// @ts-ignore
|
||||
refRoot.current = ReactDOM.createRoot(document.createElement('div'));
|
||||
}
|
||||
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'pic_auth_code' });
|
||||
const refKey = useRef<CaptchaKey>(captchaKey);
|
||||
const refCallback = useRef<SubmitCallback>();
|
||||
const pending = useRef(false);
|
||||
const autoInitCaptchaData = /email/i.test(refKey.current);
|
||||
|
||||
const [stateShow, setStateShow] = useState(false);
|
||||
const [captcha, setCaptcha] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [imgCode, setImgCode] = useState<FormValue>({
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
});
|
||||
const refCaptcha = useRef(captcha);
|
||||
const refImgCode = useRef(imgCode);
|
||||
|
||||
const fetchCaptchaData = () => {
|
||||
pending.current = true;
|
||||
checkImgCode(refKey.current)
|
||||
.then((resp) => {
|
||||
setCaptcha(resp);
|
||||
})
|
||||
.finally(() => {
|
||||
pending.current = false;
|
||||
});
|
||||
};
|
||||
|
||||
const resetCapture = () => {
|
||||
setCaptcha({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
};
|
||||
|
||||
const show = () => {
|
||||
if (!stateShow) {
|
||||
setStateShow(true);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* There are some cases where the React scheduler cancels the execution of some functions,
|
||||
* which prevents them from closing properly:
|
||||
* for example, if the parent component uninstalls the child component directly,
|
||||
* and the `captchaModal.close()` call is inside the child component.
|
||||
* In this case, call `await captchaModal.close()` and wait for the close action to complete.
|
||||
*/
|
||||
const close = (reset = true) => {
|
||||
setStateShow(false);
|
||||
if (reset) {
|
||||
resetCapture();
|
||||
}
|
||||
const p = new Promise<void>((resolve) => {
|
||||
setTimeout(resolve);
|
||||
});
|
||||
return p;
|
||||
};
|
||||
|
||||
const handleCaptchaError = (fel: FieldError[] = []) => {
|
||||
const captchaErr = fel.find((o) => {
|
||||
return o.error_field === 'captcha_code';
|
||||
});
|
||||
|
||||
const ri = refImgCode.current;
|
||||
if (captchaErr) {
|
||||
/**
|
||||
* `imgCode.value` No value but a validation error is received,
|
||||
* indicating that it is the first time the interface has returned a CAPTCHA error,
|
||||
* triggering the CAPTCHA logic. There is no need to display the error message at this point.
|
||||
*/
|
||||
if (ri.value) {
|
||||
setImgCode({
|
||||
...ri,
|
||||
isInvalid: true,
|
||||
errorMsg: captchaErr.error_msg,
|
||||
});
|
||||
}
|
||||
fetchCaptchaData();
|
||||
} else {
|
||||
setImgCode({
|
||||
...ri,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
});
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (evt) => {
|
||||
evt.preventDefault();
|
||||
setImgCode({
|
||||
value: evt.target.value || '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
});
|
||||
};
|
||||
|
||||
const getCaptcha = () => {
|
||||
const rc = refCaptcha.current;
|
||||
const ri = refImgCode.current;
|
||||
const r = {
|
||||
verify: !!rc?.verify,
|
||||
captcha_id: rc?.captcha_id,
|
||||
captcha_code: ri.value,
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
const resolveCaptchaReq = (req: ImgCodeReq) => {
|
||||
const r = getCaptcha();
|
||||
if (r.verify) {
|
||||
req.captcha_code = r.captcha_code;
|
||||
req.captcha_id = r.captcha_id;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (evt) => {
|
||||
evt.preventDefault();
|
||||
if (!imgCode.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refCallback.current) {
|
||||
refCallback.current();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoInitCaptchaData) {
|
||||
fetchCaptchaData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
refImgCode.current = imgCode;
|
||||
refCaptcha.current = captcha;
|
||||
}, [captcha, imgCode]);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
refRoot.current.render(
|
||||
<Modal
|
||||
size="sm"
|
||||
title="Captcha"
|
||||
show={stateShow}
|
||||
onHide={() => close(false)}
|
||||
centered>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title as="h5">{t('title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Form.Group controlId="code" className="mb-3">
|
||||
<div className="mb-3 p-2 d-flex align-items-center justify-content-center bg-light rounded-2">
|
||||
<img
|
||||
src={captcha?.captcha_img}
|
||||
alt="captcha img"
|
||||
width="auto"
|
||||
height="40px"
|
||||
/>
|
||||
</div>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder={t('placeholder')}
|
||||
isInvalid={imgCode?.isInvalid}
|
||||
onChange={handleChange}
|
||||
value={imgCode.value}
|
||||
/>
|
||||
<Button
|
||||
onClick={fetchCaptchaData}
|
||||
variant="outline-secondary"
|
||||
title={t('refresh', { keyPrefix: 'btns' })}
|
||||
style={{
|
||||
borderTopRightRadius: '0.375rem',
|
||||
borderBottomRightRadius: '0.375rem',
|
||||
}}>
|
||||
<Icon name="arrow-repeat" />
|
||||
</Button>
|
||||
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{imgCode?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</InputGroup>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-grid">
|
||||
<Button type="submit" disabled={!imgCode.value}>
|
||||
{t('verify', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
</Modal>,
|
||||
);
|
||||
});
|
||||
|
||||
const r = {
|
||||
close,
|
||||
show,
|
||||
check: (submitFunc: SubmitCallback) => {
|
||||
if (pending.current) {
|
||||
return false;
|
||||
}
|
||||
refCallback.current = submitFunc;
|
||||
if (captcha?.verify) {
|
||||
show();
|
||||
}
|
||||
return submitFunc();
|
||||
},
|
||||
getCaptcha,
|
||||
resolveCaptchaReq,
|
||||
fetchCaptchaData,
|
||||
handleCaptchaError,
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import { useToast } from '@/hooks';
|
||||
import { useToast, useCaptchaModal } from '@/hooks';
|
||||
import type * as Type from '@/common/interface';
|
||||
import { reportList, postReport, closeQuestion, putReport } from '@/services';
|
||||
|
||||
|
@ -37,6 +37,8 @@ const useReportModal = (callback?: () => void) => {
|
|||
const [show, setShow] = useState(false);
|
||||
const [list, setList] = useState<any[]>([]);
|
||||
|
||||
const rCaptcha = useCaptchaModal('report');
|
||||
|
||||
useEffect(() => {
|
||||
const div = document.createElement('div');
|
||||
rootRef.current.root = ReactDOM.createRoot(div);
|
||||
|
@ -103,18 +105,32 @@ const useReportModal = (callback?: () => void) => {
|
|||
return;
|
||||
}
|
||||
if (!params.isBackend && params.action === 'flag') {
|
||||
postReport({
|
||||
source: params.type,
|
||||
report_type: reportType.type,
|
||||
object_id: params.id,
|
||||
content: content.value,
|
||||
}).then(() => {
|
||||
toast.onShow({
|
||||
msg: t('flag_success', { keyPrefix: 'toast' }),
|
||||
variant: 'warning',
|
||||
});
|
||||
onClose();
|
||||
asyncCallback();
|
||||
rCaptcha.check(() => {
|
||||
const flagReq = {
|
||||
source: params.type,
|
||||
report_type: reportType.type,
|
||||
object_id: params.id,
|
||||
content: content.value,
|
||||
captcha_code: undefined,
|
||||
captcha_id: undefined,
|
||||
};
|
||||
rCaptcha.resolveCaptchaReq(flagReq);
|
||||
|
||||
postReport(flagReq)
|
||||
.then(async () => {
|
||||
await rCaptcha.close();
|
||||
toast.onShow({
|
||||
msg: t('flag_success', { keyPrefix: 'toast' }),
|
||||
variant: 'warning',
|
||||
});
|
||||
onClose();
|
||||
asyncCallback();
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
rCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import dayjs from 'dayjs';
|
|||
import classNames from 'classnames';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { usePageTags, usePromptWithUnload } from '@/hooks';
|
||||
import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||
import { Editor, EditorRef, TagSelector } from '@/components';
|
||||
import type * as Type from '@/common/interface';
|
||||
import { DRAFT_QUESTION_STORAGE_KEY } from '@/common/constants';
|
||||
|
@ -102,6 +102,9 @@ const Ask = () => {
|
|||
isEdit ? '' : formData.title.value,
|
||||
);
|
||||
|
||||
const saveCaptcha = useCaptchaModal('question');
|
||||
const editCaptcha = useCaptchaModal('edit');
|
||||
|
||||
const removeDraft = () => {
|
||||
saveDraft.save.cancel();
|
||||
saveDraft.remove();
|
||||
|
@ -251,52 +254,69 @@ const Ask = () => {
|
|||
tags: formData.tags.value,
|
||||
};
|
||||
if (isEdit) {
|
||||
modifyQuestion({
|
||||
...params,
|
||||
id: qid,
|
||||
edit_summary: formData.edit_summary.value,
|
||||
})
|
||||
.then((res) => {
|
||||
navigate(pathFactory.questionLanding(qid, params.url_title), {
|
||||
state: { isReview: res?.wait_for_review },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let res;
|
||||
if (checked) {
|
||||
res = await saveQuestionWidthAnaser({
|
||||
editCaptcha.check(() => {
|
||||
const ep = {
|
||||
...params,
|
||||
answer_content: formData.answer_content.value,
|
||||
}).catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res = await saveQuestion(params).catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const id = res?.id || res?.question?.id;
|
||||
if (id) {
|
||||
if (checked) {
|
||||
navigate(pathFactory.questionLanding(id, res?.question?.url_title));
|
||||
} else {
|
||||
navigate(pathFactory.questionLanding(id));
|
||||
id: qid,
|
||||
edit_summary: formData.edit_summary.value,
|
||||
};
|
||||
const imgCode = editCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
ep.captcha_code = imgCode.captcha_code;
|
||||
ep.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
}
|
||||
removeDraft();
|
||||
modifyQuestion(ep)
|
||||
.then(async (res) => {
|
||||
await editCaptcha.close();
|
||||
navigate(pathFactory.questionLanding(qid, params.url_title), {
|
||||
state: { isReview: res?.wait_for_review },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
editCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
saveCaptcha.check(async () => {
|
||||
const imgCode = saveCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = imgCode.captcha_code;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
let res;
|
||||
if (checked) {
|
||||
res = await saveQuestionWidthAnaser({
|
||||
...params,
|
||||
answer_content: formData.answer_content.value,
|
||||
}).catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res = await saveQuestion(params).catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const id = res?.id || res?.question?.id;
|
||||
if (id) {
|
||||
if (checked) {
|
||||
navigate(pathFactory.questionLanding(id, res?.question?.url_title));
|
||||
} else {
|
||||
navigate(pathFactory.questionLanding(id));
|
||||
}
|
||||
}
|
||||
removeDraft();
|
||||
});
|
||||
}
|
||||
};
|
||||
const backPage = () => {
|
||||
|
|
|
@ -8,6 +8,7 @@ import classNames from 'classnames';
|
|||
import { Avatar } from '@/components';
|
||||
import { getInviteUser, putInviteUser } from '@/services';
|
||||
import type * as Type from '@/common/interface';
|
||||
import { useCaptchaModal } from '@/hooks';
|
||||
|
||||
import PeopleDropdown from './PeopleDropdown';
|
||||
|
||||
|
@ -22,6 +23,7 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
|
|||
const MAX_ASK_NUMBER = 5;
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [users, setUsers] = useState<Type.UserInfoBase[]>();
|
||||
const iaCaptcha = useCaptchaModal('invitation_answer');
|
||||
|
||||
const initInviteUsers = () => {
|
||||
if (!questionId) {
|
||||
|
@ -60,14 +62,23 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
|
|||
const names = users.map((_) => {
|
||||
return _.username;
|
||||
});
|
||||
putInviteUser(questionId, names)
|
||||
.then(() => {
|
||||
setEditing(false);
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.log('ex: ', ex);
|
||||
});
|
||||
iaCaptcha.check(() => {
|
||||
const imgCode: Type.ImgCodeReq = {};
|
||||
iaCaptcha.resolveCaptchaReq(imgCode);
|
||||
putInviteUser(questionId, names, imgCode)
|
||||
.then(async () => {
|
||||
await iaCaptcha.close();
|
||||
setEditing(false);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
iaCaptcha.handleCaptchaError(ex.list);
|
||||
}
|
||||
console.log('ex: ', ex);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initInviteUsers();
|
||||
}, [questionId]);
|
||||
|
|
|
@ -5,9 +5,9 @@ import { useTranslation, Trans } from 'react-i18next';
|
|||
import { marked } from 'marked';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { usePromptWithUnload } from '@/hooks';
|
||||
import { usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||
import { Editor, Modal, TextArea } from '@/components';
|
||||
import { FormDataType } from '@/common/interface';
|
||||
import { FormDataType, PostAnswerReq } from '@/common/interface';
|
||||
import { postAnswer } from '@/services';
|
||||
import { guard, handleFormError, SaveDraft, storageExpires } from '@/utils';
|
||||
import { DRAFT_ANSWER_STORAGE_KEY } from '@/common/constants';
|
||||
|
@ -41,6 +41,7 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
|||
const [editorFocusState, setEditorFocusState] = useState(false);
|
||||
const [hasDraft, setHasDraft] = useState(false);
|
||||
const [showTips, setShowTips] = useState(data.loggedUserRank < 100);
|
||||
const aCaptcha = useCaptchaModal('answer');
|
||||
|
||||
usePromptWithUnload({
|
||||
when: Boolean(formData.content.value),
|
||||
|
@ -135,29 +136,40 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
|||
if (!checkValidated()) {
|
||||
return;
|
||||
}
|
||||
postAnswer({
|
||||
question_id: data?.qid,
|
||||
content: formData.content.value,
|
||||
html: marked.parse(formData.content.value),
|
||||
})
|
||||
.then((res) => {
|
||||
setShowEditor(false);
|
||||
setFormData({
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
|
||||
aCaptcha.check(() => {
|
||||
const params: PostAnswerReq = {
|
||||
question_id: data?.qid,
|
||||
content: formData.content.value,
|
||||
html: marked.parse(formData.content.value),
|
||||
};
|
||||
const imgCode = aCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = imgCode.captcha_code;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
postAnswer(params)
|
||||
.then(async (res) => {
|
||||
await aCaptcha.close();
|
||||
setShowEditor(false);
|
||||
setFormData({
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
removeDraft();
|
||||
callback?.(res.info);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
aCaptcha.handleCaptchaError(ex.list);
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
removeDraft();
|
||||
callback?.(res.info);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clickBtn = () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import dayjs from 'dayjs';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { handleFormError, scrollToDocTop } from '@/utils';
|
||||
import { usePageTags, usePromptWithUnload } from '@/hooks';
|
||||
import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { Editor, EditorRef, Icon, htmlRender } from '@/components';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
@ -51,6 +51,7 @@ const Index = () => {
|
|||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||
const [immData, setImmData] = useState(initFormData);
|
||||
const [contentChanged, setContentChanged] = useState(false);
|
||||
const editCaptcha = useCaptchaModal('edit');
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (data?.info?.content) {
|
||||
|
@ -136,36 +137,43 @@ const Index = () => {
|
|||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!checkValidated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params: Type.AnswerParams = {
|
||||
content: formData.content.value,
|
||||
html: editorRef.current.getHtml(),
|
||||
question_id: qid,
|
||||
id: aid,
|
||||
edit_summary: formData.description.value,
|
||||
};
|
||||
modifyAnswer(params)
|
||||
.then((res) => {
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
slugTitle: data?.question?.url_title,
|
||||
answerId: aid,
|
||||
}),
|
||||
{
|
||||
state: { isReview: res?.wait_for_review },
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
editCaptcha.check(() => {
|
||||
const params: Type.AnswerParams = {
|
||||
content: formData.content.value,
|
||||
html: editorRef.current.getHtml(),
|
||||
question_id: qid,
|
||||
id: aid,
|
||||
edit_summary: formData.description.value,
|
||||
};
|
||||
editCaptcha.resolveCaptchaReq(params);
|
||||
|
||||
modifyAnswer(params)
|
||||
.then(async (res) => {
|
||||
await editCaptcha.close();
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
slugTitle: data?.question?.url_title,
|
||||
answerId: aid,
|
||||
}),
|
||||
{
|
||||
state: { isReview: res?.wait_for_review },
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
editCaptcha.handleCaptchaError(ex.list);
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleSelectedRevision = (e) => {
|
||||
const index = e.target.value;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Row, Col, ListGroup } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { usePageTags } from '@/hooks';
|
||||
import { usePageTags, useCaptchaModal } from '@/hooks';
|
||||
import { Pagination } from '@/components';
|
||||
import { useSearch } from '@/services';
|
||||
import { getSearchResult } from '@/services';
|
||||
import type { SearchParams, SearchRes } from '@/common/interface';
|
||||
|
||||
import {
|
||||
Head,
|
||||
|
@ -21,15 +23,52 @@ const Index = () => {
|
|||
const page = searchParams.get('page') || 1;
|
||||
const q = searchParams.get('q') || '';
|
||||
const order = searchParams.get('order') || 'active';
|
||||
|
||||
const { data, isLoading } = useSearch({
|
||||
q,
|
||||
order,
|
||||
page: Number(page),
|
||||
size: 20,
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [data, setData] = useState<SearchRes>({
|
||||
count: 0,
|
||||
list: [],
|
||||
extra: null,
|
||||
});
|
||||
|
||||
const { count = 0, list = [], extra = null } = data || {};
|
||||
|
||||
const searchCaptcha = useCaptchaModal('search');
|
||||
|
||||
const doSearch = () => {
|
||||
setIsLoading(true);
|
||||
const params: SearchParams = {
|
||||
q,
|
||||
order,
|
||||
page: Number(page),
|
||||
size: 20,
|
||||
};
|
||||
|
||||
const captcha = searchCaptcha.getCaptcha();
|
||||
if (captcha?.verify) {
|
||||
params.captcha_id = captcha.captcha_id;
|
||||
params.captcha_code = captcha.captcha_code;
|
||||
}
|
||||
|
||||
getSearchResult(params)
|
||||
.then((resp) => {
|
||||
searchCaptcha.close();
|
||||
setData(resp);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
searchCaptcha.handleCaptchaError(err.list);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
searchCaptcha.check(() => {
|
||||
doSearch();
|
||||
});
|
||||
}, [q, order, page]);
|
||||
|
||||
let pageTitle = t('search', { keyPrefix: 'page_title' });
|
||||
if (q) {
|
||||
pageTitle = `${t('posts_containing', { keyPrefix: 'page_title' })} '${q}'`;
|
||||
|
@ -37,6 +76,7 @@ const Index = () => {
|
|||
usePageTags({
|
||||
title: pageTitle,
|
||||
});
|
||||
|
||||
return (
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="page-main flex-auto">
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
import { FC, memo, useEffect, useState } from 'react';
|
||||
import { FC, memo, useState } from 'react';
|
||||
import { Form, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type {
|
||||
ImgCodeRes,
|
||||
PasswordResetReq,
|
||||
FormDataType,
|
||||
} from '@/common/interface';
|
||||
import { resetPassword, checkImgCode } from '@/services';
|
||||
import { PicAuthCodeModal } from '@/components/Modal';
|
||||
import type { PasswordResetReq, FormDataType } from '@/common/interface';
|
||||
import { resetPassword } from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { useCaptchaModal } from '@/hooks';
|
||||
|
||||
interface IProps {
|
||||
visible: boolean;
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
visible?: boolean;
|
||||
callback: (param: number, email: string) => void;
|
||||
}
|
||||
|
||||
const Index: FC<IProps> = ({ visible = false, callback }) => {
|
||||
const Index: FC<IProps> = ({ callback }) => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'account_forgot' });
|
||||
const [formData, setFormData] = useState<FormDataType>({
|
||||
e_mail: {
|
||||
|
@ -24,26 +21,9 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [showModal, setModalState] = useState(false);
|
||||
|
||||
const getImgCode = () => {
|
||||
checkImgCode({
|
||||
action: 'find_pass',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const emailCaptcha = useCaptchaModal('email');
|
||||
|
||||
const handleChange = (params: FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
|
@ -73,27 +53,24 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
|||
const params: PasswordResetReq = {
|
||||
e_mail: formData.e_mail.value,
|
||||
};
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = formData.captcha_code.value;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
|
||||
const captcha = emailCaptcha.getCaptcha();
|
||||
if (captcha.verify) {
|
||||
params.captcha_code = captcha.captcha_code;
|
||||
params.captcha_id = captcha.captcha_id;
|
||||
}
|
||||
|
||||
resetPassword(params)
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
await emailCaptcha.close();
|
||||
callback?.(2, formData.e_mail.value);
|
||||
setModalState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
emailCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
setFormData({ ...data });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -105,64 +82,41 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
sendEmail();
|
||||
emailCaptcha.check(() => {
|
||||
sendEmail();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
getImgCode();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||
<Form.Group controlId="email" className="mb-3">
|
||||
<Form.Label>{t('email.label')}</Form.Label>
|
||||
<Form.Control
|
||||
required
|
||||
type="email"
|
||||
value={formData.e_mail.value}
|
||||
isInvalid={formData.e_mail.isInvalid}
|
||||
onChange={(e) => {
|
||||
handleChange({
|
||||
e_mail: {
|
||||
value: e.target.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.e_mail.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||
<Form.Group controlId="email" className="mb-3">
|
||||
<Form.Label>{t('email.label')}</Form.Label>
|
||||
<Form.Control
|
||||
required
|
||||
type="email"
|
||||
value={formData.e_mail.value}
|
||||
isInvalid={formData.e_mail.isInvalid}
|
||||
onChange={(e) => {
|
||||
handleChange({
|
||||
e_mail: {
|
||||
value: e.target.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.e_mail.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-grid mb-3">
|
||||
<Button variant="primary" type="submit">
|
||||
{t('btn_name')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={sendEmail}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</>
|
||||
<div className="d-grid mb-3">
|
||||
<Button variant="primary" type="submit">
|
||||
{t('btn_name')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
import { FC, memo, useEffect, useState } from 'react';
|
||||
import { FC, memo, useState } from 'react';
|
||||
import { Form, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type {
|
||||
ImgCodeRes,
|
||||
PasswordResetReq,
|
||||
FormDataType,
|
||||
} from '@/common/interface';
|
||||
import type { PasswordResetReq, FormDataType } from '@/common/interface';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { changeEmail, checkImgCode } from '@/services';
|
||||
import { PicAuthCodeModal } from '@/components/Modal';
|
||||
import { changeEmail } from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { useCaptchaModal } from '@/hooks';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
|
||||
|
@ -21,28 +17,12 @@ const Index: FC = () => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [showModal, setModalState] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { user: userInfo, update: updateUser } = loggedUserInfoStore();
|
||||
|
||||
const getImgCode = () => {
|
||||
checkImgCode({
|
||||
action: 'e_mail',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const emailCaptcha = useCaptchaModal('email');
|
||||
|
||||
const handleChange = (params: FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
|
@ -72,28 +52,25 @@ const Index: FC = () => {
|
|||
const params: PasswordResetReq = {
|
||||
e_mail: formData.e_mail.value,
|
||||
};
|
||||
const imgCode = emailCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = formData.captcha_code.value;
|
||||
params.captcha_code = imgCode.captcha_code;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
|
||||
changeEmail(params)
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
await emailCaptcha.close();
|
||||
userInfo.e_mail = formData.e_mail.value;
|
||||
updateUser(userInfo);
|
||||
navigate('/users/login', { replace: true });
|
||||
setModalState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
emailCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
setFormData({ ...data });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -104,69 +81,48 @@ const Index: FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
sendEmail();
|
||||
emailCaptcha.check(() => {
|
||||
sendEmail();
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate('/users/login?status=inactive', { replace: true });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getImgCode();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||
<Form.Group controlId="email" className="mb-3">
|
||||
<Form.Label>{t('email.label')}</Form.Label>
|
||||
<Form.Control
|
||||
required
|
||||
type="email"
|
||||
value={formData.e_mail.value}
|
||||
isInvalid={formData.e_mail.isInvalid}
|
||||
onChange={(e) => {
|
||||
handleChange({
|
||||
e_mail: {
|
||||
value: e.target.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.e_mail.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||
<Form.Group controlId="email" className="mb-3">
|
||||
<Form.Label>{t('email.label')}</Form.Label>
|
||||
<Form.Control
|
||||
required
|
||||
type="email"
|
||||
value={formData.e_mail.value}
|
||||
isInvalid={formData.e_mail.isInvalid}
|
||||
onChange={(e) => {
|
||||
handleChange({
|
||||
e_mail: {
|
||||
value: e.target.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.e_mail.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-grid mb-3">
|
||||
<Button variant="primary" type="submit">
|
||||
{t('btn_update')}
|
||||
</Button>
|
||||
<Button variant="link" className="mt-2 d-block" onClick={goBack}>
|
||||
{t('btn_cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={sendEmail}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</>
|
||||
<div className="d-grid mb-3">
|
||||
<Button variant="primary" type="submit">
|
||||
{t('btn_update')}
|
||||
</Button>
|
||||
<Button variant="link" className="mt-2 d-block" onClick={goBack}>
|
||||
{t('btn_cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,12 +3,8 @@ import { Container, Form, Button, Col } from 'react-bootstrap';
|
|||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePageTags } from '@/hooks';
|
||||
import type {
|
||||
LoginReqParams,
|
||||
ImgCodeRes,
|
||||
FormDataType,
|
||||
} from '@/common/interface';
|
||||
import { usePageTags, useCaptchaModal } from '@/hooks';
|
||||
import type { LoginReqParams, FormDataType } from '@/common/interface';
|
||||
import { Unactivate, WelcomeTitle, PluginRender } from '@/components';
|
||||
import {
|
||||
loggedUserInfoStore,
|
||||
|
@ -16,14 +12,12 @@ import {
|
|||
userCenterStore,
|
||||
} from '@/stores';
|
||||
import { floppyNavigation, guard, handleFormError, userCenter } from '@/utils';
|
||||
import { login, checkImgCode, UcAgent } from '@/services';
|
||||
import { PicAuthCodeModal } from '@/components/Modal';
|
||||
import { login, UcAgent } from '@/services';
|
||||
|
||||
const Index: React.FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [refresh, setRefresh] = useState(0);
|
||||
const { user: storeUser, update: updateUser } = loggedUserInfoStore((_) => _);
|
||||
const loginSetting = loginSettingStore((state) => state.login);
|
||||
const ucAgent = userCenterStore().agent;
|
||||
|
@ -45,34 +39,15 @@ const Index: React.FC = () => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [showModal, setModalState] = useState(false);
|
||||
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
const handleChange = (params: FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
};
|
||||
|
||||
const getImgCode = () => {
|
||||
if (!canOriginalLogin) {
|
||||
return;
|
||||
}
|
||||
checkImgCode({
|
||||
action: 'login',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const passwordCaptcha = useCaptchaModal('password');
|
||||
|
||||
const checkValidated = (): boolean => {
|
||||
let bol = true;
|
||||
|
@ -110,34 +85,31 @@ const Index: React.FC = () => {
|
|||
e_mail: formData.e_mail.value,
|
||||
pass: formData.pass.value,
|
||||
};
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = formData.captcha_code.value;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
|
||||
const captcha = passwordCaptcha.getCaptcha();
|
||||
if (captcha?.verify) {
|
||||
params.captcha_code = captcha.captcha_code;
|
||||
params.captcha_id = captcha.captcha_id;
|
||||
}
|
||||
|
||||
login(params)
|
||||
.then((res) => {
|
||||
passwordCaptcha.close();
|
||||
updateUser(res);
|
||||
const userStat = guard.deriveLoginState();
|
||||
if (userStat.isNotActivated) {
|
||||
// inactive
|
||||
setStep(2);
|
||||
setRefresh((pre) => pre + 1);
|
||||
} else {
|
||||
guard.handleLoginRedirect(navigate);
|
||||
}
|
||||
|
||||
setModalState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
setFormData({ ...data });
|
||||
passwordCaptcha.handleCaptchaError(err.list);
|
||||
}
|
||||
setRefresh((pre) => pre + 1);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -149,18 +121,11 @@ const Index: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
handleLogin();
|
||||
passwordCaptcha.check(() => {
|
||||
handleLogin();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getImgCode();
|
||||
}, [refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
const isInactive = searchParams.get('status');
|
||||
|
||||
|
@ -168,6 +133,7 @@ const Index: React.FC = () => {
|
|||
setStep(2);
|
||||
}
|
||||
}, []);
|
||||
|
||||
usePageTags({
|
||||
title: t('login', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
|
@ -263,18 +229,6 @@ const Index: React.FC = () => {
|
|||
) : null}
|
||||
|
||||
{step === 2 && <Unactivate visible={step === 2} />}
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={handleLogin}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
import React, { FormEvent, MouseEvent, useEffect, useState } from 'react';
|
||||
import React, { FormEvent, MouseEvent, useState } from 'react';
|
||||
import { Form, Button } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { PicAuthCodeModal } from '@/components/Modal';
|
||||
import { ImgCodeRes } from '@/common/interface';
|
||||
import { useCaptchaModal } from '@/hooks';
|
||||
import type { FormDataType, RegisterReqParams } from '@/common/interface';
|
||||
import {
|
||||
register,
|
||||
getRegisterCaptcha,
|
||||
useLegalTos,
|
||||
useLegalPrivacy,
|
||||
} from '@/services';
|
||||
import { register, useLegalTos, useLegalPrivacy } from '@/services';
|
||||
import userStore from '@/stores/loggedUserInfo';
|
||||
import { handleFormError } from '@/utils';
|
||||
|
||||
|
@ -37,25 +31,11 @@ const Index: React.FC<Props> = ({ callback }) => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const updateUser = userStore((state) => state.update);
|
||||
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [showModal, setModalState] = useState(false);
|
||||
const getImgCode = () => {
|
||||
getRegisterCaptcha().then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const updateUser = userStore((state) => state.update);
|
||||
const emailCaptcha = useCaptchaModal('email');
|
||||
|
||||
const handleChange = (params: FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
};
|
||||
|
@ -86,6 +66,7 @@ const Index: React.FC<Props> = ({ callback }) => {
|
|||
});
|
||||
return bol;
|
||||
};
|
||||
|
||||
const { data: tos } = useLegalTos();
|
||||
const { data: privacy } = useLegalPrivacy();
|
||||
const argumentClick = (evt: MouseEvent, type: 'tos' | 'privacy') => {
|
||||
|
@ -117,25 +98,24 @@ const Index: React.FC<Props> = ({ callback }) => {
|
|||
pass: formData.pass.value,
|
||||
};
|
||||
|
||||
if (imgCode.verify) {
|
||||
reqParams.captcha_code = formData.captcha_code.value;
|
||||
reqParams.captcha_id = imgCode.captcha_id;
|
||||
const captcha = emailCaptcha.getCaptcha();
|
||||
if (captcha?.verify) {
|
||||
reqParams.captcha_code = captcha.captcha_code;
|
||||
reqParams.captcha_id = captcha.captcha_id;
|
||||
}
|
||||
|
||||
register(reqParams)
|
||||
.then((res) => {
|
||||
emailCaptcha.close();
|
||||
updateUser(res);
|
||||
setModalState(false);
|
||||
callback();
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
emailCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
setFormData({ ...data });
|
||||
}
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -145,15 +125,11 @@ const Index: React.FC<Props> = ({ callback }) => {
|
|||
if (!checkValidated()) {
|
||||
return;
|
||||
}
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
handleRegister();
|
||||
emailCaptcha.check(() => {
|
||||
handleRegister();
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
getImgCode();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||
|
@ -260,18 +236,6 @@ const Index: React.FC<Props> = ({ callback }) => {
|
|||
Already have an account? <Link to="/users/login">Log in</Link>
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={handleRegister}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,22 +3,15 @@ import { Form, Button } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import { useToast } from '@/hooks';
|
||||
import { getLoggedUserInfo, changeEmail, checkImgCode } from '@/services';
|
||||
import { useToast, useCaptchaModal } from '@/hooks';
|
||||
import { getLoggedUserInfo, changeEmail } from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { PicAuthCodeModal } from '@/components';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'settings.account',
|
||||
});
|
||||
const [step, setStep] = useState(1);
|
||||
const [showModal, setModalState] = useState(false);
|
||||
const [imgCode, setImgCode] = useState<Type.ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
const [formData, setFormData] = useState<Type.FormDataType>({
|
||||
e_mail: {
|
||||
value: '',
|
||||
|
@ -30,28 +23,17 @@ const Index: FC = () => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [userInfo, setUserInfo] = useState<Type.UserInfoRes>();
|
||||
const toast = useToast();
|
||||
const emailCaptcha = useCaptchaModal('edit_userinfo');
|
||||
|
||||
useEffect(() => {
|
||||
getLoggedUserInfo().then((resp) => {
|
||||
setUserInfo(resp);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getImgCode = () => {
|
||||
checkImgCode({
|
||||
action: 'e_mail',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (params: Type.FormDataType) => {
|
||||
setFormData({ ...formData, ...params });
|
||||
};
|
||||
|
@ -95,11 +77,6 @@ const Index: FC = () => {
|
|||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
captcha_code: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -112,14 +89,15 @@ const Index: FC = () => {
|
|||
pass: formData.pass.value,
|
||||
};
|
||||
|
||||
const imgCode = emailCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = formData.captcha_code.value;
|
||||
params.captcha_code = imgCode.captcha_code;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
changeEmail(params)
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
await emailCaptcha.close();
|
||||
setStep(1);
|
||||
setModalState(false);
|
||||
toast.onShow({
|
||||
msg: t('change_email_info'),
|
||||
variant: 'warning',
|
||||
|
@ -128,15 +106,10 @@ const Index: FC = () => {
|
|||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
emailCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -147,11 +120,9 @@ const Index: FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
postEmail();
|
||||
emailCaptcha.check(() => {
|
||||
postEmail();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -174,7 +145,6 @@ const Index: FC = () => {
|
|||
variant="outline-secondary"
|
||||
onClick={() => {
|
||||
setStep(2);
|
||||
getImgCode();
|
||||
}}>
|
||||
{t('change_email_btn')}
|
||||
</Button>
|
||||
|
@ -240,18 +210,6 @@ const Index: FC = () => {
|
|||
</div>
|
||||
</Form>
|
||||
)}
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={postEmail}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,12 +4,11 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import classname from 'classnames';
|
||||
|
||||
import { useToast } from '@/hooks';
|
||||
import type { FormDataType, ImgCodeRes } from '@/common/interface';
|
||||
import { modifyPassword, checkImgCode } from '@/services';
|
||||
import { useToast, useCaptchaModal } from '@/hooks';
|
||||
import type { FormDataType } from '@/common/interface';
|
||||
import { modifyPassword } from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { PicAuthCodeModal } from '@/components';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', {
|
||||
|
@ -35,20 +34,8 @@ const Index: FC = () => {
|
|||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
const [showModal, setModalState] = useState(false);
|
||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
||||
captcha_id: '',
|
||||
captcha_img: '',
|
||||
verify: false,
|
||||
});
|
||||
|
||||
const getImgCode = () => {
|
||||
checkImgCode({
|
||||
action: 'modify_pass',
|
||||
}).then((res) => {
|
||||
setImgCode(res);
|
||||
});
|
||||
};
|
||||
const infoCaptcha = useCaptchaModal('edit_userinfo');
|
||||
|
||||
const handleFormState = () => {
|
||||
setFormState((pre) => !pre);
|
||||
|
@ -128,13 +115,14 @@ const Index: FC = () => {
|
|||
pass: formData.pass.value,
|
||||
};
|
||||
|
||||
const imgCode = infoCaptcha.getCaptcha();
|
||||
if (imgCode.verify) {
|
||||
params.captcha_code = formData.captcha_code.value;
|
||||
params.captcha_code = imgCode.captcha_code;
|
||||
params.captcha_id = imgCode.captcha_id;
|
||||
}
|
||||
modifyPassword(params)
|
||||
.then(() => {
|
||||
setModalState(false);
|
||||
.then(async () => {
|
||||
await infoCaptcha.close();
|
||||
toast.onShow({
|
||||
msg: t('update_password', { keyPrefix: 'toast' }),
|
||||
variant: 'success',
|
||||
|
@ -143,15 +131,10 @@ const Index: FC = () => {
|
|||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
infoCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
||||
setModalState(false);
|
||||
}
|
||||
setFormData({ ...data });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
getImgCode();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -162,11 +145,9 @@ const Index: FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (imgCode.verify) {
|
||||
setModalState(true);
|
||||
return;
|
||||
}
|
||||
postModifyPass();
|
||||
infoCaptcha.check(() => {
|
||||
postModifyPass();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -262,24 +243,11 @@ const Index: FC = () => {
|
|||
type="submit"
|
||||
onClick={() => {
|
||||
handleFormState();
|
||||
getImgCode();
|
||||
}}>
|
||||
{t('change_pass_btn')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PicAuthCodeModal
|
||||
visible={showModal}
|
||||
data={{
|
||||
captcha: formData.captcha_code,
|
||||
imgCode,
|
||||
}}
|
||||
handleCaptcha={handleChange}
|
||||
clickSubmit={postModifyPass}
|
||||
refreshImgCode={getImgCode}
|
||||
onClose={() => setModalState(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -61,10 +61,15 @@ export const getInviteUser = (questionId: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const putInviteUser = (questionId: string, users: string[]) => {
|
||||
export const putInviteUser = (
|
||||
questionId: string,
|
||||
users: string[],
|
||||
imgCode: Type.ImgCodeReq = {},
|
||||
) => {
|
||||
const apiUrl = '/answer/api/v1/question/invite';
|
||||
return request.put(apiUrl, {
|
||||
id: questionId,
|
||||
invite_user: users,
|
||||
...imgCode,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
import useSWR from 'swr';
|
||||
import qs from 'qs';
|
||||
|
||||
import request from '@/utils/request';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
export const useSearch = (params?: Type.SearchParams) => {
|
||||
export const getSearchResult = (params?: Type.SearchParams) => {
|
||||
const apiUrl = '/answer/api/v1/search';
|
||||
const queryParams = qs.stringify(params, { skipNulls: true });
|
||||
const { data, error, mutate } = useSWR<Type.SearchRes, Error>(
|
||||
params?.q ? `${apiUrl}?${queryParams}` : null,
|
||||
request.instance.get,
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !data && !error,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
|
||||
return request.get<Type.SearchRes>(apiUrl, {
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -60,9 +60,10 @@ export const updateComment = (params) => {
|
|||
return request.put('/answer/api/v1/comment', params);
|
||||
};
|
||||
|
||||
export const deleteComment = (id) => {
|
||||
export const deleteComment = (id, imgCode: Type.ImgCodeReq = {}) => {
|
||||
return request.delete('/answer/api/v1/comment', {
|
||||
comment_id: id,
|
||||
...imgCode,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -102,19 +103,10 @@ export const register = (params: Type.RegisterReqParams) => {
|
|||
return request.post<any>('/answer/api/v1/user/register/email', params);
|
||||
};
|
||||
|
||||
export const getRegisterCaptcha = () => {
|
||||
const apiUrl = '/answer/api/v1/user/register/captcha';
|
||||
return request.get(apiUrl);
|
||||
};
|
||||
|
||||
export const logout = () => {
|
||||
return request.get('/answer/api/v1/user/logout');
|
||||
};
|
||||
|
||||
export const verifyEmail = (code: string) => {
|
||||
return request.get(`/answer/api/v1/email/verify?code=${code}`);
|
||||
};
|
||||
|
||||
export const resendEmail = (params?: Type.ImgCodeReq) => {
|
||||
params = qs.parse(
|
||||
qs.stringify(params, {
|
||||
|
@ -134,19 +126,19 @@ export const getLoggedUserInfo = (config = { passingError: false }) => {
|
|||
return request.get<Type.UserInfoRes>('/answer/api/v1/user/info', config);
|
||||
};
|
||||
|
||||
export const modifyPassword = (params: Type.ModifyPasswordReq) => {
|
||||
return request.put('/answer/api/v1/user/password', params);
|
||||
};
|
||||
|
||||
export const modifyUserInfo = (params: Type.ModifyUserReq) => {
|
||||
return request.put('/answer/api/v1/user/info', params);
|
||||
};
|
||||
|
||||
export const modifyPassword = (params: Type.ModifyPasswordReq) => {
|
||||
return request.put('/answer/api/v1/user/password', params);
|
||||
};
|
||||
|
||||
export const resetPassword = (params: Type.PasswordResetReq) => {
|
||||
return request.post('/answer/api/v1/user/password/reset', params);
|
||||
};
|
||||
|
||||
export const replacementPassword = (params: { code: string; pass: string }) => {
|
||||
export const replacementPassword = (params: Type.PasswordReplaceReq) => {
|
||||
return request.post('/answer/api/v1/user/password/replacement', params);
|
||||
};
|
||||
|
||||
|
@ -154,10 +146,13 @@ export const activateAccount = (code: string) => {
|
|||
return request.post(`/answer/api/v1/user/email/verification`, { code });
|
||||
};
|
||||
|
||||
export const checkImgCode = (params: Type.CheckImgReq) => {
|
||||
return request.get<Type.ImgCodeRes>(
|
||||
`/answer/api/v1/user/action/record?${qs.stringify(params)}`,
|
||||
);
|
||||
export const checkImgCode = (k: Type.CaptchaKey) => {
|
||||
const apiUrl = `/answer/api/v1/user/action/record`;
|
||||
return request.get<Type.ImgCodeRes>(apiUrl, {
|
||||
params: {
|
||||
action: k,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const setNotice = (params: Type.SetNoticeReq) => {
|
||||
|
@ -189,7 +184,7 @@ export const bookmark = (params: { group_id: string; object_id: string }) => {
|
|||
};
|
||||
|
||||
export const postVote = (
|
||||
params: { object_id: string; is_cancel: boolean },
|
||||
params: { object_id: string; is_cancel: boolean } & Type.ImgCodeReq,
|
||||
type: 'down' | 'up',
|
||||
) => {
|
||||
return request.post(`/answer/api/v1/vote/${type}`, params);
|
||||
|
@ -224,20 +219,30 @@ export const reportList = ({
|
|||
return request.get(`${api}?object_type=${type}&action=${action}`);
|
||||
};
|
||||
|
||||
export const postReport = (params: {
|
||||
source: Type.ReportType;
|
||||
content: string;
|
||||
object_id: string;
|
||||
report_type: number;
|
||||
}) => {
|
||||
export const postReport = (
|
||||
params: {
|
||||
source: Type.ReportType;
|
||||
content: string;
|
||||
object_id: string;
|
||||
report_type: number;
|
||||
} & Type.ImgCodeReq,
|
||||
) => {
|
||||
return request.post('/answer/api/v1/report', params);
|
||||
};
|
||||
|
||||
export const deleteQuestion = (params: { id: string }) => {
|
||||
export const deleteQuestion = (params: {
|
||||
id: string;
|
||||
captcha_code?: string;
|
||||
captcha_id?: string;
|
||||
}) => {
|
||||
return request.delete('/answer/api/v1/question', params);
|
||||
};
|
||||
|
||||
export const deleteAnswer = (params: { id: string }) => {
|
||||
export const deleteAnswer = (params: {
|
||||
id: string;
|
||||
captcha_code?: string;
|
||||
captcha_id?: string;
|
||||
}) => {
|
||||
return request.delete('/answer/api/v1/answer', params);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ import i18next from 'i18next';
|
|||
|
||||
import pattern from '@/common/pattern';
|
||||
import { USER_AGENT_NAMES } from '@/common/constants';
|
||||
|
||||
const Diff = require('diff');
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
function thousandthDivision(num) {
|
||||
const reg = /\d{1,3}(?=(\d{3})+$)/g;
|
||||
|
@ -114,7 +113,7 @@ function escapeRemove(str: string) {
|
|||
}
|
||||
|
||||
function handleFormError(
|
||||
error: { list: Array<{ error_field: string; error_msg: string }> },
|
||||
error: { list: Type.FieldError[] },
|
||||
data: any,
|
||||
keymap?: Array<{ from: string; to: string }>,
|
||||
) {
|
||||
|
@ -148,6 +147,8 @@ function escapeHtml(str: string) {
|
|||
return str.replace(/[&<>"'`]/g, (tag) => tagsToReplace[tag] || tag);
|
||||
}
|
||||
|
||||
const Diff = require('diff');
|
||||
|
||||
function diffText(newText: string, oldText?: string): string {
|
||||
if (!newText) {
|
||||
return '';
|
||||
|
|
|
@ -94,36 +94,42 @@ export interface NavigateConfig {
|
|||
}
|
||||
const navigate = (to: string | number, config: NavigateConfig = {}) => {
|
||||
let { handler = 'href' } = config;
|
||||
if (to && typeof to === 'string') {
|
||||
if (equalToCurrentHref(to)) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 1. Blocking redirection of two login pages
|
||||
* 2. Auto storage login redirect
|
||||
* Note: The or judgement cannot be missing here, both jumps will be used
|
||||
*/
|
||||
if (to === RouteAlias.login || to === getLoginUrl()) {
|
||||
storageLoginRedirect();
|
||||
/**
|
||||
* Note: Synchronised navigation can result in asynchronous actions such as page animations and state modifications not being completed.
|
||||
*/
|
||||
setTimeout(() => {
|
||||
if (to && typeof to === 'string') {
|
||||
if (equalToCurrentHref(to)) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 1. Blocking redirection of two login pages
|
||||
* 2. Auto storage login redirect
|
||||
* Note: The or judgement cannot be missing here, both jumps will be used
|
||||
*/
|
||||
if (to === RouteAlias.login || to === getLoginUrl()) {
|
||||
storageLoginRedirect();
|
||||
}
|
||||
|
||||
if (!isRoutableLink(to) && handler !== 'href' && handler !== 'replace') {
|
||||
handler = 'href';
|
||||
}
|
||||
if (handler === 'href' && config.options?.replace) {
|
||||
handler = 'replace';
|
||||
}
|
||||
if (handler === 'href') {
|
||||
window.location.href = to;
|
||||
} else if (handler === 'replace') {
|
||||
window.location.replace(to);
|
||||
} else if (typeof handler === 'function') {
|
||||
handler(to, config.options);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRoutableLink(to) && handler !== 'href' && handler !== 'replace') {
|
||||
handler = 'href';
|
||||
if (typeof to === 'number' && typeof handler === 'function') {
|
||||
handler(to);
|
||||
}
|
||||
if (handler === 'href' && config.options?.replace) {
|
||||
handler = 'replace';
|
||||
}
|
||||
if (handler === 'href') {
|
||||
window.location.href = to;
|
||||
} else if (handler === 'replace') {
|
||||
window.location.replace(to);
|
||||
} else if (typeof handler === 'function') {
|
||||
handler(to, config.options);
|
||||
}
|
||||
}
|
||||
if (typeof to === 'number' && typeof handler === 'function') {
|
||||
handler(to);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,6 +61,7 @@ class Request {
|
|||
config: errConfig,
|
||||
} = error.response || {};
|
||||
const { data = {}, msg = '' } = errBody || {};
|
||||
|
||||
const errorObject: {
|
||||
code: any;
|
||||
msg: string;
|
||||
|
@ -74,6 +75,7 @@ class Request {
|
|||
msg,
|
||||
data,
|
||||
};
|
||||
|
||||
if (status === 400) {
|
||||
if (data?.err_type && errConfig?.passingError) {
|
||||
return Promise.reject(errorObject);
|
||||
|
@ -127,6 +129,7 @@ class Request {
|
|||
floppyNavigation.navigateToLogin();
|
||||
return Promise.reject(false);
|
||||
}
|
||||
|
||||
if (status === 403) {
|
||||
// Permission interception
|
||||
if (data?.type === 'url_expired') {
|
||||
|
@ -173,6 +176,7 @@ class Request {
|
|||
errorCodeStore.getState().update('404');
|
||||
return Promise.reject(false);
|
||||
}
|
||||
|
||||
if (status >= 500) {
|
||||
if (isIgnoredPath(IGNORE_PATH_LIST)) {
|
||||
return Promise.reject(false);
|
||||
|
|
Loading…
Reference in New Issue