Merge branch 'ui' into 'main'

Ui

See merge request opensource/answer!19
This commit is contained in:
Li Shuailing 2022-09-29 08:42:20 +00:00
commit 8287915659
44 changed files with 206 additions and 362 deletions

View File

@ -26,6 +26,7 @@ module.exports = {
const config = configFunction(proxy, allowedHost); const config = configFunction(proxy, allowedHost);
config.proxy = { config.proxy = {
'/answer': { '/answer': {
// target: "http://10.0.20.84:8080",
target: 'http://10.0.10.98:2060', target: 'http://10.0.10.98:2060',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,

View File

@ -46,7 +46,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.5.0", "react-bootstrap": "^2.5.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0", "react-helmet-async": "^1.3.0",
"react-i18next": "^11.18.3", "react-i18next": "^11.18.3",
"react-router-dom": "^6.4.0", "react-router-dom": "^6.4.0",
"swr": "^1.3.0", "swr": "^1.3.0",

View File

@ -67,7 +67,7 @@ specifiers:
react-app-rewired: ^2.2.1 react-app-rewired: ^2.2.1
react-bootstrap: ^2.5.0 react-bootstrap: ^2.5.0
react-dom: ^18.2.0 react-dom: ^18.2.0
react-helmet: ^6.1.0 react-helmet-async: ^1.3.0
react-i18next: ^11.18.3 react-i18next: ^11.18.3
react-router-dom: ^6.4.0 react-router-dom: ^6.4.0
react-scripts: 5.0.1 react-scripts: 5.0.1
@ -102,7 +102,7 @@ dependencies:
react: 18.2.0 react: 18.2.0
react-bootstrap: 2.5.0_7ey2zzynotv32rpkwno45fsx4e react-bootstrap: 2.5.0_7ey2zzynotv32rpkwno45fsx4e
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
react-helmet: 6.1.0_react@18.2.0 react-helmet-async: 1.3.0_biqbaboplfbrettd7655fr4n2y
react-i18next: 11.18.6_ulhmqqxshznzmtuvahdi5nasbq react-i18next: 11.18.6_ulhmqqxshznzmtuvahdi5nasbq
react-router-dom: 6.4.0_biqbaboplfbrettd7655fr4n2y react-router-dom: 6.4.0_biqbaboplfbrettd7655fr4n2y
swr: 1.3.0_react@18.2.0 swr: 1.3.0_react@18.2.0
@ -9581,16 +9581,19 @@ packages:
resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==} resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==}
dev: false dev: false
/react-helmet/6.1.0_react@18.2.0: /react-helmet-async/1.3.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==} resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
peerDependencies: peerDependencies:
react: '>=16.3.0' react: ^16.6.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
dependencies: dependencies:
object-assign: 4.1.1 '@babel/runtime': 7.19.0
invariant: 2.2.4
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-fast-compare: 3.2.0 react-fast-compare: 3.2.0
react-side-effect: 2.1.2_react@18.2.0 shallowequal: 1.1.0
dev: false dev: false
/react-i18next/11.18.6_ulhmqqxshznzmtuvahdi5nasbq: /react-i18next/11.18.6_ulhmqqxshznzmtuvahdi5nasbq:
@ -9747,14 +9750,6 @@ packages:
- webpack-hot-middleware - webpack-hot-middleware
- webpack-plugin-serve - webpack-plugin-serve
/react-side-effect/2.1.2_react@18.2.0:
resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==}
peerDependencies:
react: ^16.3.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies: peerDependencies:
@ -10257,6 +10252,10 @@ packages:
/setprototypeof/1.2.0: /setprototypeof/1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
/shallowequal/1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
dev: false
/shebang-command/2.0.0: /shebang-command/2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}

View File

@ -80,7 +80,7 @@ export interface RegisterReqParams extends LoginReqParams {
name: string; name: string;
} }
export interface ModifyPassReq { export interface ModifyPasswordReq {
old_pass: string; old_pass: string;
pass: string; pass: string;
} }
@ -137,7 +137,7 @@ export interface ImgCodeRes {
verify: boolean; verify: boolean;
} }
export interface PssRetReq extends ImgCodeReq { export interface PasswordResetReq extends ImgCodeReq {
e_mail: string; e_mail: string;
} }
@ -145,11 +145,11 @@ export interface CheckImgReq {
action: 'login' | 'e_mail' | 'find_pass'; action: 'login' | 'e_mail' | 'find_pass';
} }
export interface NoticeSetReq { export interface SetNoticeReq {
notice_switch: boolean; notice_switch: boolean;
} }
export interface QuDetailRes { export interface QuestionDetailRes {
id: string; id: string;
title: string; title: string;
content: string; content: string;

View File

@ -5,23 +5,35 @@ import { Avatar } from '@answer/components';
interface Props { interface Props {
data: any; data: any;
showAvatar?: boolean;
avatarSize?: string; avatarSize?: string;
className?: string; className?: string;
} }
const Index: FC<Props> = ({ const Index: FC<Props> = ({
data, data,
showAvatar = true,
avatarSize = '20px', avatarSize = '20px',
className = 'fs-14', className = 'fs-14',
}) => { }) => {
return ( return (
<div className={`text-secondary ${className}`}> <div className={`text-secondary ${className}`}>
{data.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`}> <Link to={`/users/${data?.username}`}>
{showAvatar && (
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" /> <Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
)}
<span className="me-1 text-break">{data?.display_name}</span>
</Link> </Link>
<Link to={`/users/${data?.username}`} className="me-1 text-break"> ) : (
{data?.display_name} <>
</Link> {showAvatar && (
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
)}
<span className="me-1 text-break">{data?.display_name}</span>
</>
)}
<span className="fw-bold">{data?.rank}</span> <span className="fw-bold">{data?.rank}</span>
</div> </div>
); );

View File

@ -17,15 +17,20 @@ const ActionBar = ({
onReply, onReply,
onVote, onVote,
onAction, onAction,
userStatus = '',
}) => { }) => {
const { t } = useTranslation('translation', { keyPrefix: 'comment' }); const { t } = useTranslation('translation', { keyPrefix: 'comment' });
return ( return (
<div className="d-flex justify-content-between fs-14"> <div className="d-flex justify-content-between fs-14">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center text-secondary">
{userStatus !== 'deleted' ? (
<Link to={`/users/${username}`}>{nickName}</Link> <Link to={`/users/${username}`}>{nickName}</Link>
<span className="mx-1 text-secondary"></span> ) : (
<FormatTime time={createdAt} className="text-secondary me-3" /> <span>{nickName}</span>
)}
<span className="mx-1"></span>
<FormatTime time={createdAt} className="me-3" />
<Button <Button
variant="link" variant="link"
size="sm" size="sm"

View File

@ -36,7 +36,7 @@ const Form = ({
<Mentions pageUsers={pageUsers.getUsers()}> <Mentions pageUsers={pageUsers.getUsers()}>
<TextArea size="sm" value={value} onChange={handleChange} /> <TextArea size="sm" value={value} onChange={handleChange} />
</Mentions> </Mentions>
<div className="text-muted fs-14">{t(`tip_${mode}`)}</div> <div className="form-text">{t(`tip_${mode}`)}</div>
</div> </div>
{type === 'edit' ? ( {type === 'edit' ? (
<div className="d-flex flex-column"> <div className="d-flex flex-column">

View File

@ -22,7 +22,7 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
<Mentions pageUsers={pageUsers.getUsers()}> <Mentions pageUsers={pageUsers.getUsers()}>
<TextArea size="sm" value={value} onChange={handleChange} /> <TextArea size="sm" value={value} onChange={handleChange} />
</Mentions> </Mentions>
<div className="text-muted fs-14">{t(`tip_${mode}`)}</div> <div className="form-text">{t(`tip_${mode}`)}</div>
</div> </div>
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<Button <Button

View File

@ -269,6 +269,7 @@ const Comment = ({ objectId, mode }) => {
voteCount={item.vote_count} voteCount={item.vote_count}
isVote={item.is_vote} isVote={item.is_vote}
memberActions={item.member_actions} memberActions={item.member_actions}
userStatus={item.user_status}
onReply={() => { onReply={() => {
handleReply(item.comment_id); handleReply(item.comment_id);
}} }}

View File

@ -14,7 +14,7 @@ import { useSearchParams, NavLink, Link, useNavigate } from 'react-router-dom';
import { Avatar, Icon } from '@answer/components'; import { Avatar, Icon } from '@answer/components';
import { userInfoStore, siteInfoStore, interfaceStore } from '@answer/stores'; import { userInfoStore, siteInfoStore, interfaceStore } from '@answer/stores';
import { logout, useQueryNotificationRedDot } from '@answer/api'; import { logout, useQueryNotificationStatus } from '@answer/api';
import Storage from '@answer/utils/storage'; import Storage from '@answer/utils/storage';
import './index.scss'; import './index.scss';
@ -28,7 +28,7 @@ const Header: FC = () => {
const [searchStr, setSearch] = useState(''); const [searchStr, setSearch] = useState('');
const siteInfo = siteInfoStore((state) => state.siteInfo); const siteInfo = siteInfoStore((state) => state.siteInfo);
const { interface: interfaceInfo } = interfaceStore(); const { interface: interfaceInfo } = interfaceStore();
const { data: redDot } = useQueryNotificationRedDot(); const { data: redDot } = useQueryNotificationStatus();
const handleInput = (val) => { const handleInput = (val) => {
setSearch(val); setSearch(val);
}; };

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Modal } from '@answer/components'; import { Modal } from '@answer/components';
import { useReportModal, useToast } from '@answer/hooks'; import { useReportModal, useToast } from '@answer/hooks';
import { questionDelete, answerDelete } from '@answer/api'; import { deleteQuestion, deleteAnswer } from '@answer/api';
import { isLogin } from '@answer/utils'; import { isLogin } from '@answer/utils';
import Share from '../Share'; import Share from '../Share';
@ -61,7 +61,7 @@ const Index: FC<IProps> = ({
confirmBtnVariant: 'danger', confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }), confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => { onConfirm: () => {
questionDelete({ deleteQuestion({
id: qid, id: qid,
}).then(() => { }).then(() => {
toast.onShow({ toast.onShow({
@ -82,7 +82,7 @@ const Index: FC<IProps> = ({
confirmBtnVariant: 'danger', confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }), confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => { onConfirm: () => {
answerDelete({ deleteAnswer({
id: aid, id: aid,
}).then(() => { }).then(() => {
// refersh page // refersh page

View File

@ -1,4 +1,4 @@
import { FC, memo } from 'react'; import { FC } from 'react';
import { Pagination } from 'react-bootstrap'; import { Pagination } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom'; import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
@ -47,6 +47,7 @@ const PageItem = ({ page, currentPage, path }: PageItemProps) => {
href={path} href={path}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
navigate(path); navigate(path);
window.scrollTo(0, 0); window.scrollTo(0, 0);
}}> }}>
@ -111,7 +112,7 @@ const Index: FC<Props> = ({
)} )}
{currentPage === 4 && totalPage > 6 && ( {currentPage === 4 && totalPage > 6 && (
<PageItem <PageItem
key="6" key="page6"
page={6} page={6}
currentPage={currentPage} currentPage={currentPage}
path={handleParams(6)} path={handleParams(6)}
@ -133,13 +134,13 @@ const Index: FC<Props> = ({
{currentPage >= 5 && ( {currentPage >= 5 && (
<> <>
<PageItem <PageItem
key="prev2" key={realPage - 2}
page={realPage - 2} page={realPage - 2}
currentPage={currentPage} currentPage={currentPage}
path={handleParams(realPage - 2)} path={handleParams(realPage - 2)}
/> />
<PageItem <PageItem
key="prev1" key={realPage - 1}
page={realPage - 1} page={realPage - 1}
currentPage={currentPage} currentPage={currentPage}
path={handleParams(realPage - 1)} path={handleParams(realPage - 1)}
@ -194,4 +195,4 @@ const Index: FC<Props> = ({
); );
}; };
export default memo(Index); export default Index;

View File

@ -5,7 +5,14 @@ import { useTranslation } from 'react-i18next';
import { useQuestionList } from '@answer/api'; import { useQuestionList } from '@answer/api';
import type * as Type from '@answer/common/interface'; import type * as Type from '@answer/common/interface';
import { Icon, Tag, Pagination, FormatTime, Empty } from '@answer/components'; import {
Icon,
Tag,
Pagination,
FormatTime,
Empty,
BaseUserCard,
} from '@answer/components';
const QuestionOrderKeys: Type.QuestionOrderBy[] = [ const QuestionOrderKeys: Type.QuestionOrderBy[] = [
'newest', 'newest',
@ -25,12 +32,11 @@ const QuestionLastUpdate = ({ q }) => {
// question answered // question answered
return ( return (
<> <>
<a <BaseUserCard
className="p-0" data={q.last_answered_user_info}
href={`/users/${q.last_answered_user_info.username}`}> showAvatar={false}
{q.last_answered_user_info.display_name} className="me-1"
</a> />
<span className="fw-bold px-1">{q.last_answered_user_info.rank}</span>
<FormatTime <FormatTime
time={q.update_time} time={q.update_time}
@ -45,10 +51,11 @@ const QuestionLastUpdate = ({ q }) => {
// question modified // question modified
return ( return (
<> <>
<a className="p-0" href={`/users/${q.update_user_info.username}`}> <BaseUserCard
{q.update_user_info.display_name} data={q.update_user_info}
</a> showAvatar={false}
<span className="fw-bold px-1">{q.update_user_info.rank}</span> className="me-1"
/>
<FormatTime <FormatTime
time={q.edit_time} time={q.edit_time}
@ -62,10 +69,7 @@ const QuestionLastUpdate = ({ q }) => {
// default: asked // default: asked
return ( return (
<> <>
<a className="p-0" href={`/users/${q.user_info.username}`}> <BaseUserCard data={q.user_info} showAvatar={false} className="me-1" />
{q.user_info.display_name}
</a>
<strong className="px-1">{q.user_info.rank}</strong>
<FormatTime <FormatTime
time={q.create_time} time={q.create_time}

View File

@ -50,7 +50,7 @@ const Index: FC<IProps> = ({ type, qid, aid, title }) => {
}); });
}; };
useEffect(() => { useEffect(() => {
if (window.navigator?.canShare?.({ text: '111' })) { if (window.navigator?.canShare?.({ text: 'can_share' })) {
setSystemShareState(true); setSystemShareState(true);
} }
}, []); }, []);

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } 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 { emailReSend, checkImgCode } from '@answer/api'; import { resendEmail, checkImgCode } from '@answer/api';
import { PicAuthCodeModal } from '@answer/components/Modal'; import { PicAuthCodeModal } from '@answer/components/Modal';
import type { import type {
ImgCodeRes, ImgCodeRes,
@ -53,7 +53,7 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
captcha_id: imgCode.captcha_id, captcha_id: imgCode.captcha_id,
}; };
} }
emailReSend(obj) resendEmail(obj)
.then(() => { .then(() => {
setSuccess(true); setSuccess(true);
setModalState(false); setModalState(false);

View File

@ -12,14 +12,22 @@ interface Props {
const Index: FC<Props> = ({ data, time, preFix }) => { const Index: FC<Props> = ({ data, time, preFix }) => {
return ( return (
<div className="d-flex"> <div className="d-flex">
{data?.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`}> <Link to={`/users/${data?.username}`}>
<Avatar avatar={data?.avatar} size="40px" className="me-2" /> <Avatar avatar={data?.avatar} size="40px" className="me-2" />
</Link> </Link>
) : (
<Avatar avatar={data?.avatar} size="40px" className="me-2" />
)}
<div className="fs-14 text-secondary"> <div className="fs-14 text-secondary">
<div> <div>
{data?.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`} className="me-1 text-break"> <Link to={`/users/${data?.username}`} className="me-1 text-break">
{data?.display_name} {data?.display_name}
</Link> </Link>
) : (
<span className="me-1 text-break">{data?.display_name}</span>
)}
<span className="fw-bold">{data?.rank}</span> <span className="fw-bold">{data?.rank}</span>
</div> </div>
{time && <FormatTime time={time} preFix={preFix} />} {time && <FormatTime time={time} preFix={preFix} />}

View File

@ -848,7 +848,7 @@
"interface": { "interface": {
"page_title": "Interface", "page_title": "Interface",
"logo": { "logo": {
"label": "Logo", "label": "Logo (optional)",
"msg": "Site logo cannot be empty.", "msg": "Site logo cannot be empty.",
"text": "You can upload your image or <1>reset</1> it to the site title text." "text": "You can upload your image or <1>reset</1> it to the site title text."
}, },

View File

@ -30,7 +30,7 @@ const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted'];
const Answers: FC = () => { const Answers: FC = () => {
const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('status') || answerFilterItems[0]; const curFilter = urlSearchParams.get('status') || answerFilterItems[0];
const pageSize = 20; const PAGE_SIZE = 20;
const curPage = Number(urlSearchParams.get('page')) || 1; const curPage = Number(urlSearchParams.get('page')) || 1;
const { t } = useTranslation('translation', { keyPrefix: 'admin.answers' }); const { t } = useTranslation('translation', { keyPrefix: 'admin.answers' });
@ -39,7 +39,7 @@ const Answers: FC = () => {
isLoading, isLoading,
mutate: refreshList, mutate: refreshList,
} = useAnswerSearch({ } = useAnswerSearch({
page_size: pageSize, page_size: PAGE_SIZE,
page: curPage, page: curPage,
status: curFilter as Type.AdminContentsFilterBy, status: curFilter as Type.AdminContentsFilterBy,
}); });
@ -189,7 +189,7 @@ const Answers: FC = () => {
<Pagination <Pagination
currentPage={curPage} currentPage={curPage}
totalSize={count} totalSize={count}
pageSize={pageSize} pageSize={PAGE_SIZE}
/> />
</div> </div>
</> </>

View File

@ -23,14 +23,14 @@ const Flags: FC = () => {
const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('status') || flagFilterKeys[0]; const curFilter = urlSearchParams.get('status') || flagFilterKeys[0];
const curType = urlSearchParams.get('type') || flagTypeKeys[0]; const curType = urlSearchParams.get('type') || flagTypeKeys[0];
const pageSize = 20; const PAGE_SIZE = 20;
const curPage = Number(urlSearchParams.get('page')) || 1; const curPage = Number(urlSearchParams.get('page')) || 1;
const { const {
data: listData, data: listData,
isLoading, isLoading,
mutate: refreshList, mutate: refreshList,
} = useFlagSearch({ } = useFlagSearch({
page_size: pageSize, page_size: PAGE_SIZE,
page: curPage, page: curPage,
status: curFilter as Type.FlagStatus, status: curFilter as Type.FlagStatus,
object_type: curType as Type.FlagType, object_type: curType as Type.FlagType,
@ -159,7 +159,7 @@ const Flags: FC = () => {
<Pagination <Pagination
currentPage={curPage} currentPage={curPage}
totalSize={count} totalSize={count}
pageSize={pageSize} pageSize={PAGE_SIZE}
/> />
</div> </div>
</> </>

View File

@ -23,7 +23,7 @@ import { useEditStatusModal, useReportModal } from '@answer/hooks';
import { import {
useQuestionSearch, useQuestionSearch,
changeQuestionStatus, changeQuestionStatus,
questionDelete, deleteQuestion,
} from '@answer/api'; } from '@answer/api';
import * as Type from '@answer/common/interface'; import * as Type from '@answer/common/interface';
@ -35,7 +35,7 @@ const questionFilterItems: Type.AdminContentsFilterBy[] = [
'deleted', 'deleted',
]; ];
const pageSize = 20; const PAGE_SIZE = 20;
const Questions: FC = () => { const Questions: FC = () => {
const [urlSearchParams, setUrlSearchParams] = useSearchParams(); const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('status') || questionFilterItems[0]; const curFilter = urlSearchParams.get('status') || questionFilterItems[0];
@ -47,7 +47,7 @@ const Questions: FC = () => {
isLoading, isLoading,
mutate: refreshList, mutate: refreshList,
} = useQuestionSearch({ } = useQuestionSearch({
page_size: pageSize, page_size: PAGE_SIZE,
page: curPage, page: curPage,
status: curFilter as Type.AdminContentsFilterBy, status: curFilter as Type.AdminContentsFilterBy,
}); });
@ -89,7 +89,7 @@ const Questions: FC = () => {
confirmBtnVariant: 'danger', confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }), confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => { onConfirm: () => {
questionDelete({ deleteQuestion({
id, id,
}).then(() => { }).then(() => {
refreshList(); refreshList();
@ -207,7 +207,7 @@ const Questions: FC = () => {
<Pagination <Pagination
currentPage={curPage} currentPage={curPage}
totalSize={count} totalSize={count}
pageSize={pageSize} pageSize={PAGE_SIZE}
/> />
</div> </div>
</> </>

View File

@ -1,140 +0,0 @@
import { FC } from 'react';
import {
Container,
Row,
Col,
Button,
Table,
Figure,
Stack,
} from 'react-bootstrap';
import { AccordionNav } from '@answer/components';
import { ADMIN_NAV_MENUS } from '@answer/common/constants';
import '../index.scss';
const UserOverview: FC = () => {
return (
<Container className="admin-container">
<Row>
<Col lg={2}>
<AccordionNav menus={ADMIN_NAV_MENUS} />
</Col>
<Col lg={10}>
<Button variant="outline-secondary" size="sm">
Back
</Button>
<h5 className="mb-3 mt-4">Profile</h5>
<Table className="mb-5">
<tbody className="align-middle">
<tr>
<td>ID</td>
<td>1030000000091295</td>
<td />
</tr>
<tr>
<td>Display name</td>
<td>Jim Green</td>
<td />
</tr>
<tr>
<td>username</td>
<td>jimgreen</td>
<td>
<Button variant="link" size="sm">
Edit
</Button>
</td>
</tr>
<tr>
<td>Profile image</td>
<td>
<Figure.Image
width={48}
height={48}
className="rounded-1 m-0"
src="https://gw.alicdn.com/bao/uploaded/i4/1607723262/O1CN01JJCGVD1Zy2jryOhDc_!!1607723262.jpg"
/>
</td>
<td />
</tr>
</tbody>
</Table>
<h5 className="mb-3 mt-4">Permissions</h5>
<Table className="mb-5">
<tbody className="align-middle">
<tr>
<td>Activated</td>
<td>No</td>
<td>
<Button size="sm" variant="link">
Activate
</Button>
</td>
</tr>
<tr>
<td>Admin?</td>
<td>No</td>
<td>
<Button size="sm" variant="link">
Grant
</Button>
</td>
</tr>
<tr>
<td>Suspended?</td>
<td>No</td>
<td>
<Stack direction="horizontal" gap={1}>
<Button size="sm" variant="link" className="text-danger">
Suspend
</Button>
<div className="text-secondary text-nowrap">
A suspended user can't log in
</div>
</Stack>
</td>
</tr>
</tbody>
</Table>
<h5 className="mb-3 mt-4">Activity</h5>
<Table className="mb-5">
<tbody className="align-middle">
<tr>
<td>Reputation</td>
<td>1805</td>
</tr>
<tr>
<td>Answers</td>
<td>30</td>
</tr>
<tr>
<td>Questions</td>
<td>10</td>
</tr>
<tr>
<td>Created</td>
<td>Sep 1, 2022 at 16:00</td>
</tr>
<tr>
<td>Registration IP address</td>
<td>11.22.33.44</td>
</tr>
<tr>
<td>Seen</td>
<td>Sep 6, 2022 at 09:35</td>
</tr>
<tr>
<td>Last IP address</td>
<td>11.22.33.44</td>
</tr>
</tbody>
</Table>
</Col>
</Row>
</Container>
);
};
export default UserOverview;

View File

@ -1,7 +1,7 @@
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { Helmet } from 'react-helmet'; import { Helmet, HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr'; import { SWRConfig } from 'swr';
@ -56,11 +56,9 @@ const Layout: FC = () => {
} }
return ( return (
<> <HelmetProvider>
<Helmet> <Helmet>
{siteInfo ? ( {siteInfo && <meta name="description" content={siteInfo.description} />}
<meta name="description" content={siteInfo.description} />
) : null}
</Helmet> </Helmet>
<SWRConfig <SWRConfig
value={{ value={{
@ -74,7 +72,7 @@ const Layout: FC = () => {
<Toast msg={toastMsg} variant={variant} onClose={closeToast} /> <Toast msg={toastMsg} variant={variant} onClose={closeToast} />
<Footer /> <Footer />
</SWRConfig> </SWRConfig>
</> </HelmetProvider>
); );
}; };

View File

@ -20,49 +20,26 @@ import type * as Type from '@answer/common/interface';
import SearchQuestion from './components/SearchQuestion'; import SearchQuestion from './components/SearchQuestion';
interface FormDataItem { interface FormDataItem {
title: { title: Type.FormValue<string>;
value: string; tags: Type.FormValue<Type.Tag[]>;
isInvalid: boolean; content: Type.FormValue<string>;
errorMsg: string; answer: Type.FormValue<string>;
focus?: boolean;
};
tags: {
value: Type.Tag[];
isInvalid: boolean;
errorMsg: string;
focus?: boolean;
};
content: {
value: string;
isInvalid: boolean;
errorMsg: string;
focus?: boolean;
};
answer: {
value: string;
isInvalid: boolean;
errorMsg: string;
focus?: boolean;
};
} }
const initFormData = { const initFormData = {
title: { title: {
value: '', value: '',
isInvalid: false, isInvalid: false,
errorMsg: '', errorMsg: '',
focus: false,
}, },
tags: { tags: {
value: [], value: [],
isInvalid: false, isInvalid: false,
errorMsg: '', errorMsg: '',
focus: false,
}, },
content: { content: {
value: '', value: '',
isInvalid: false, isInvalid: false,
errorMsg: '', errorMsg: '',
focus: false,
}, },
answer: { answer: {
value: '', value: '',

View File

@ -1,7 +1,7 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { ButtonGroup } from 'react-bootstrap'; import { ButtonGroup } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
interface Props { interface Props {
count: number; count: number;
@ -11,6 +11,7 @@ const Index: FC<Props> = ({ count = 0, order = 'default' }) => {
const { t } = useTranslation('translation', { const { t } = useTranslation('translation', {
keyPrefix: 'question_detail.answers', keyPrefix: 'question_detail.answers',
}); });
const location = useLocation();
return ( return (
<div <div
className="d-flex align-items-center justify-content-between mt-5 mb-3" className="d-flex align-items-center justify-content-between mt-5 mb-3"
@ -20,14 +21,14 @@ const Index: FC<Props> = ({ count = 0, order = 'default' }) => {
</h5> </h5>
<ButtonGroup size="sm"> <ButtonGroup size="sm">
<Link <Link
to={`${window.location.pathname}?order=default`} to={`${location.pathname}?order=default`}
className={`btn btn-outline-secondary ${ className={`btn btn-outline-secondary ${
order !== 'updated' ? 'active' : '' order !== 'updated' ? 'active' : ''
}`}> }`}>
{t('score')} {t('score')}
</Link> </Link>
<Link <Link
to={`${window.location.pathname}?order=updated`} to={`${location.pathname}?order=updated`}
className={`btn btn-outline-secondary ${ className={`btn btn-outline-secondary ${
order === 'updated' ? 'active' : '' order === 'updated' ? 'active' : ''
}`}> }`}>

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Container, Row, Col } from 'react-bootstrap'; import { Container, Row, Col } from 'react-bootstrap';
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
import { questionDetail, getAnswers } from '@answer/api'; import { questionDetail, getAnswers } from '@answer/api';
import { Pagination, PageTitle } from '@answer/components'; import { Pagination, PageTitle } from '@answer/components';
@ -9,7 +9,7 @@ import { scrollTop } from '@answer/utils';
import { usePageUsers } from '@answer/hooks'; import { usePageUsers } from '@answer/hooks';
import type { import type {
ListResult, ListResult,
QuDetailRes, QuestionDetailRes,
AnswerItem, AnswerItem,
} from '@answer/common/interface'; } from '@answer/common/interface';
@ -25,12 +25,13 @@ import {
import './index.scss'; import './index.scss';
const Index = () => { const Index = () => {
const navigate = useNavigate();
const { qid = '', aid = '' } = useParams(); const { qid = '', aid = '' } = useParams();
const [urlSearch] = useSearchParams(); const [urlSearch] = useSearchParams();
const page = Number(urlSearch.get('page') || 0); const page = Number(urlSearch.get('page') || 0);
const order = urlSearch.get('order') || ''; const order = urlSearch.get('order') || '';
const [question, setQuestion] = useState<QuDetailRes | null>(null); const [question, setQuestion] = useState<QuestionDetailRes | null>(null);
const [answers, setAnswers] = useState<ListResult<AnswerItem>>({ const [answers, setAnswers] = useState<ListResult<AnswerItem>>({
count: -1, count: -1,
list: [], list: [],
@ -75,7 +76,7 @@ const Index = () => {
const initPage = (type: string) => { const initPage = (type: string) => {
if (type === 'delete_question') { if (type === 'delete_question') {
setTimeout(() => { setTimeout(() => {
window.history.back(); navigate(-1);
}, 1000); }, 1000);
return; return;
} }

View File

@ -17,16 +17,8 @@ import type * as Type from '@answer/common/interface';
import './index.scss'; import './index.scss';
interface FormDataItem { interface FormDataItem {
answer: { answer: Type.FormValue<string>;
value: string; description: Type.FormValue<string>;
isInvalid: boolean;
errorMsg: string;
};
description: {
value: string;
isInvalid: boolean;
errorMsg: string;
};
} }
const initFormData = { const initFormData = {
answer: { answer: {
@ -111,7 +103,7 @@ const Ask = () => {
id: aid, id: aid,
}; };
modifyAnswer(params).then(() => { modifyAnswer(params).then(() => {
window.location.href = `/questions/${qid}/${aid}`; navigate(`/questions/${qid}/${aid}`);
}); });
}; };

View File

@ -1,6 +1,6 @@
import { FC, memo } from 'react'; import { FC, memo } from 'react';
import { ListGroupItem, ButtonGroup, Button } from 'react-bootstrap'; import { ListGroupItem, ButtonGroup, Button } from 'react-bootstrap';
import { useSearchParams, useNavigate } from 'react-router-dom'; import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const sortBtns = [ const sortBtns = [
@ -21,6 +21,7 @@ interface Props {
} }
const Index: FC<Props> = ({ sort, count = 0 }) => { const Index: FC<Props> = ({ sort, count = 0 }) => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation('translation', { const { t } = useTranslation('translation', {
@ -28,7 +29,7 @@ const Index: FC<Props> = ({ sort, count = 0 }) => {
}); });
const handleParams = (order): string => { const handleParams = (order): string => {
const basePath = window.location.pathname; const basePath = location.pathname;
searchParams.delete('page'); searchParams.delete('page');
searchParams.set('order', order); searchParams.set('order', order);
const searchStr = searchParams.toString(); const searchStr = searchParams.toString();

View File

@ -1,9 +1,8 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { ListGroupItem, Badge } from 'react-bootstrap'; import { ListGroupItem, Badge } from 'react-bootstrap';
import { Icon, Tag, FormatTime } from '@answer/components'; import { Icon, Tag, FormatTime, BaseUserCard } from '@answer/components';
import type { SearchResItem } from '@answer/common/interface'; import type { SearchResItem } from '@answer/common/interface';
import { formatCount } from '@answer/utils';
interface Props { interface Props {
data: SearchResItem; data: SearchResItem;
@ -28,14 +27,8 @@ const Index: FC<Props> = ({ data }) => {
</a> </a>
</div> </div>
<div className="d-flex flex-wrap align-items-center fs-14 text-secondary mb-2"> <div className="d-flex flex-wrap align-items-center fs-14 text-secondary mb-2">
<a href={`/users/${data.object?.user_info?.username}`}> <BaseUserCard data={data.object?.user_info} showAvatar={false} />
{data.object?.user_info?.display_name}
</a>
{data.object?.user_info?.rank > 0 && (
<span className="fw-bold ms-1">
{formatCount(data.object.user_info.rank)}
</span>
)}
<span className="split-dot" /> <span className="split-dot" />
<FormatTime <FormatTime
time={data.object?.created_at} time={data.object?.created_at}

View File

@ -9,28 +9,13 @@ import classNames from 'classnames';
import { Editor, EditorRef, PageTitle } from '@answer/components'; import { Editor, EditorRef, PageTitle } from '@answer/components';
import { useTagInfo, modifyTag, useQueryRevisions } from '@answer/api'; import { useTagInfo, modifyTag, useQueryRevisions } from '@answer/api';
import { userInfoStore } from '@answer/stores'; import { userInfoStore } from '@answer/stores';
import type * as Type from '@answer/common/interface';
interface FormDataItem { interface FormDataItem {
displayName: { displayName: Type.FormValue<string>;
value: string; slugName: Type.FormValue<string>;
isInvalid: boolean; description: Type.FormValue<string>;
errorMsg: string; editSummary: Type.FormValue<string>;
};
slugName: {
value: string;
isInvalid: boolean;
errorMsg: string;
};
description: {
value: string;
isInvalid: boolean;
errorMsg: string;
};
editSummary: {
value: string;
isInvalid: boolean;
errorMsg: string;
};
} }
const initFormData = { const initFormData = {
displayName: { displayName: {

View File

@ -2,10 +2,10 @@ import { FC, memo, useEffect, 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 { passRetrieve, checkImgCode } from '@answer/api'; import { resetPassword, checkImgCode } from '@answer/api';
import type { import type {
ImgCodeRes, ImgCodeRes,
PssRetReq, PasswordResetReq,
FormDataType, FormDataType,
} from '@answer/common/interface'; } from '@answer/common/interface';
@ -70,7 +70,7 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
const params: PssRetReq = { const params: PasswordResetReq = {
e_mail: formData.e_mail.value, e_mail: formData.e_mail.value,
}; };
if (imgCode.verify) { if (imgCode.verify) {
@ -78,7 +78,7 @@ const Index: FC<IProps> = ({ visible = false, callback }) => {
params.captcha_id = imgCode.captcha_id; params.captcha_id = imgCode.captcha_id;
} }
passRetrieve(params) resetPassword(params)
.then(() => { .then(() => {
callback?.(2, formData.e_mail.value); callback?.(2, formData.e_mail.value);
setModalState(false); setModalState(false);

View File

@ -1,7 +1,7 @@
import { FC, memo, useEffect } from 'react'; import { FC, memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { accountActivate } from '@answer/api'; import { activateAccount } from '@answer/api';
import { userInfoStore } from '@answer/stores'; import { userInfoStore } from '@answer/stores';
import { getQueryString } from '@answer/utils'; import { getQueryString } from '@answer/utils';
@ -14,10 +14,10 @@ const Index: FC = () => {
const code = getQueryString('code'); const code = getQueryString('code');
if (code) { if (code) {
accountActivate(encodeURIComponent(code)).then((res) => { activateAccount(encodeURIComponent(code)).then((res) => {
updateUser(res); updateUser(res);
setTimeout(() => { setTimeout(() => {
window.location.href = '/users/account-activation/success'; window.location.replace('/users/account-activation/success');
}, 0); }, 0);
}); });
} }

View File

@ -108,13 +108,9 @@ const Index: React.FC = () => {
setRefresh((pre) => pre + 1); setRefresh((pre) => pre + 1);
} }
if (res.mail_status === 1) { if (res.mail_status === 1) {
const path = Storage.get('ANSWER_PATH'); const path = Storage.get('ANSWER_PATH') || '/';
Storage.remove('ANSWER_PATH'); Storage.remove('ANSWER_PATH');
if (path) { window.location.replace(path);
window.location.href = path;
} else {
window.location.href = '/';
}
} }
setModalState(false); setModalState(false);

View File

@ -37,9 +37,13 @@ const Inbox = ({ data, handleReadNotification }) => {
key={item.id} key={item.id}
className={classNames('py-3', !item.is_read && 'warning')}> className={classNames('py-3', !item.is_read && 'warning')}>
<div> <div>
{item.user_info.status !== 'deleted' ? (
<Link to={`/users/${item.user_info.username}`}> <Link to={`/users/${item.user_info.username}`}>
{item.user_info.display_name} {item.user_info.display_name}{' '}
</Link>{' '} </Link>
) : (
<span>{item.user_info.display_name} </span>
)}
{item.notification_action}{' '} {item.notification_action}{' '}
<Link to={url} onClick={() => handleReadNotification(item.id)}> <Link to={url} onClick={() => handleReadNotification(item.id)}>
{item.object_info.title} {item.object_info.title}

View File

@ -5,8 +5,8 @@ import { useParams, useNavigate } from 'react-router-dom';
import { import {
useQueryNotifications, useQueryNotifications,
clearUnReadNotification, clearUnreadNotification,
clearNotificationRedDot, clearNotificationStatus,
readNotification, readNotification,
} from '@answer/api'; } from '@answer/api';
import { PageTitle } from '@answer/components'; import { PageTitle } from '@answer/components';
@ -29,7 +29,7 @@ const Notifications = () => {
}); });
useEffect(() => { useEffect(() => {
clearNotificationRedDot(type); clearNotificationStatus(type);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -56,7 +56,7 @@ const Notifications = () => {
}; };
const handleUnreadNotification = async () => { const handleUnreadNotification = async () => {
await clearUnReadNotification(type); await clearUnreadNotification(type);
mutate(); mutate();
}; };

View File

@ -3,7 +3,7 @@ import { Container, Col, Form, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { passRetrieveSet } from '@answer/api'; import { replacementPassword } from '@answer/api';
import { userInfoStore } from '@answer/stores'; import { userInfoStore } from '@answer/stores';
import { getQueryString, isLogin } from '@answer/utils'; import { getQueryString, isLogin } from '@answer/utils';
import type { FormDataType } from '@answer/common/interface'; import type { FormDataType } from '@answer/common/interface';
@ -98,7 +98,7 @@ const Index: React.FC = () => {
console.error('code is required'); console.error('code is required');
return; return;
} }
passRetrieveSet({ replacementPassword({
code: encodeURIComponent(code), code: encodeURIComponent(code),
pass: formData.pass.value, pass: formData.pass.value,
}) })

View File

@ -2,7 +2,7 @@ import { FC, memo } from 'react';
import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { ListGroup, ListGroupItem } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, FormatTime, Tag } from '@answer/components'; import { Icon, FormatTime, Tag, BaseUserCard } from '@answer/components';
interface Props { interface Props {
visible: boolean; visible: boolean;
@ -31,15 +31,10 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
</h6> </h6>
<div className="d-flex align-items-center fs-14 text-secondary mb-2"> <div className="d-flex align-items-center fs-14 text-secondary mb-2">
{tabName === 'bookmarks' && ( {tabName === 'bookmarks' && (
<div className="d-flex"> <>
<a <BaseUserCard data={item.user_info} showAvatar={false} />
href={`/users/${item.user_info?.username}`}
className="me-1">
{item.user_info?.display_name}
</a>
<strong>{item.user_info?.rank}</strong>
<span className="split-dot" /> <span className="split-dot" />
</div> </>
)} )}
<FormatTime <FormatTime
time={item.create_time} time={item.create_time}

View File

@ -1,6 +1,6 @@
import { FC, memo } from 'react'; import { FC, memo } from 'react';
import { ButtonGroup, Button } from 'react-bootstrap'; import { ButtonGroup, Button } from 'react-bootstrap';
import { useSearchParams, useNavigate } from 'react-router-dom'; import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const sortBtns = [ const sortBtns = [
@ -26,10 +26,11 @@ const Index: FC<Props> = ({
}) => { }) => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation('translation', { keyPrefix: 'personal' }); const { t } = useTranslation('translation', { keyPrefix: 'personal' });
const handleParams = (order): string => { const handleParams = (order): string => {
const basePath = window.location.pathname; const basePath = location.pathname;
searchParams.delete('page'); searchParams.delete('page');
searchParams.set('order', order); searchParams.set('order', order);
const searchStr = searchParams.toString(); const searchStr = searchParams.toString();

View File

@ -1,6 +1,7 @@
import { FC, memo } from 'react'; import { FC, memo } from 'react';
import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap'; import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Avatar, Icon } from '@answer/components'; import { Avatar, Icon } from '@answer/components';
import type { UserInfoRes } from '@answer/common/interface'; import type { UserInfoRes } from '@answer/common/interface';
@ -16,14 +17,26 @@ const Index: FC<Props> = ({ data }) => {
} }
return ( return (
<div className="d-flex mb-4"> <div className="d-flex mb-4">
<a href={`/users/${data.username}`}> {data?.status !== 'deleted' ? (
<Link to={`/users/${data.username}`} reloadDocument>
<Avatar avatar={data.avatar} size="160px" /> <Avatar avatar={data.avatar} size="160px" />
</a> </Link>
) : (
<Avatar avatar={data.avatar} size="160px" />
)}
<div className="ms-4"> <div className="ms-4">
<div className="d-flex align-items-center mb-2"> <div className="d-flex align-items-center mb-2">
<a href={`/users/${data.username}`} className="text-body h3 mb-0"> {data?.status !== 'deleted' ? (
<Link
to={`/users/${data.username}`}
className="text-body h3 mb-0"
reloadDocument>
{data.display_name} {data.display_name}
</a> </Link>
) : (
<span className="text-body h3 mb-0">{data.display_name}</span>
)}
{data?.is_admin && ( {data?.is_admin && (
<div className="ms-2"> <div className="ms-2">
<OverlayTrigger <OverlayTrigger

View File

@ -3,7 +3,7 @@ import { Form, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { FormDataType } from '@answer/common/interface'; import type { FormDataType } from '@answer/common/interface';
import { noticeSet, getUserInfo } from '@answer/api'; import { setNotice, getUserInfo } from '@answer/api';
import { useToast } from '@answer/hooks'; import { useToast } from '@answer/hooks';
const Index = () => { const Index = () => {
@ -34,7 +34,7 @@ const Index = () => {
const handleSubmit = (event: FormEvent) => { const handleSubmit = (event: FormEvent) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
noticeSet({ setNotice({
notice_switch: formData.notice_switch.value, notice_switch: formData.notice_switch.value,
}).then(() => { }).then(() => {
toast.onShow({ toast.onShow({

View File

@ -9,7 +9,7 @@ const Suspended = () => {
const userInfo = userInfoStore((state) => state.user); const userInfo = userInfoStore((state) => state.user);
if (userInfo.status !== 'forbidden') { if (userInfo.status !== 'forbidden') {
window.location.href = '/'; window.location.replace('/');
return null; return null;
} }

View File

@ -29,7 +29,7 @@ export const readNotification = (id) => {
}); });
}; };
export const useQueryNotificationRedDot = () => { export const useQueryNotificationStatus = () => {
const apiUrl = '/answer/api/v1/notification/status'; const apiUrl = '/answer/api/v1/notification/status';
return useSWR<{ inbox: number; achievement: number }>( return useSWR<{ inbox: number; achievement: number }>(
@ -41,13 +41,13 @@ export const useQueryNotificationRedDot = () => {
); );
}; };
export const clearNotificationRedDot = (type) => { export const clearNotificationStatus = (type) => {
return request.instance.put('/answer/api/v1/notification/status', { return request.instance.put('/answer/api/v1/notification/status', {
type, type,
}); });
}; };
export const clearUnReadNotification = (type) => { export const clearUnreadNotification = (type) => {
return request.instance.put('/answer/api/v1/notification/read/state/all', { return request.instance.put('/answer/api/v1/notification/read/state/all', {
type, type,
}); });

View File

@ -96,11 +96,11 @@ export const logout = () => {
return request.get('/answer/api/v1/user/logout'); return request.get('/answer/api/v1/user/logout');
}; };
export const emailVerify = (code: string) => { export const verifyEmail = (code: string) => {
return request.get(`/answer/api/v1/email/verify?code=${code}`); return request.get(`/answer/api/v1/email/verify?code=${code}`);
}; };
export const emailReSend = (params?: Type.ImgCodeReq) => { export const resendEmail = (params?: Type.ImgCodeReq) => {
params = qs.parse( params = qs.parse(
qs.stringify(params, { qs.stringify(params, {
skipNulls: true, skipNulls: true,
@ -119,7 +119,7 @@ export const getUserInfo = () => {
return request.get<Type.UserInfoRes>('/answer/api/v1/user/info'); return request.get<Type.UserInfoRes>('/answer/api/v1/user/info');
}; };
export const modifyPassword = (params: Type.ModifyPassReq) => { export const modifyPassword = (params: Type.ModifyPasswordReq) => {
return request.post('/answer/api/v1/user/password/modify', params); return request.post('/answer/api/v1/user/password/modify', params);
}; };
@ -131,15 +131,15 @@ export const uploadAvatar = (params: Type.AvatarUploadReq) => {
return request.post('/answer/api/v1/user/avatar/upload', params); return request.post('/answer/api/v1/user/avatar/upload', params);
}; };
export const passRetrieve = (params: Type.PssRetReq) => { 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 passRetrieveSet = (params: { code: string; pass: string }) => { export const replacementPassword = (params: { code: string; pass: string }) => {
return request.post('/answer/api/v1/user/password/replacement', params); return request.post('/answer/api/v1/user/password/replacement', params);
}; };
export const accountActivate = (code: string) => { 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 });
}; };
@ -149,7 +149,7 @@ export const checkImgCode = (params: Type.CheckImgReq) => {
); );
}; };
export const noticeSet = (params: Type.NoticeSetReq) => { export const setNotice = (params: Type.SetNoticeReq) => {
return request.post('/answer/api/v1/user/notice/set', params); return request.post('/answer/api/v1/user/notice/set', params);
}; };
@ -158,7 +158,9 @@ export const saveQuestion = (params: Type.QuestionParams) => {
}; };
export const questionDetail = (id: string) => { export const questionDetail = (id: string) => {
return request.get<Type.QuDetailRes>(`/answer/api/v1/question/info?id=${id}`); return request.get<Type.QuestionDetailRes>(
`/answer/api/v1/question/info?id=${id}`,
);
}; };
export const langConfig = () => { export const langConfig = () => {
@ -227,11 +229,11 @@ export const postReport = (params: {
return request.post('/answer/api/v1/report', params); return request.post('/answer/api/v1/report', params);
}; };
export const questionDelete = (params: { id: string }) => { export const deleteQuestion = (params: { id: string }) => {
return request.delete('/answer/api/v1/question', params); return request.delete('/answer/api/v1/question', params);
}; };
export const answerDelete = (params: { id: string }) => { export const deleteAnswer = (params: { id: string }) => {
return request.delete('/answer/api/v1/answer', params); return request.delete('/answer/api/v1/answer', params);
}; };

View File

@ -34,7 +34,7 @@ function isLogin(needToLogin?: boolean): boolean {
// login and active // login and active
if (user.username && user.mail_status === 1) { if (user.username && user.mail_status === 1) {
if (LOGIN_NEED_BACK.includes(path)) { if (LOGIN_NEED_BACK.includes(path)) {
window.location.href = '/'; window.location.replace('/');
} }
return true; return true;
} }

View File

@ -6,12 +6,6 @@ import { userInfoStore, toastStore } from '@answer/stores';
import Storage from './storage'; import Storage from './storage';
// type Result<T> = {
// code: number;
// msg: string;
// data: T;
// };
const API = { const API = {
development: '', development: '',
production: '', production: '',