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;
|
[prop: string]: FormValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FieldError {
|
||||||
|
error_field: string;
|
||||||
|
error_msg: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Paging {
|
export interface Paging {
|
||||||
page: number;
|
page: number;
|
||||||
page_size?: number;
|
page_size?: number;
|
||||||
|
@ -52,7 +57,7 @@ export interface TagInfo extends TagBase {
|
||||||
main_tag_slug_name?: string;
|
main_tag_slug_name?: string;
|
||||||
excerpt?;
|
excerpt?;
|
||||||
}
|
}
|
||||||
export interface QuestionParams {
|
export interface QuestionParams extends ImgCodeReq{
|
||||||
title: string;
|
title: string;
|
||||||
url_title?: string;
|
url_title?: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
@ -68,7 +73,7 @@ export interface ListResult<T = any> {
|
||||||
list: T[];
|
list: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnswerParams {
|
export interface AnswerParams extends ImgCodeReq {
|
||||||
content: string;
|
content: string;
|
||||||
html: string;
|
html: string;
|
||||||
question_id: string;
|
question_id: string;
|
||||||
|
@ -169,10 +174,29 @@ export interface PasswordResetReq extends ImgCodeReq {
|
||||||
e_mail: string;
|
e_mail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CheckImgReq {
|
export interface PasswordReplaceReq extends ImgCodeReq {
|
||||||
action: 'login' | 'e_mail' | 'find_pass' | 'modify_pass';
|
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 {
|
export interface SetNoticeReq {
|
||||||
notice_switch: boolean;
|
notice_switch: boolean;
|
||||||
}
|
}
|
||||||
|
@ -222,7 +246,7 @@ export interface AnswerItem {
|
||||||
[prop: string]: any;
|
[prop: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostAnswerReq {
|
export interface PostAnswerReq extends ImgCodeReq {
|
||||||
content: string;
|
content: string;
|
||||||
html?: string;
|
html?: string;
|
||||||
question_id: string;
|
question_id: string;
|
||||||
|
@ -425,7 +449,7 @@ export interface FollowParams {
|
||||||
/**
|
/**
|
||||||
* @description search request params
|
* @description search request params
|
||||||
*/
|
*/
|
||||||
export interface SearchParams {
|
export interface SearchParams extends ImgCodeReq {
|
||||||
q: string;
|
q: string;
|
||||||
order: string;
|
order: string;
|
||||||
page: number;
|
page: number;
|
||||||
|
|
|
@ -6,9 +6,10 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { Icon } from '@/components';
|
import { Icon } from '@/components';
|
||||||
import { loggedUserInfoStore } from '@/stores';
|
import { loggedUserInfoStore } from '@/stores';
|
||||||
import { useToast } from '@/hooks';
|
import { useToast, useCaptchaModal } from '@/hooks';
|
||||||
import { tryNormalLogged } from '@/utils/guard';
|
import { tryNormalLogged } from '@/utils/guard';
|
||||||
import { bookmark, postVote } from '@/services';
|
import { bookmark, postVote } from '@/services';
|
||||||
|
import * as Types from '@/common/interface';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -36,6 +37,8 @@ const Index: FC<Props> = ({ className, data, source }) => {
|
||||||
const { username = '' } = loggedUserInfoStore((state) => state.user);
|
const { username = '' } = loggedUserInfoStore((state) => state.user);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const vCaptcha = useCaptchaModal('vote');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setVotes(data.votesCount);
|
setVotes(data.votesCount);
|
||||||
|
@ -61,19 +64,30 @@ const Index: FC<Props> = ({ className, data, source }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isCancel = (type === 'up' && like) || (type === 'down' && hate);
|
const isCancel = (type === 'up' && like) || (type === 'down' && hate);
|
||||||
|
vCaptcha.check(() => {
|
||||||
|
const imgCode: Types.ImgCodeReq = {
|
||||||
|
captcha_id: undefined,
|
||||||
|
captcha_code: undefined,
|
||||||
|
};
|
||||||
|
vCaptcha.resolveCaptchaReq(imgCode);
|
||||||
postVote(
|
postVote(
|
||||||
{
|
{
|
||||||
object_id: data?.id,
|
object_id: data?.id,
|
||||||
is_cancel: isCancel,
|
is_cancel: isCancel,
|
||||||
|
...imgCode,
|
||||||
},
|
},
|
||||||
type,
|
type,
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
|
await vCaptcha.close();
|
||||||
setVotes(res.votes);
|
setVotes(res.votes);
|
||||||
setLike(res.vote_status === 'vote_up');
|
setLike(res.vote_status === 'vote_up');
|
||||||
setHated(res.vote_status === 'vote_down');
|
setHated(res.vote_status === 'vote_down');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
if (err?.isError) {
|
||||||
|
vCaptcha.handleCaptchaError(err.list);
|
||||||
|
}
|
||||||
const errMsg = err?.value;
|
const errMsg = err?.value;
|
||||||
if (errMsg) {
|
if (errMsg) {
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
|
@ -82,6 +96,7 @@ const Index: FC<Props> = ({ className, data, source }) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBookmark = () => {
|
const handleBookmark = () => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { unionBy } from 'lodash';
|
||||||
|
|
||||||
import * as Types from '@/common/interface';
|
import * as Types from '@/common/interface';
|
||||||
import { Modal } from '@/components';
|
import { Modal } from '@/components';
|
||||||
import { usePageUsers, useReportModal } from '@/hooks';
|
import { usePageUsers, useReportModal, useCaptchaModal } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
matchedUsers,
|
matchedUsers,
|
||||||
parseUserInfo,
|
parseUserInfo,
|
||||||
|
@ -43,6 +43,11 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
|
|
||||||
const reportModal = useReportModal();
|
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 { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||||
const scrollCallback = useCallback((el, co) => {
|
const scrollCallback = useCallback((el, co) => {
|
||||||
if (pageIndex === 0 && co.comment_id === commentId) {
|
if (pageIndex === 0 && co.comment_id === commentId) {
|
||||||
|
@ -120,10 +125,18 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.type === 'edit') {
|
if (item.type === 'edit') {
|
||||||
return updateComment({
|
return editCaptcha.check(() => {
|
||||||
|
const up = {
|
||||||
...params,
|
...params,
|
||||||
comment_id: item.comment_id,
|
comment_id: item.comment_id,
|
||||||
}).then((res) => {
|
captcha_code: undefined,
|
||||||
|
captcha_id: undefined,
|
||||||
|
};
|
||||||
|
editCaptcha.resolveCaptchaReq(up);
|
||||||
|
|
||||||
|
return updateComment(up)
|
||||||
|
.then(async (res) => {
|
||||||
|
await editCaptcha.close();
|
||||||
setComments(
|
setComments(
|
||||||
comments.map((comment) => {
|
comments.map((comment) => {
|
||||||
if (comment.comment_id === item.comment_id) {
|
if (comment.comment_id === item.comment_id) {
|
||||||
|
@ -134,9 +147,24 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
return comment;
|
return comment;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.isError) {
|
||||||
|
editCaptcha.handleCaptchaError(err.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return addComment(params).then((res) => {
|
|
||||||
|
return addCaptcha.check(() => {
|
||||||
|
const req = {
|
||||||
|
...params,
|
||||||
|
captcha_code: undefined,
|
||||||
|
captcha_id: undefined,
|
||||||
|
};
|
||||||
|
addCaptcha.resolveCaptchaReq(req);
|
||||||
|
|
||||||
|
return addComment(req).then((res) => {
|
||||||
if (item.type === 'reply') {
|
if (item.type === 'reply') {
|
||||||
const index = comments.findIndex(
|
const index = comments.findIndex(
|
||||||
(comment) => comment.comment_id === item.comment_id,
|
(comment) => comment.comment_id === item.comment_id,
|
||||||
|
@ -158,6 +186,7 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
|
|
||||||
setVisibleComment(false);
|
setVisibleComment(false);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
|
@ -167,11 +196,23 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
confirmBtnVariant: 'danger',
|
confirmBtnVariant: 'danger',
|
||||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
deleteComment(id).then(() => {
|
dCaptcha.check(() => {
|
||||||
|
const imgCode = { captcha_id: undefined, captcha_code: undefined };
|
||||||
|
dCaptcha.resolveCaptchaReq(imgCode);
|
||||||
|
|
||||||
|
deleteComment(id, imgCode)
|
||||||
|
.then(async () => {
|
||||||
|
await dCaptcha.close();
|
||||||
if (pageIndex === 0) {
|
if (pageIndex === 0) {
|
||||||
mutate();
|
mutate();
|
||||||
}
|
}
|
||||||
setComments(comments.filter((item) => item.comment_id !== id));
|
setComments(comments.filter((item) => item.comment_id !== id));
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
dCaptcha.handleCaptchaError(ex.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -182,13 +223,23 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vCaptcha.check(() => {
|
||||||
|
const imgCode: Types.ImgCodeReq = {
|
||||||
|
captcha_id: undefined,
|
||||||
|
captcha_code: undefined,
|
||||||
|
};
|
||||||
|
vCaptcha.resolveCaptchaReq(imgCode);
|
||||||
|
|
||||||
postVote(
|
postVote(
|
||||||
{
|
{
|
||||||
object_id: id,
|
object_id: id,
|
||||||
is_cancel,
|
is_cancel,
|
||||||
|
...imgCode,
|
||||||
},
|
},
|
||||||
'up',
|
'up',
|
||||||
).then(() => {
|
)
|
||||||
|
.then(async () => {
|
||||||
|
await vCaptcha.close();
|
||||||
setComments(
|
setComments(
|
||||||
comments.map((item) => {
|
comments.map((item) => {
|
||||||
if (item.comment_id === id) {
|
if (item.comment_id === id) {
|
||||||
|
@ -200,6 +251,12 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
return item;
|
return item;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
vCaptcha.handleCaptchaError(ex.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Modal } from '@/components';
|
import { Modal } from '@/components';
|
||||||
import { useReportModal, useToast } from '@/hooks';
|
import { useReportModal, useToast, useCaptchaModal } from '@/hooks';
|
||||||
import { QuestionOperationReq } from '@/common/interface';
|
import { QuestionOperationReq } from '@/common/interface';
|
||||||
import Share from '../Share';
|
import Share from '../Share';
|
||||||
import {
|
import {
|
||||||
|
@ -44,6 +44,7 @@ const Index: FC<IProps> = ({
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const reportModal = useReportModal();
|
const reportModal = useReportModal();
|
||||||
|
const dCaptcha = useCaptchaModal('delete');
|
||||||
|
|
||||||
const refreshQuestion = () => {
|
const refreshQuestion = () => {
|
||||||
callback?.('default');
|
callback?.('default');
|
||||||
|
@ -77,14 +78,28 @@ const Index: FC<IProps> = ({
|
||||||
confirmBtnVariant: 'danger',
|
confirmBtnVariant: 'danger',
|
||||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
deleteQuestion({
|
dCaptcha.check(() => {
|
||||||
|
const req = {
|
||||||
id: qid,
|
id: qid,
|
||||||
}).then(() => {
|
captcha_code: undefined,
|
||||||
|
captcha_id: undefined,
|
||||||
|
};
|
||||||
|
dCaptcha.resolveCaptchaReq(req);
|
||||||
|
|
||||||
|
deleteQuestion(req)
|
||||||
|
.then(async () => {
|
||||||
|
await dCaptcha.close();
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
msg: t('post_deleted', { keyPrefix: 'messages' }),
|
msg: t('post_deleted', { keyPrefix: 'messages' }),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
callback?.('delete_question');
|
callback?.('delete_question');
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
dCaptcha.handleCaptchaError(ex.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -98,15 +113,29 @@ const Index: FC<IProps> = ({
|
||||||
confirmBtnVariant: 'danger',
|
confirmBtnVariant: 'danger',
|
||||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
deleteAnswer({
|
dCaptcha.check(() => {
|
||||||
|
const req = {
|
||||||
id: aid,
|
id: aid,
|
||||||
}).then(() => {
|
captcha_code: undefined,
|
||||||
|
captcha_id: undefined,
|
||||||
|
};
|
||||||
|
dCaptcha.resolveCaptchaReq(req);
|
||||||
|
|
||||||
|
deleteAnswer(req)
|
||||||
|
.then(async () => {
|
||||||
|
await dCaptcha.close();
|
||||||
// refresh page
|
// refresh page
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
msg: t('tip_answer_deleted'),
|
msg: t('tip_answer_deleted'),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
callback?.('all');
|
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 { Button, Col } from 'react-bootstrap';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
import type { ImgCodeReq, FormDataType } from '@/common/interface';
|
||||||
import type { ImgCodeRes, ImgCodeReq, FormDataType } from '@/common/interface';
|
|
||||||
import { loggedUserInfoStore } from '@/stores';
|
import { loggedUserInfoStore } from '@/stores';
|
||||||
import { resendEmail, checkImgCode } from '@/services';
|
import { resendEmail } from '@/services';
|
||||||
import { CAPTCHA_CODE_STORAGE_KEY } from '@/common/constants';
|
|
||||||
import Storage from '@/utils/storage';
|
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
import { useCaptchaModal } from '@/hooks';
|
||||||
|
|
||||||
interface IProps {
|
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 { t } = useTranslation('translation', { keyPrefix: 'inactive' });
|
||||||
const [isSuccess, setSuccess] = useState(false);
|
const [isSuccess, setSuccess] = useState(false);
|
||||||
const [showModal, setModalState] = useState(false);
|
|
||||||
const { e_mail } = loggedUserInfoStore((state) => state.user);
|
const { e_mail } = loggedUserInfoStore((state) => state.user);
|
||||||
const [formData, setFormData] = useState<FormDataType>({
|
const [formData, setFormData] = useState<FormDataType>({
|
||||||
captcha_code: {
|
captcha_code: {
|
||||||
|
@ -27,75 +24,39 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
|
||||||
captcha_id: '',
|
|
||||||
captcha_img: '',
|
|
||||||
verify: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getImgCode = () => {
|
const emailCaptcha = useCaptchaModal('email');
|
||||||
checkImgCode({
|
|
||||||
action: 'e_mail',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = (e?: any) => {
|
const submit = () => {
|
||||||
if (e) {
|
let req: ImgCodeReq = {};
|
||||||
e.preventDefault();
|
const imgCode = emailCaptcha.getCaptcha();
|
||||||
}
|
|
||||||
let obj: ImgCodeReq = {};
|
|
||||||
if (imgCode.verify) {
|
if (imgCode.verify) {
|
||||||
const code = Storage.get(CAPTCHA_CODE_STORAGE_KEY) || '';
|
req = {
|
||||||
obj = {
|
captcha_code: imgCode.captcha_code,
|
||||||
captcha_code: code,
|
|
||||||
captcha_id: imgCode.captcha_id,
|
captcha_id: imgCode.captcha_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
resendEmail(obj)
|
resendEmail(req)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
emailCaptcha.close();
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
setModalState(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
emailCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
getImgCode();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSentEmail = () => {
|
const onSentEmail = (evt) => {
|
||||||
if (imgCode.verify) {
|
evt.preventDefault();
|
||||||
setModalState(true);
|
emailCaptcha.check(() => {
|
||||||
if (!formData.captcha_code.value) {
|
|
||||||
setFormData({
|
|
||||||
captcha_code: {
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: t('msg.empty'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
submit();
|
submit();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (params: FormDataType) => {
|
|
||||||
setFormData({ ...formData, ...params });
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
getImgCode();
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col md={6} className="mx-auto text-center">
|
<Col md={6} className="mx-auto text-center">
|
||||||
{isSuccess ? (
|
{isSuccess ? (
|
||||||
|
@ -124,18 +85,6 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PicAuthCodeModal
|
|
||||||
visible={showModal}
|
|
||||||
data={{
|
|
||||||
captcha: formData.captcha_code,
|
|
||||||
imgCode,
|
|
||||||
}}
|
|
||||||
handleCaptcha={handleChange}
|
|
||||||
clickSubmit={submit}
|
|
||||||
refreshImgCode={getImgCode}
|
|
||||||
onClose={() => setModalState(false)}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ import usePageTags from './usePageTags';
|
||||||
import useLoginRedirect from './useLoginRedirect';
|
import useLoginRedirect from './useLoginRedirect';
|
||||||
import usePromptWithUnload from './usePrompt';
|
import usePromptWithUnload from './usePrompt';
|
||||||
import useActivationEmailModal from './useActivationEmailModal';
|
import useActivationEmailModal from './useActivationEmailModal';
|
||||||
|
import useCaptchaModal from './useCaptchaModal';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useTagModal,
|
useTagModal,
|
||||||
|
@ -26,4 +27,5 @@ export {
|
||||||
useLoginRedirect,
|
useLoginRedirect,
|
||||||
usePromptWithUnload,
|
usePromptWithUnload,
|
||||||
useActivationEmailModal,
|
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 ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
import { useToast } from '@/hooks';
|
import { useToast, useCaptchaModal } from '@/hooks';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
import { reportList, postReport, closeQuestion, putReport } from '@/services';
|
import { reportList, postReport, closeQuestion, putReport } from '@/services';
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@ const useReportModal = (callback?: () => void) => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const [list, setList] = useState<any[]>([]);
|
const [list, setList] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const rCaptcha = useCaptchaModal('report');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
rootRef.current.root = ReactDOM.createRoot(div);
|
rootRef.current.root = ReactDOM.createRoot(div);
|
||||||
|
@ -103,18 +105,32 @@ const useReportModal = (callback?: () => void) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!params.isBackend && params.action === 'flag') {
|
if (!params.isBackend && params.action === 'flag') {
|
||||||
postReport({
|
rCaptcha.check(() => {
|
||||||
|
const flagReq = {
|
||||||
source: params.type,
|
source: params.type,
|
||||||
report_type: reportType.type,
|
report_type: reportType.type,
|
||||||
object_id: params.id,
|
object_id: params.id,
|
||||||
content: content.value,
|
content: content.value,
|
||||||
}).then(() => {
|
captcha_code: undefined,
|
||||||
|
captcha_id: undefined,
|
||||||
|
};
|
||||||
|
rCaptcha.resolveCaptchaReq(flagReq);
|
||||||
|
|
||||||
|
postReport(flagReq)
|
||||||
|
.then(async () => {
|
||||||
|
await rCaptcha.close();
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
msg: t('flag_success', { keyPrefix: 'toast' }),
|
msg: t('flag_success', { keyPrefix: 'toast' }),
|
||||||
variant: 'warning',
|
variant: 'warning',
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
asyncCallback();
|
asyncCallback();
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
rCaptcha.handleCaptchaError(ex.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import dayjs from 'dayjs';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import { usePageTags, usePromptWithUnload } from '@/hooks';
|
import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||||
import { Editor, EditorRef, TagSelector } from '@/components';
|
import { Editor, EditorRef, TagSelector } from '@/components';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
import { DRAFT_QUESTION_STORAGE_KEY } from '@/common/constants';
|
import { DRAFT_QUESTION_STORAGE_KEY } from '@/common/constants';
|
||||||
|
@ -102,6 +102,9 @@ const Ask = () => {
|
||||||
isEdit ? '' : formData.title.value,
|
isEdit ? '' : formData.title.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const saveCaptcha = useCaptchaModal('question');
|
||||||
|
const editCaptcha = useCaptchaModal('edit');
|
||||||
|
|
||||||
const removeDraft = () => {
|
const removeDraft = () => {
|
||||||
saveDraft.save.cancel();
|
saveDraft.save.cancel();
|
||||||
saveDraft.remove();
|
saveDraft.remove();
|
||||||
|
@ -251,23 +254,39 @@ const Ask = () => {
|
||||||
tags: formData.tags.value,
|
tags: formData.tags.value,
|
||||||
};
|
};
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
modifyQuestion({
|
editCaptcha.check(() => {
|
||||||
|
const ep = {
|
||||||
...params,
|
...params,
|
||||||
id: qid,
|
id: qid,
|
||||||
edit_summary: formData.edit_summary.value,
|
edit_summary: formData.edit_summary.value,
|
||||||
})
|
};
|
||||||
.then((res) => {
|
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();
|
||||||
navigate(pathFactory.questionLanding(qid, params.url_title), {
|
navigate(pathFactory.questionLanding(qid, params.url_title), {
|
||||||
state: { isReview: res?.wait_for_review },
|
state: { isReview: res?.wait_for_review },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
editCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} else {
|
} 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;
|
let res;
|
||||||
if (checked) {
|
if (checked) {
|
||||||
res = await saveQuestionWidthAnaser({
|
res = await saveQuestionWidthAnaser({
|
||||||
|
@ -297,6 +316,7 @@ const Ask = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeDraft();
|
removeDraft();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const backPage = () => {
|
const backPage = () => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import classNames from 'classnames';
|
||||||
import { Avatar } from '@/components';
|
import { Avatar } from '@/components';
|
||||||
import { getInviteUser, putInviteUser } from '@/services';
|
import { getInviteUser, putInviteUser } from '@/services';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
|
import { useCaptchaModal } from '@/hooks';
|
||||||
|
|
||||||
import PeopleDropdown from './PeopleDropdown';
|
import PeopleDropdown from './PeopleDropdown';
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
|
||||||
const MAX_ASK_NUMBER = 5;
|
const MAX_ASK_NUMBER = 5;
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [users, setUsers] = useState<Type.UserInfoBase[]>();
|
const [users, setUsers] = useState<Type.UserInfoBase[]>();
|
||||||
|
const iaCaptcha = useCaptchaModal('invitation_answer');
|
||||||
|
|
||||||
const initInviteUsers = () => {
|
const initInviteUsers = () => {
|
||||||
if (!questionId) {
|
if (!questionId) {
|
||||||
|
@ -60,14 +62,23 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
|
||||||
const names = users.map((_) => {
|
const names = users.map((_) => {
|
||||||
return _.username;
|
return _.username;
|
||||||
});
|
});
|
||||||
putInviteUser(questionId, names)
|
iaCaptcha.check(() => {
|
||||||
.then(() => {
|
const imgCode: Type.ImgCodeReq = {};
|
||||||
|
iaCaptcha.resolveCaptchaReq(imgCode);
|
||||||
|
putInviteUser(questionId, names, imgCode)
|
||||||
|
.then(async () => {
|
||||||
|
await iaCaptcha.close();
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
})
|
})
|
||||||
.catch((ex) => {
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
iaCaptcha.handleCaptchaError(ex.list);
|
||||||
|
}
|
||||||
console.log('ex: ', ex);
|
console.log('ex: ', ex);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initInviteUsers();
|
initInviteUsers();
|
||||||
}, [questionId]);
|
}, [questionId]);
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { usePromptWithUnload } from '@/hooks';
|
import { usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||||
import { Editor, Modal, TextArea } from '@/components';
|
import { Editor, Modal, TextArea } from '@/components';
|
||||||
import { FormDataType } from '@/common/interface';
|
import { FormDataType, PostAnswerReq } from '@/common/interface';
|
||||||
import { postAnswer } from '@/services';
|
import { postAnswer } from '@/services';
|
||||||
import { guard, handleFormError, SaveDraft, storageExpires } from '@/utils';
|
import { guard, handleFormError, SaveDraft, storageExpires } from '@/utils';
|
||||||
import { DRAFT_ANSWER_STORAGE_KEY } from '@/common/constants';
|
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 [editorFocusState, setEditorFocusState] = useState(false);
|
||||||
const [hasDraft, setHasDraft] = useState(false);
|
const [hasDraft, setHasDraft] = useState(false);
|
||||||
const [showTips, setShowTips] = useState(data.loggedUserRank < 100);
|
const [showTips, setShowTips] = useState(data.loggedUserRank < 100);
|
||||||
|
const aCaptcha = useCaptchaModal('answer');
|
||||||
|
|
||||||
usePromptWithUnload({
|
usePromptWithUnload({
|
||||||
when: Boolean(formData.content.value),
|
when: Boolean(formData.content.value),
|
||||||
|
@ -135,12 +136,21 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
||||||
if (!checkValidated()) {
|
if (!checkValidated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
postAnswer({
|
|
||||||
|
aCaptcha.check(() => {
|
||||||
|
const params: PostAnswerReq = {
|
||||||
question_id: data?.qid,
|
question_id: data?.qid,
|
||||||
content: formData.content.value,
|
content: formData.content.value,
|
||||||
html: marked.parse(formData.content.value),
|
html: marked.parse(formData.content.value),
|
||||||
})
|
};
|
||||||
.then((res) => {
|
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);
|
setShowEditor(false);
|
||||||
setFormData({
|
setFormData({
|
||||||
content: {
|
content: {
|
||||||
|
@ -154,10 +164,12 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
||||||
})
|
})
|
||||||
.catch((ex) => {
|
.catch((ex) => {
|
||||||
if (ex.isError) {
|
if (ex.isError) {
|
||||||
|
aCaptcha.handleCaptchaError(ex.list);
|
||||||
const stateData = handleFormError(ex, formData);
|
const stateData = handleFormError(ex, formData);
|
||||||
setFormData({ ...stateData });
|
setFormData({ ...stateData });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickBtn = () => {
|
const clickBtn = () => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import dayjs from 'dayjs';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { handleFormError, scrollToDocTop } from '@/utils';
|
import { handleFormError, scrollToDocTop } from '@/utils';
|
||||||
import { usePageTags, usePromptWithUnload } from '@/hooks';
|
import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
import { Editor, EditorRef, Icon, htmlRender } from '@/components';
|
import { Editor, EditorRef, Icon, htmlRender } from '@/components';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
|
@ -51,6 +51,7 @@ const Index = () => {
|
||||||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||||
const [immData, setImmData] = useState(initFormData);
|
const [immData, setImmData] = useState(initFormData);
|
||||||
const [contentChanged, setContentChanged] = useState(false);
|
const [contentChanged, setContentChanged] = useState(false);
|
||||||
|
const editCaptcha = useCaptchaModal('edit');
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (data?.info?.content) {
|
if (data?.info?.content) {
|
||||||
|
@ -136,10 +137,12 @@ const Index = () => {
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (!checkValidated()) {
|
if (!checkValidated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editCaptcha.check(() => {
|
||||||
const params: Type.AnswerParams = {
|
const params: Type.AnswerParams = {
|
||||||
content: formData.content.value,
|
content: formData.content.value,
|
||||||
html: editorRef.current.getHtml(),
|
html: editorRef.current.getHtml(),
|
||||||
|
@ -147,8 +150,11 @@ const Index = () => {
|
||||||
id: aid,
|
id: aid,
|
||||||
edit_summary: formData.description.value,
|
edit_summary: formData.description.value,
|
||||||
};
|
};
|
||||||
|
editCaptcha.resolveCaptchaReq(params);
|
||||||
|
|
||||||
modifyAnswer(params)
|
modifyAnswer(params)
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
|
await editCaptcha.close();
|
||||||
navigate(
|
navigate(
|
||||||
pathFactory.answerLanding({
|
pathFactory.answerLanding({
|
||||||
questionId: qid,
|
questionId: qid,
|
||||||
|
@ -162,10 +168,12 @@ const Index = () => {
|
||||||
})
|
})
|
||||||
.catch((ex) => {
|
.catch((ex) => {
|
||||||
if (ex.isError) {
|
if (ex.isError) {
|
||||||
|
editCaptcha.handleCaptchaError(ex.list);
|
||||||
const stateData = handleFormError(ex, formData);
|
const stateData = handleFormError(ex, formData);
|
||||||
setFormData({ ...stateData });
|
setFormData({ ...stateData });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const handleSelectedRevision = (e) => {
|
const handleSelectedRevision = (e) => {
|
||||||
const index = e.target.value;
|
const index = e.target.value;
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { Row, Col, ListGroup } from 'react-bootstrap';
|
import { Row, Col, ListGroup } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
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 { Pagination } from '@/components';
|
||||||
import { useSearch } from '@/services';
|
import { getSearchResult } from '@/services';
|
||||||
|
import type { SearchParams, SearchRes } from '@/common/interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Head,
|
Head,
|
||||||
|
@ -21,15 +23,52 @@ const Index = () => {
|
||||||
const page = searchParams.get('page') || 1;
|
const page = searchParams.get('page') || 1;
|
||||||
const q = searchParams.get('q') || '';
|
const q = searchParams.get('q') || '';
|
||||||
const order = searchParams.get('order') || 'active';
|
const order = searchParams.get('order') || 'active';
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [data, setData] = useState<SearchRes>({
|
||||||
|
count: 0,
|
||||||
|
list: [],
|
||||||
|
extra: null,
|
||||||
|
});
|
||||||
|
const { count = 0, list = [], extra = null } = data || {};
|
||||||
|
|
||||||
const { data, isLoading } = useSearch({
|
const searchCaptcha = useCaptchaModal('search');
|
||||||
|
|
||||||
|
const doSearch = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const params: SearchParams = {
|
||||||
q,
|
q,
|
||||||
order,
|
order,
|
||||||
page: Number(page),
|
page: Number(page),
|
||||||
size: 20,
|
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]);
|
||||||
|
|
||||||
const { count = 0, list = [], extra = null } = data || {};
|
|
||||||
let pageTitle = t('search', { keyPrefix: 'page_title' });
|
let pageTitle = t('search', { keyPrefix: 'page_title' });
|
||||||
if (q) {
|
if (q) {
|
||||||
pageTitle = `${t('posts_containing', { keyPrefix: 'page_title' })} '${q}'`;
|
pageTitle = `${t('posts_containing', { keyPrefix: 'page_title' })} '${q}'`;
|
||||||
|
@ -37,6 +76,7 @@ const Index = () => {
|
||||||
usePageTags({
|
usePageTags({
|
||||||
title: pageTitle,
|
title: pageTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="pt-4 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Col className="page-main flex-auto">
|
<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 { Form, Button } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type {
|
import type { PasswordResetReq, FormDataType } from '@/common/interface';
|
||||||
ImgCodeRes,
|
import { resetPassword } from '@/services';
|
||||||
PasswordResetReq,
|
|
||||||
FormDataType,
|
|
||||||
} from '@/common/interface';
|
|
||||||
import { resetPassword, checkImgCode } from '@/services';
|
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
import { useCaptchaModal } from '@/hooks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
visible: boolean;
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
|
visible?: boolean;
|
||||||
callback: (param: number, email: string) => void;
|
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 { t } = useTranslation('translation', { keyPrefix: 'account_forgot' });
|
||||||
const [formData, setFormData] = useState<FormDataType>({
|
const [formData, setFormData] = useState<FormDataType>({
|
||||||
e_mail: {
|
e_mail: {
|
||||||
|
@ -24,26 +21,9 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
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 = () => {
|
const emailCaptcha = useCaptchaModal('email');
|
||||||
checkImgCode({
|
|
||||||
action: 'find_pass',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (params: FormDataType) => {
|
const handleChange = (params: FormDataType) => {
|
||||||
setFormData({ ...formData, ...params });
|
setFormData({ ...formData, ...params });
|
||||||
|
@ -73,27 +53,24 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
||||||
const params: PasswordResetReq = {
|
const params: PasswordResetReq = {
|
||||||
e_mail: formData.e_mail.value,
|
e_mail: formData.e_mail.value,
|
||||||
};
|
};
|
||||||
if (imgCode.verify) {
|
|
||||||
params.captcha_code = formData.captcha_code.value;
|
const captcha = emailCaptcha.getCaptcha();
|
||||||
params.captcha_id = imgCode.captcha_id;
|
if (captcha.verify) {
|
||||||
|
params.captcha_code = captcha.captcha_code;
|
||||||
|
params.captcha_id = captcha.captcha_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPassword(params)
|
resetPassword(params)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
|
await emailCaptcha.close();
|
||||||
callback?.(2, formData.e_mail.value);
|
callback?.(2, formData.e_mail.value);
|
||||||
setModalState(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
emailCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
|
||||||
setModalState(false);
|
|
||||||
}
|
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
getImgCode();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,22 +82,12 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgCode.verify) {
|
emailCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEmail();
|
sendEmail();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
getImgCode();
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||||
<Form.Group controlId="email" className="mb-3">
|
<Form.Group controlId="email" className="mb-3">
|
||||||
<Form.Label>{t('email.label')}</Form.Label>
|
<Form.Label>{t('email.label')}</Form.Label>
|
||||||
|
@ -150,19 +117,6 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<PicAuthCodeModal
|
|
||||||
visible={showModal}
|
|
||||||
data={{
|
|
||||||
captcha: formData.captcha_code,
|
|
||||||
imgCode,
|
|
||||||
}}
|
|
||||||
handleCaptcha={handleChange}
|
|
||||||
clickSubmit={sendEmail}
|
|
||||||
refreshImgCode={getImgCode}
|
|
||||||
onClose={() => setModalState(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import { FC, memo, useEffect, useState } from 'react';
|
import { FC, memo, useState } from 'react';
|
||||||
import { Form, Button } from 'react-bootstrap';
|
import { Form, Button } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import type {
|
import type { PasswordResetReq, FormDataType } from '@/common/interface';
|
||||||
ImgCodeRes,
|
|
||||||
PasswordResetReq,
|
|
||||||
FormDataType,
|
|
||||||
} from '@/common/interface';
|
|
||||||
import { loggedUserInfoStore } from '@/stores';
|
import { loggedUserInfoStore } from '@/stores';
|
||||||
import { changeEmail, checkImgCode } from '@/services';
|
import { changeEmail } from '@/services';
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
import { useCaptchaModal } from '@/hooks';
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
|
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
|
||||||
|
@ -21,28 +17,12 @@ const Index: FC = () => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
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 navigate = useNavigate();
|
||||||
const { user: userInfo, update: updateUser } = loggedUserInfoStore();
|
const { user: userInfo, update: updateUser } = loggedUserInfoStore();
|
||||||
|
|
||||||
const getImgCode = () => {
|
const emailCaptcha = useCaptchaModal('email');
|
||||||
checkImgCode({
|
|
||||||
action: 'e_mail',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (params: FormDataType) => {
|
const handleChange = (params: FormDataType) => {
|
||||||
setFormData({ ...formData, ...params });
|
setFormData({ ...formData, ...params });
|
||||||
|
@ -72,28 +52,25 @@ const Index: FC = () => {
|
||||||
const params: PasswordResetReq = {
|
const params: PasswordResetReq = {
|
||||||
e_mail: formData.e_mail.value,
|
e_mail: formData.e_mail.value,
|
||||||
};
|
};
|
||||||
|
const imgCode = emailCaptcha.getCaptcha();
|
||||||
if (imgCode.verify) {
|
if (imgCode.verify) {
|
||||||
params.captcha_code = formData.captcha_code.value;
|
params.captcha_code = imgCode.captcha_code;
|
||||||
params.captcha_id = imgCode.captcha_id;
|
params.captcha_id = imgCode.captcha_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
changeEmail(params)
|
changeEmail(params)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
|
await emailCaptcha.close();
|
||||||
userInfo.e_mail = formData.e_mail.value;
|
userInfo.e_mail = formData.e_mail.value;
|
||||||
updateUser(userInfo);
|
updateUser(userInfo);
|
||||||
navigate('/users/login', { replace: true });
|
navigate('/users/login', { replace: true });
|
||||||
setModalState(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
emailCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
|
||||||
setModalState(false);
|
|
||||||
}
|
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
getImgCode();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,24 +81,16 @@ const Index: FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgCode.verify) {
|
emailCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEmail();
|
sendEmail();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
navigate('/users/login?status=inactive', { replace: true });
|
navigate('/users/login?status=inactive', { replace: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getImgCode();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
||||||
<Form.Group controlId="email" className="mb-3">
|
<Form.Group controlId="email" className="mb-3">
|
||||||
<Form.Label>{t('email.label')}</Form.Label>
|
<Form.Label>{t('email.label')}</Form.Label>
|
||||||
|
@ -154,19 +123,6 @@ const Index: FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<PicAuthCodeModal
|
|
||||||
visible={showModal}
|
|
||||||
data={{
|
|
||||||
captcha: formData.captcha_code,
|
|
||||||
imgCode,
|
|
||||||
}}
|
|
||||||
handleCaptcha={handleChange}
|
|
||||||
clickSubmit={sendEmail}
|
|
||||||
refreshImgCode={getImgCode}
|
|
||||||
onClose={() => setModalState(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,8 @@ import { Container, Form, Button, Col } from 'react-bootstrap';
|
||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { usePageTags } from '@/hooks';
|
import { usePageTags, useCaptchaModal } from '@/hooks';
|
||||||
import type {
|
import type { LoginReqParams, FormDataType } from '@/common/interface';
|
||||||
LoginReqParams,
|
|
||||||
ImgCodeRes,
|
|
||||||
FormDataType,
|
|
||||||
} from '@/common/interface';
|
|
||||||
import { Unactivate, WelcomeTitle, PluginRender } from '@/components';
|
import { Unactivate, WelcomeTitle, PluginRender } from '@/components';
|
||||||
import {
|
import {
|
||||||
loggedUserInfoStore,
|
loggedUserInfoStore,
|
||||||
|
@ -16,14 +12,12 @@ import {
|
||||||
userCenterStore,
|
userCenterStore,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { floppyNavigation, guard, handleFormError, userCenter } from '@/utils';
|
import { floppyNavigation, guard, handleFormError, userCenter } from '@/utils';
|
||||||
import { login, checkImgCode, UcAgent } from '@/services';
|
import { login, UcAgent } from '@/services';
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
|
||||||
|
|
||||||
const Index: React.FC = () => {
|
const Index: React.FC = () => {
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [refresh, setRefresh] = useState(0);
|
|
||||||
const { user: storeUser, update: updateUser } = loggedUserInfoStore((_) => _);
|
const { user: storeUser, update: updateUser } = loggedUserInfoStore((_) => _);
|
||||||
const loginSetting = loginSettingStore((state) => state.login);
|
const loginSetting = loginSettingStore((state) => state.login);
|
||||||
const ucAgent = userCenterStore().agent;
|
const ucAgent = userCenterStore().agent;
|
||||||
|
@ -45,34 +39,15 @@ const Index: React.FC = () => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
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 [step, setStep] = useState(1);
|
||||||
|
|
||||||
const handleChange = (params: FormDataType) => {
|
const handleChange = (params: FormDataType) => {
|
||||||
setFormData({ ...formData, ...params });
|
setFormData({ ...formData, ...params });
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImgCode = () => {
|
const passwordCaptcha = useCaptchaModal('password');
|
||||||
if (!canOriginalLogin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
checkImgCode({
|
|
||||||
action: 'login',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkValidated = (): boolean => {
|
const checkValidated = (): boolean => {
|
||||||
let bol = true;
|
let bol = true;
|
||||||
|
@ -110,34 +85,31 @@ const Index: React.FC = () => {
|
||||||
e_mail: formData.e_mail.value,
|
e_mail: formData.e_mail.value,
|
||||||
pass: formData.pass.value,
|
pass: formData.pass.value,
|
||||||
};
|
};
|
||||||
if (imgCode.verify) {
|
|
||||||
params.captcha_code = formData.captcha_code.value;
|
const captcha = passwordCaptcha.getCaptcha();
|
||||||
params.captcha_id = imgCode.captcha_id;
|
if (captcha?.verify) {
|
||||||
|
params.captcha_code = captcha.captcha_code;
|
||||||
|
params.captcha_id = captcha.captcha_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
login(params)
|
login(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
passwordCaptcha.close();
|
||||||
updateUser(res);
|
updateUser(res);
|
||||||
const userStat = guard.deriveLoginState();
|
const userStat = guard.deriveLoginState();
|
||||||
if (userStat.isNotActivated) {
|
if (userStat.isNotActivated) {
|
||||||
// inactive
|
// inactive
|
||||||
setStep(2);
|
setStep(2);
|
||||||
setRefresh((pre) => pre + 1);
|
|
||||||
} else {
|
} else {
|
||||||
guard.handleLoginRedirect(navigate);
|
guard.handleLoginRedirect(navigate);
|
||||||
}
|
}
|
||||||
|
|
||||||
setModalState(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
|
||||||
setModalState(false);
|
|
||||||
}
|
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
|
passwordCaptcha.handleCaptchaError(err.list);
|
||||||
}
|
}
|
||||||
setRefresh((pre) => pre + 1);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,18 +121,11 @@ const Index: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgCode.verify) {
|
passwordCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLogin();
|
handleLogin();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getImgCode();
|
|
||||||
}, [refresh]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isInactive = searchParams.get('status');
|
const isInactive = searchParams.get('status');
|
||||||
|
|
||||||
|
@ -168,6 +133,7 @@ const Index: React.FC = () => {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
usePageTags({
|
usePageTags({
|
||||||
title: t('login', { keyPrefix: 'page_title' }),
|
title: t('login', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
|
@ -263,18 +229,6 @@ const Index: React.FC = () => {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{step === 2 && <Unactivate visible={step === 2} />}
|
{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>
|
</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 { Form, Button } from 'react-bootstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
import { useCaptchaModal } from '@/hooks';
|
||||||
import { ImgCodeRes } from '@/common/interface';
|
|
||||||
import type { FormDataType, RegisterReqParams } from '@/common/interface';
|
import type { FormDataType, RegisterReqParams } from '@/common/interface';
|
||||||
import {
|
import { register, useLegalTos, useLegalPrivacy } from '@/services';
|
||||||
register,
|
|
||||||
getRegisterCaptcha,
|
|
||||||
useLegalTos,
|
|
||||||
useLegalPrivacy,
|
|
||||||
} from '@/services';
|
|
||||||
import userStore from '@/stores/loggedUserInfo';
|
import userStore from '@/stores/loggedUserInfo';
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
|
||||||
|
@ -37,25 +31,11 @@ const Index: React.FC<Props> = ({ callback }) => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
},
|
},
|
||||||
captcha_code: {
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const updateUser = userStore((state) => state.update);
|
|
||||||
|
|
||||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
const updateUser = userStore((state) => state.update);
|
||||||
captcha_id: '',
|
const emailCaptcha = useCaptchaModal('email');
|
||||||
captcha_img: '',
|
|
||||||
verify: false,
|
|
||||||
});
|
|
||||||
const [showModal, setModalState] = useState(false);
|
|
||||||
const getImgCode = () => {
|
|
||||||
getRegisterCaptcha().then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleChange = (params: FormDataType) => {
|
const handleChange = (params: FormDataType) => {
|
||||||
setFormData({ ...formData, ...params });
|
setFormData({ ...formData, ...params });
|
||||||
};
|
};
|
||||||
|
@ -86,6 +66,7 @@ const Index: React.FC<Props> = ({ callback }) => {
|
||||||
});
|
});
|
||||||
return bol;
|
return bol;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: tos } = useLegalTos();
|
const { data: tos } = useLegalTos();
|
||||||
const { data: privacy } = useLegalPrivacy();
|
const { data: privacy } = useLegalPrivacy();
|
||||||
const argumentClick = (evt: MouseEvent, type: 'tos' | 'privacy') => {
|
const argumentClick = (evt: MouseEvent, type: 'tos' | 'privacy') => {
|
||||||
|
@ -117,25 +98,24 @@ const Index: React.FC<Props> = ({ callback }) => {
|
||||||
pass: formData.pass.value,
|
pass: formData.pass.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (imgCode.verify) {
|
const captcha = emailCaptcha.getCaptcha();
|
||||||
reqParams.captcha_code = formData.captcha_code.value;
|
if (captcha?.verify) {
|
||||||
reqParams.captcha_id = imgCode.captcha_id;
|
reqParams.captcha_code = captcha.captcha_code;
|
||||||
|
reqParams.captcha_id = captcha.captcha_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
register(reqParams)
|
register(reqParams)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
emailCaptcha.close();
|
||||||
updateUser(res);
|
updateUser(res);
|
||||||
setModalState(false);
|
|
||||||
callback();
|
callback();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
emailCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
|
||||||
setModalState(false);
|
|
||||||
}
|
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
getImgCode();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,15 +125,11 @@ const Index: React.FC<Props> = ({ callback }) => {
|
||||||
if (!checkValidated()) {
|
if (!checkValidated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (imgCode.verify) {
|
emailCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleRegister();
|
handleRegister();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
getImgCode();
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
|
<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>
|
Already have an account? <Link to="/users/login">Log in</Link>
|
||||||
</Trans>
|
</Trans>
|
||||||
</div>
|
</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 { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
import { useToast } from '@/hooks';
|
import { useToast, useCaptchaModal } from '@/hooks';
|
||||||
import { getLoggedUserInfo, changeEmail, checkImgCode } from '@/services';
|
import { getLoggedUserInfo, changeEmail } from '@/services';
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
import { PicAuthCodeModal } from '@/components';
|
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'settings.account',
|
keyPrefix: 'settings.account',
|
||||||
});
|
});
|
||||||
const [step, setStep] = useState(1);
|
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>({
|
const [formData, setFormData] = useState<Type.FormDataType>({
|
||||||
e_mail: {
|
e_mail: {
|
||||||
value: '',
|
value: '',
|
||||||
|
@ -30,28 +23,17 @@ const Index: FC = () => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
},
|
},
|
||||||
captcha_code: {
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const [userInfo, setUserInfo] = useState<Type.UserInfoRes>();
|
const [userInfo, setUserInfo] = useState<Type.UserInfoRes>();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const emailCaptcha = useCaptchaModal('edit_userinfo');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLoggedUserInfo().then((resp) => {
|
getLoggedUserInfo().then((resp) => {
|
||||||
setUserInfo(resp);
|
setUserInfo(resp);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getImgCode = () => {
|
|
||||||
checkImgCode({
|
|
||||||
action: 'e_mail',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (params: Type.FormDataType) => {
|
const handleChange = (params: Type.FormDataType) => {
|
||||||
setFormData({ ...formData, ...params });
|
setFormData({ ...formData, ...params });
|
||||||
};
|
};
|
||||||
|
@ -95,11 +77,6 @@ const Index: FC = () => {
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
},
|
},
|
||||||
captcha_code: {
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,14 +89,15 @@ const Index: FC = () => {
|
||||||
pass: formData.pass.value,
|
pass: formData.pass.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const imgCode = emailCaptcha.getCaptcha();
|
||||||
if (imgCode.verify) {
|
if (imgCode.verify) {
|
||||||
params.captcha_code = formData.captcha_code.value;
|
params.captcha_code = imgCode.captcha_code;
|
||||||
params.captcha_id = imgCode.captcha_id;
|
params.captcha_id = imgCode.captcha_id;
|
||||||
}
|
}
|
||||||
changeEmail(params)
|
changeEmail(params)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
|
await emailCaptcha.close();
|
||||||
setStep(1);
|
setStep(1);
|
||||||
setModalState(false);
|
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
msg: t('change_email_info'),
|
msg: t('change_email_info'),
|
||||||
variant: 'warning',
|
variant: 'warning',
|
||||||
|
@ -128,15 +106,10 @@ const Index: FC = () => {
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
emailCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
setFormData({ ...data });
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgCode.verify) {
|
emailCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
postEmail();
|
postEmail();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -174,7 +145,6 @@ const Index: FC = () => {
|
||||||
variant="outline-secondary"
|
variant="outline-secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
getImgCode();
|
|
||||||
}}>
|
}}>
|
||||||
{t('change_email_btn')}
|
{t('change_email_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -240,18 +210,6 @@ const Index: FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PicAuthCodeModal
|
|
||||||
visible={showModal}
|
|
||||||
data={{
|
|
||||||
captcha: formData.captcha_code,
|
|
||||||
imgCode,
|
|
||||||
}}
|
|
||||||
handleCaptcha={handleChange}
|
|
||||||
clickSubmit={postEmail}
|
|
||||||
refreshImgCode={getImgCode}
|
|
||||||
onClose={() => setModalState(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,12 +4,11 @@ import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import classname from 'classnames';
|
import classname from 'classnames';
|
||||||
|
|
||||||
import { useToast } from '@/hooks';
|
import { useToast, useCaptchaModal } from '@/hooks';
|
||||||
import type { FormDataType, ImgCodeRes } from '@/common/interface';
|
import type { FormDataType } from '@/common/interface';
|
||||||
import { modifyPassword, checkImgCode } from '@/services';
|
import { modifyPassword } from '@/services';
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
import { loggedUserInfoStore } from '@/stores';
|
import { loggedUserInfoStore } from '@/stores';
|
||||||
import { PicAuthCodeModal } from '@/components';
|
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
|
@ -35,20 +34,8 @@ const Index: FC = () => {
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [showModal, setModalState] = useState(false);
|
|
||||||
const [imgCode, setImgCode] = useState<ImgCodeRes>({
|
|
||||||
captcha_id: '',
|
|
||||||
captcha_img: '',
|
|
||||||
verify: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getImgCode = () => {
|
const infoCaptcha = useCaptchaModal('edit_userinfo');
|
||||||
checkImgCode({
|
|
||||||
action: 'modify_pass',
|
|
||||||
}).then((res) => {
|
|
||||||
setImgCode(res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormState = () => {
|
const handleFormState = () => {
|
||||||
setFormState((pre) => !pre);
|
setFormState((pre) => !pre);
|
||||||
|
@ -128,13 +115,14 @@ const Index: FC = () => {
|
||||||
pass: formData.pass.value,
|
pass: formData.pass.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const imgCode = infoCaptcha.getCaptcha();
|
||||||
if (imgCode.verify) {
|
if (imgCode.verify) {
|
||||||
params.captcha_code = formData.captcha_code.value;
|
params.captcha_code = imgCode.captcha_code;
|
||||||
params.captcha_id = imgCode.captcha_id;
|
params.captcha_id = imgCode.captcha_id;
|
||||||
}
|
}
|
||||||
modifyPassword(params)
|
modifyPassword(params)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
setModalState(false);
|
await infoCaptcha.close();
|
||||||
toast.onShow({
|
toast.onShow({
|
||||||
msg: t('update_password', { keyPrefix: 'toast' }),
|
msg: t('update_password', { keyPrefix: 'toast' }),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
|
@ -143,15 +131,10 @@ const Index: FC = () => {
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
infoCaptcha.handleCaptchaError(err.list);
|
||||||
const data = handleFormError(err, formData);
|
const data = handleFormError(err, formData);
|
||||||
if (!err.list.find((v) => v.error_field.indexOf('captcha') >= 0)) {
|
|
||||||
setModalState(false);
|
|
||||||
}
|
|
||||||
setFormData({ ...data });
|
setFormData({ ...data });
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
getImgCode();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,11 +145,9 @@ const Index: FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgCode.verify) {
|
infoCaptcha.check(() => {
|
||||||
setModalState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
postModifyPass();
|
postModifyPass();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -262,24 +243,11 @@ const Index: FC = () => {
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleFormState();
|
handleFormState();
|
||||||
getImgCode();
|
|
||||||
}}>
|
}}>
|
||||||
{t('change_pass_btn')}
|
{t('change_pass_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PicAuthCodeModal
|
|
||||||
visible={showModal}
|
|
||||||
data={{
|
|
||||||
captcha: formData.captcha_code,
|
|
||||||
imgCode,
|
|
||||||
}}
|
|
||||||
handleCaptcha={handleChange}
|
|
||||||
clickSubmit={postModifyPass}
|
|
||||||
refreshImgCode={getImgCode}
|
|
||||||
onClose={() => setModalState(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
</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';
|
const apiUrl = '/answer/api/v1/question/invite';
|
||||||
return request.put(apiUrl, {
|
return request.put(apiUrl, {
|
||||||
id: questionId,
|
id: questionId,
|
||||||
invite_user: users,
|
invite_user: users,
|
||||||
|
...imgCode,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,10 @@
|
||||||
import useSWR from 'swr';
|
|
||||||
import qs from 'qs';
|
|
||||||
|
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type * as Type from '@/common/interface';
|
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 apiUrl = '/answer/api/v1/search';
|
||||||
const queryParams = qs.stringify(params, { skipNulls: true });
|
|
||||||
const { data, error, mutate } = useSWR<Type.SearchRes, Error>(
|
return request.get<Type.SearchRes>(apiUrl, {
|
||||||
params?.q ? `${apiUrl}?${queryParams}` : null,
|
params,
|
||||||
request.instance.get,
|
});
|
||||||
);
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
isLoading: !data && !error,
|
|
||||||
error,
|
|
||||||
mutate,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,9 +60,10 @@ export const updateComment = (params) => {
|
||||||
return request.put('/answer/api/v1/comment', 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', {
|
return request.delete('/answer/api/v1/comment', {
|
||||||
comment_id: id,
|
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);
|
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 = () => {
|
export const logout = () => {
|
||||||
return request.get('/answer/api/v1/user/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) => {
|
export const resendEmail = (params?: Type.ImgCodeReq) => {
|
||||||
params = qs.parse(
|
params = qs.parse(
|
||||||
qs.stringify(params, {
|
qs.stringify(params, {
|
||||||
|
@ -134,19 +126,19 @@ export const getLoggedUserInfo = (config = { passingError: false }) => {
|
||||||
return request.get<Type.UserInfoRes>('/answer/api/v1/user/info', config);
|
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) => {
|
export const modifyUserInfo = (params: Type.ModifyUserReq) => {
|
||||||
return request.put('/answer/api/v1/user/info', params);
|
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) => {
|
export const resetPassword = (params: Type.PasswordResetReq) => {
|
||||||
return request.post('/answer/api/v1/user/password/reset', params);
|
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);
|
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 });
|
return request.post(`/answer/api/v1/user/email/verification`, { code });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkImgCode = (params: Type.CheckImgReq) => {
|
export const checkImgCode = (k: Type.CaptchaKey) => {
|
||||||
return request.get<Type.ImgCodeRes>(
|
const apiUrl = `/answer/api/v1/user/action/record`;
|
||||||
`/answer/api/v1/user/action/record?${qs.stringify(params)}`,
|
return request.get<Type.ImgCodeRes>(apiUrl, {
|
||||||
);
|
params: {
|
||||||
|
action: k,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setNotice = (params: Type.SetNoticeReq) => {
|
export const setNotice = (params: Type.SetNoticeReq) => {
|
||||||
|
@ -189,7 +184,7 @@ export const bookmark = (params: { group_id: string; object_id: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postVote = (
|
export const postVote = (
|
||||||
params: { object_id: string; is_cancel: boolean },
|
params: { object_id: string; is_cancel: boolean } & Type.ImgCodeReq,
|
||||||
type: 'down' | 'up',
|
type: 'down' | 'up',
|
||||||
) => {
|
) => {
|
||||||
return request.post(`/answer/api/v1/vote/${type}`, params);
|
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}`);
|
return request.get(`${api}?object_type=${type}&action=${action}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postReport = (params: {
|
export const postReport = (
|
||||||
|
params: {
|
||||||
source: Type.ReportType;
|
source: Type.ReportType;
|
||||||
content: string;
|
content: string;
|
||||||
object_id: string;
|
object_id: string;
|
||||||
report_type: number;
|
report_type: number;
|
||||||
}) => {
|
} & Type.ImgCodeReq,
|
||||||
|
) => {
|
||||||
return request.post('/answer/api/v1/report', params);
|
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);
|
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);
|
return request.delete('/answer/api/v1/answer', params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ import i18next from 'i18next';
|
||||||
|
|
||||||
import pattern from '@/common/pattern';
|
import pattern from '@/common/pattern';
|
||||||
import { USER_AGENT_NAMES } from '@/common/constants';
|
import { USER_AGENT_NAMES } from '@/common/constants';
|
||||||
|
import type * as Type from '@/common/interface';
|
||||||
const Diff = require('diff');
|
|
||||||
|
|
||||||
function thousandthDivision(num) {
|
function thousandthDivision(num) {
|
||||||
const reg = /\d{1,3}(?=(\d{3})+$)/g;
|
const reg = /\d{1,3}(?=(\d{3})+$)/g;
|
||||||
|
@ -114,7 +113,7 @@ function escapeRemove(str: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFormError(
|
function handleFormError(
|
||||||
error: { list: Array<{ error_field: string; error_msg: string }> },
|
error: { list: Type.FieldError[] },
|
||||||
data: any,
|
data: any,
|
||||||
keymap?: Array<{ from: string; to: string }>,
|
keymap?: Array<{ from: string; to: string }>,
|
||||||
) {
|
) {
|
||||||
|
@ -148,6 +147,8 @@ function escapeHtml(str: string) {
|
||||||
return str.replace(/[&<>"'`]/g, (tag) => tagsToReplace[tag] || tag);
|
return str.replace(/[&<>"'`]/g, (tag) => tagsToReplace[tag] || tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Diff = require('diff');
|
||||||
|
|
||||||
function diffText(newText: string, oldText?: string): string {
|
function diffText(newText: string, oldText?: string): string {
|
||||||
if (!newText) {
|
if (!newText) {
|
||||||
return '';
|
return '';
|
||||||
|
|
|
@ -94,6 +94,10 @@ export interface NavigateConfig {
|
||||||
}
|
}
|
||||||
const navigate = (to: string | number, config: NavigateConfig = {}) => {
|
const navigate = (to: string | number, config: NavigateConfig = {}) => {
|
||||||
let { handler = 'href' } = config;
|
let { handler = 'href' } = config;
|
||||||
|
/**
|
||||||
|
* 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 (to && typeof to === 'string') {
|
||||||
if (equalToCurrentHref(to)) {
|
if (equalToCurrentHref(to)) {
|
||||||
return;
|
return;
|
||||||
|
@ -121,9 +125,11 @@ const navigate = (to: string | number, config: NavigateConfig = {}) => {
|
||||||
handler(to, config.options);
|
handler(to, config.options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof to === 'number' && typeof handler === 'function') {
|
if (typeof to === 'number' && typeof handler === 'function') {
|
||||||
handler(to);
|
handler(to);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -61,6 +61,7 @@ class Request {
|
||||||
config: errConfig,
|
config: errConfig,
|
||||||
} = error.response || {};
|
} = error.response || {};
|
||||||
const { data = {}, msg = '' } = errBody || {};
|
const { data = {}, msg = '' } = errBody || {};
|
||||||
|
|
||||||
const errorObject: {
|
const errorObject: {
|
||||||
code: any;
|
code: any;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
@ -74,6 +75,7 @@ class Request {
|
||||||
msg,
|
msg,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status === 400) {
|
if (status === 400) {
|
||||||
if (data?.err_type && errConfig?.passingError) {
|
if (data?.err_type && errConfig?.passingError) {
|
||||||
return Promise.reject(errorObject);
|
return Promise.reject(errorObject);
|
||||||
|
@ -127,6 +129,7 @@ class Request {
|
||||||
floppyNavigation.navigateToLogin();
|
floppyNavigation.navigateToLogin();
|
||||||
return Promise.reject(false);
|
return Promise.reject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
// Permission interception
|
// Permission interception
|
||||||
if (data?.type === 'url_expired') {
|
if (data?.type === 'url_expired') {
|
||||||
|
@ -173,6 +176,7 @@ class Request {
|
||||||
errorCodeStore.getState().update('404');
|
errorCodeStore.getState().update('404');
|
||||||
return Promise.reject(false);
|
return Promise.reject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status >= 500) {
|
if (status >= 500) {
|
||||||
if (isIgnoredPath(IGNORE_PATH_LIST)) {
|
if (isIgnoredPath(IGNORE_PATH_LIST)) {
|
||||||
return Promise.reject(false);
|
return Promise.reject(false);
|
||||||
|
|
Loading…
Reference in New Issue