mirror of https://gitee.com/answerdev/answer.git
commit
c602752dcc
|
@ -774,6 +774,8 @@ ui:
|
|||
logout: Log out
|
||||
admin: Admin
|
||||
review: Review
|
||||
bookmark: Bookmarks
|
||||
moderation: Moderation
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
|
@ -969,7 +971,7 @@ ui:
|
|||
Views: Viewed
|
||||
Follow: Follow
|
||||
Following: Following
|
||||
follow_tip: Follow this question to receive notifications.
|
||||
follow_tip: Follow this question to receive notifications
|
||||
answered: answered
|
||||
closed_in: Closed in
|
||||
show_exist: Show existing question.
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
.page-right-side {
|
||||
flex: none;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 12px;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: var(--bs-gray-300);
|
||||
min-height: calc(100vh - 62px - 74px);
|
||||
}
|
||||
|
||||
// lg
|
||||
@media screen and (max-width: 1199.9px) {
|
||||
.page-right-side {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// md
|
||||
@media screen and (max-width: 991.9px) {
|
||||
.line {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -63,29 +63,16 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
|
|||
onClick={handleLinkClick}>
|
||||
{t('header.nav.profile')}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href={`/users/${userInfo.username}/bookmarks`}
|
||||
onClick={handleLinkClick}>
|
||||
{t('header.nav.bookmark')}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="/users/settings/profile"
|
||||
onClick={handleLinkClick}>
|
||||
{t('header.nav.setting')}
|
||||
</Dropdown.Item>
|
||||
{userInfo?.role_id === 2 ? (
|
||||
<Dropdown.Item href="/admin" onClick={handleLinkClick}>
|
||||
{t('header.nav.admin')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
{redDot?.can_revision ? (
|
||||
<Dropdown.Item
|
||||
href="/review"
|
||||
className="position-relative"
|
||||
onClick={handleLinkClick}>
|
||||
{t('header.nav.review')}
|
||||
{redDot?.revision > 0 && (
|
||||
<span className="position-absolute top-50 translate-middle-y end-0 me-3 p-2 bg-danger border border-light rounded-circle">
|
||||
<span className="visually-hidden">New Review</span>
|
||||
</span>
|
||||
)}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={logOut}>
|
||||
{t('header.nav.logout')}
|
||||
|
|
|
@ -52,10 +52,14 @@
|
|||
&.theme-light {
|
||||
background: linear-gradient(180deg, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.95) 100%);
|
||||
}
|
||||
|
||||
.maxw-400 {
|
||||
max-width: 400px;;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 992.9px) {
|
||||
@media (max-width: 991.9px) {
|
||||
#header {
|
||||
.nav-grow {
|
||||
flex-grow: 1!important;
|
||||
|
@ -65,8 +69,8 @@
|
|||
display: flex!important;
|
||||
}
|
||||
|
||||
.w-75 {
|
||||
width: 100% !important;
|
||||
.maxw-400 {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
useSearchParams,
|
||||
NavLink,
|
||||
Link,
|
||||
useNavigate,
|
||||
useLocation,
|
||||
|
@ -27,6 +26,7 @@ import {
|
|||
brandingStore,
|
||||
loginSettingStore,
|
||||
themeSettingStore,
|
||||
sideNavStore,
|
||||
} from '@/stores';
|
||||
import { logout, useQueryNotificationStatus } from '@/services';
|
||||
|
||||
|
@ -45,6 +45,7 @@ const Header: FC = () => {
|
|||
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
||||
const brandingInfo = brandingStore((state) => state.branding);
|
||||
const loginSetting = loginSettingStore((state) => state.login);
|
||||
const { updateReiview, updateVisible } = sideNavStore();
|
||||
const { data: redDot } = useQueryNotificationStatus();
|
||||
/**
|
||||
* Automatically append `tag` information when creating a question
|
||||
|
@ -55,6 +56,13 @@ const Header: FC = () => {
|
|||
askUrl = `${askUrl}?tags=${tagMatch.params.slugName}`;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateReiview({
|
||||
can_revision: Boolean(redDot?.can_revision),
|
||||
revision: Number(redDot?.revision),
|
||||
});
|
||||
}, [redDot]);
|
||||
|
||||
const handleInput = (val) => {
|
||||
setSearch(val);
|
||||
};
|
||||
|
@ -106,10 +114,13 @@ const Header: FC = () => {
|
|||
aria-controls="navBarContent"
|
||||
className="answer-navBar me-2"
|
||||
id="navBarToggle"
|
||||
onClick={() => {
|
||||
updateVisible();
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="d-flex justify-content-between align-items-center nav-grow flex-nowrap">
|
||||
<Navbar.Brand to="/" as={Link} className="lh-1 me-0 me-sm-3 p-0">
|
||||
<Navbar.Brand to="/" as={Link} className="lh-1 me-0 me-sm-5 p-0">
|
||||
{brandingInfo.logo ? (
|
||||
<>
|
||||
<img
|
||||
|
@ -159,26 +170,11 @@ const Header: FC = () => {
|
|||
</div>
|
||||
|
||||
<Navbar.Collapse id="navBarContent" className="me-auto">
|
||||
<hr className="hr lg-none mb-2" style={{ marginTop: '12px' }} />
|
||||
<Col md={4}>
|
||||
<Nav>
|
||||
<NavLink className="nav-link" to="/questions">
|
||||
{t('header.nav.question')}
|
||||
</NavLink>
|
||||
<NavLink className="nav-link" to="/tags">
|
||||
{t('header.nav.tag')}
|
||||
</NavLink>
|
||||
<NavLink className="nav-link" to="/users">
|
||||
{t('header.nav.user')}
|
||||
</NavLink>
|
||||
</Nav>
|
||||
</Col>
|
||||
<hr className="hr lg-none mt-2" />
|
||||
|
||||
<Col lg={4} className="d-flex justify-content-center">
|
||||
<hr className="hr lg-none mb-3" style={{ marginTop: '12px' }} />
|
||||
<Col lg={8} className="ps-0">
|
||||
<Form
|
||||
action="/search"
|
||||
className="w-75 px-0 px-lg-2"
|
||||
className="w-100 maxw-400"
|
||||
onSubmit={handleSearch}>
|
||||
<FormControl
|
||||
placeholder={t('header.search.placeholder')}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
#sideNav {
|
||||
.nav {
|
||||
max-width: 172px;
|
||||
}
|
||||
.nav-link {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
.nav-link:hover {
|
||||
color: rgba(0, 0, 0);
|
||||
background-color: var(--bs-gray-100);
|
||||
}
|
||||
.nav-link.active {
|
||||
color: black;
|
||||
background-color: var(--bs-gray-200);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 991.9px) {
|
||||
#sideNav {
|
||||
.nav {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { FC } from 'react';
|
||||
import { Col, Nav } from 'react-bootstrap';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { loggedUserInfoStore, sideNavStore } from '@/stores';
|
||||
import { Icon } from '@/components';
|
||||
import './index.scss';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { pathname } = useLocation();
|
||||
const { user: userInfo } = loggedUserInfoStore();
|
||||
const { visible, can_revision, revision } = sideNavStore();
|
||||
return (
|
||||
<Col
|
||||
xl={2}
|
||||
lg={3}
|
||||
md={12}
|
||||
className={classnames(
|
||||
'pt-4 position-relative',
|
||||
visible ? 'fade-in' : 'd-none d-lg-block',
|
||||
)}
|
||||
id="sideNav">
|
||||
<Nav variant="pills" className="flex-column">
|
||||
<NavLink
|
||||
to="/questions"
|
||||
className={({ isActive }) =>
|
||||
isActive || pathname === '/' ? 'nav-link active' : 'nav-link'
|
||||
}>
|
||||
<Icon name="question-circle-fill" className="me-2" />
|
||||
<span>{t('header.nav.question')}</span>
|
||||
</NavLink>
|
||||
|
||||
<NavLink to="/tags" className="nav-link">
|
||||
<Icon name="tags-fill" className="me-2" />
|
||||
<span>{t('header.nav.tag')}</span>
|
||||
</NavLink>
|
||||
|
||||
<NavLink to="/users" className="nav-link">
|
||||
<Icon name="people-fill" className="me-2" />
|
||||
<span>{t('header.nav.user')}</span>
|
||||
</NavLink>
|
||||
|
||||
{can_revision || userInfo?.role_id === 2 ? (
|
||||
<>
|
||||
<div className="py-2 px-3 mt-3 fs-14 fw-bold">
|
||||
{t('header.nav.moderation')}
|
||||
</div>
|
||||
{can_revision && (
|
||||
<NavLink to="/review" className="nav-link">
|
||||
<span>{t('header.nav.review')}</span>
|
||||
<span className="float-end">
|
||||
{revision > 99 ? '99+' : revision > 0 ? revision : ''}
|
||||
</span>
|
||||
</NavLink>
|
||||
)}
|
||||
|
||||
{userInfo?.role_id === 2 ? (
|
||||
<NavLink to="/admin" className="nav-link">
|
||||
{t('header.nav.admin')}
|
||||
</NavLink>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</Nav>
|
||||
<div className="line" />
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -40,6 +40,7 @@ import HotQuestions from './HotQuestions';
|
|||
import HttpErrorContent from './HttpErrorContent';
|
||||
import CustomSidebar from './CustomSidebar';
|
||||
import ImgViewer from './ImgViewer';
|
||||
import SideNav from './SideNav';
|
||||
|
||||
export {
|
||||
Avatar,
|
||||
|
@ -86,5 +87,6 @@ export {
|
|||
HttpErrorContent,
|
||||
CustomSidebar,
|
||||
ImgViewer,
|
||||
SideNav,
|
||||
};
|
||||
export type { EditorRef, JSONSchema, UISchema };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -320,14 +320,10 @@ const Ask = () => {
|
|||
title: pageTitle,
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10} md={12}>
|
||||
<h3 className="mb-4">{isEdit ? t('edit_title') : t('title')}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<div className="pt-4 mb-5">
|
||||
<h3 className="mb-4">{isEdit ? t('edit_title') : t('title')}</h3>
|
||||
<Row>
|
||||
<Col className="flex-auto">
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
{isEdit && (
|
||||
<Form.Group controlId="revision" className="mb-3">
|
||||
|
@ -491,7 +487,7 @@ const Ask = () => {
|
|||
)}
|
||||
</Form>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Card>
|
||||
<Card.Header>
|
||||
{t('title', { keyPrefix: 'how_to_format' })}
|
||||
|
@ -505,7 +501,7 @@ const Ask = () => {
|
|||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import {
|
||||
useParams,
|
||||
useSearchParams,
|
||||
|
@ -198,69 +198,67 @@ const Index = () => {
|
|||
keywords: question?.tags.map((_) => _.slug_name).join(','),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5 questionDetailPage">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-5 mb-md-0">
|
||||
{question?.operation?.level && <Alert data={question.operation} />}
|
||||
{isLoading ? (
|
||||
<ContentLoader />
|
||||
) : (
|
||||
<Question
|
||||
data={question}
|
||||
initPage={initPage}
|
||||
hasAnswer={answers.count > 0}
|
||||
isLogged={isLogged}
|
||||
<Row className="questionDetailPage pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
{question?.operation?.level && <Alert data={question.operation} />}
|
||||
{isLoading ? (
|
||||
<ContentLoader />
|
||||
) : (
|
||||
<Question
|
||||
data={question}
|
||||
initPage={initPage}
|
||||
hasAnswer={answers.count > 0}
|
||||
isLogged={isLogged}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && answers.count > 0 && (
|
||||
<>
|
||||
<AnswerHead count={answers.count} order={order} />
|
||||
{answers?.list?.map((item) => {
|
||||
return (
|
||||
<Answer
|
||||
aid={aid}
|
||||
key={item?.id}
|
||||
data={item}
|
||||
questionTitle={question?.title || ''}
|
||||
slugTitle={question?.url_title}
|
||||
canAccept={isAuthor || isAdmin || isModerator}
|
||||
callback={initPage}
|
||||
isLogged={isLogged}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
|
||||
<div className="d-flex justify-content-center answer-item pt-4">
|
||||
<Pagination
|
||||
currentPage={Number(page || 1)}
|
||||
pageSize={15}
|
||||
totalSize={answers?.count || 0}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
Number(question?.status) !== 2 &&
|
||||
!question?.operation?.type && (
|
||||
<WriteAnswer
|
||||
data={{
|
||||
qid,
|
||||
answered: question?.answered,
|
||||
loggedUserRank,
|
||||
}}
|
||||
callback={writeAnswerCallback}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && answers.count > 0 && (
|
||||
<>
|
||||
<AnswerHead count={answers.count} order={order} />
|
||||
{answers?.list?.map((item) => {
|
||||
return (
|
||||
<Answer
|
||||
aid={aid}
|
||||
key={item?.id}
|
||||
data={item}
|
||||
questionTitle={question?.title || ''}
|
||||
slugTitle={question?.url_title}
|
||||
canAccept={isAuthor || isAdmin || isModerator}
|
||||
callback={initPage}
|
||||
isLogged={isLogged}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
|
||||
<div className="d-flex justify-content-center answer-item pt-4">
|
||||
<Pagination
|
||||
currentPage={Number(page || 1)}
|
||||
pageSize={15}
|
||||
totalSize={answers?.count || 0}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
Number(question?.status) !== 2 &&
|
||||
!question?.operation?.type && (
|
||||
<WriteAnswer
|
||||
data={{
|
||||
qid,
|
||||
answered: question?.answered,
|
||||
loggedUserRank,
|
||||
}}
|
||||
callback={writeAnswerCallback}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<CustomSidebar />
|
||||
<RelatedQuestions id={question?.id || ''} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Col>
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<CustomSidebar />
|
||||
<RelatedQuestions id={question?.id || ''} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -184,14 +184,10 @@ const Index = () => {
|
|||
title: t('edit_answer', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10} md={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<div className="pt-4 mb-5 edit-answer-wrap">
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<Row>
|
||||
<Col className="flex-auto">
|
||||
<a
|
||||
href={pathFactory.questionLanding(qid, data?.question.url_title)}
|
||||
target="_blank"
|
||||
|
@ -285,7 +281,7 @@ const Index = () => {
|
|||
</div>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Card>
|
||||
<Card.Header>
|
||||
{t('title', { keyPrefix: 'how_to_format' })}
|
||||
|
@ -299,7 +295,7 @@ const Index = () => {
|
|||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import { useMatch, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -51,49 +51,47 @@ const Questions: FC = () => {
|
|||
|
||||
usePageTags({ title: pageTitle, subtitle: slogan });
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<QuestionList
|
||||
source="questions"
|
||||
data={listData}
|
||||
order={curOrder}
|
||||
isLoading={listLoading}
|
||||
/>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<CustomSidebar />
|
||||
{!loggedUser.username && (
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">
|
||||
{t2('website_welcome', {
|
||||
site_name: siteInfo.name,
|
||||
})}
|
||||
</h5>
|
||||
<p className="card-text">{siteInfo.description}</p>
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
<QuestionList
|
||||
source="questions"
|
||||
data={listData}
|
||||
order={curOrder}
|
||||
isLoading={listLoading}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<CustomSidebar />
|
||||
{!loggedUser.username && (
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">
|
||||
{t2('website_welcome', {
|
||||
site_name: siteInfo.name,
|
||||
})}
|
||||
</h5>
|
||||
<p className="card-text">{siteInfo.description}</p>
|
||||
<Link
|
||||
to={userCenter.getLoginUrl()}
|
||||
className="btn btn-primary"
|
||||
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||
{t('login', { keyPrefix: 'btns' })}
|
||||
</Link>
|
||||
{loginSetting.allow_new_registrations ? (
|
||||
<Link
|
||||
to={userCenter.getLoginUrl()}
|
||||
className="btn btn-primary"
|
||||
to={userCenter.getSignUpUrl()}
|
||||
className="btn btn-link ms-2"
|
||||
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||
{t('login', { keyPrefix: 'btns' })}
|
||||
{t('signup', { keyPrefix: 'btns' })}
|
||||
</Link>
|
||||
{loginSetting.allow_new_registrations ? (
|
||||
<Link
|
||||
to={userCenter.getSignUpUrl()}
|
||||
className="btn btn-link ms-2"
|
||||
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||
{t('signup', { keyPrefix: 'btns' })}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
{loggedUser.access_token && <FollowingTags />}
|
||||
<HotQuestions />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
)}
|
||||
{loggedUser.access_token && <FollowingTags />}
|
||||
<HotQuestions />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, useEffect, useState } from 'react';
|
||||
import { Container, Row, Col, Alert, Stack, Button } from 'react-bootstrap';
|
||||
import { Row, Col, Alert, Stack, Button } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -123,121 +123,111 @@ const Index: FC = () => {
|
|||
title: t('review'),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-2 mt-4 mb-5">
|
||||
<Row>
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
<h3 className="mb-4">{t('review')}</h3>
|
||||
</Col>
|
||||
<Row className="pt-4 mb-5">
|
||||
<h3 className="mb-4">{t('review')}</h3>
|
||||
<Col className="flex-auto">
|
||||
{!noTasks && ro && (
|
||||
<>
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
<Alert variant="secondary">
|
||||
<Stack className="align-items-start">
|
||||
<span className="badge text-bg-secondary mb-2">
|
||||
{editBadge}
|
||||
</span>
|
||||
<Link to={itemLink} target="_blank">
|
||||
{itemTitle}
|
||||
</Link>
|
||||
<p className="mb-0">
|
||||
{t('edit_summary')}: {editSummary}
|
||||
</p>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="horizontal"
|
||||
gap={1}
|
||||
className="align-items-baseline mt-2">
|
||||
<BaseUserCard data={editor} avatarSize="24" />
|
||||
{editTime && (
|
||||
<FormatTime
|
||||
time={editTime}
|
||||
className="small text-secondary"
|
||||
preFix={t('proposed')}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Alert>
|
||||
</Col>
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
{type === 'question' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
oldData={{
|
||||
title: info.title,
|
||||
original_text: info.content,
|
||||
tags: info.tags,
|
||||
}}
|
||||
newData={{
|
||||
title: reviewInfo.title,
|
||||
original_text: reviewInfo.content,
|
||||
tags: reviewInfo.tags,
|
||||
}}
|
||||
<Alert variant="secondary">
|
||||
<Stack className="align-items-start">
|
||||
<span className="badge text-bg-secondary mb-2">
|
||||
{editBadge}
|
||||
</span>
|
||||
<Link to={itemLink} target="_blank">
|
||||
{itemTitle}
|
||||
</Link>
|
||||
<p className="mb-0">
|
||||
{t('edit_summary')}: {editSummary}
|
||||
</p>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="horizontal"
|
||||
gap={1}
|
||||
className="align-items-baseline mt-2">
|
||||
<BaseUserCard data={editor} avatarSize="24" />
|
||||
{editTime && (
|
||||
<FormatTime
|
||||
time={editTime}
|
||||
className="small text-secondary"
|
||||
preFix={t('proposed')}
|
||||
/>
|
||||
)}
|
||||
{type === 'answer' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={{
|
||||
original_text: reviewInfo.content,
|
||||
}}
|
||||
oldData={{
|
||||
original_text: info.content,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'tag' && info && reviewInfo && (
|
||||
</Stack>
|
||||
</Alert>
|
||||
{type === 'question' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
oldData={{
|
||||
title: info.title,
|
||||
original_text: info.content,
|
||||
tags: info.tags,
|
||||
}}
|
||||
newData={{
|
||||
title: reviewInfo.title,
|
||||
original_text: reviewInfo.content,
|
||||
tags: reviewInfo.tags,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'answer' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={{
|
||||
original_text: reviewInfo.original_text,
|
||||
original_text: reviewInfo.content,
|
||||
}}
|
||||
oldData={{
|
||||
original_text: info.content,
|
||||
}}
|
||||
opts={{ showTitle: false, showTagUrlSlug: false }}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
<Stack direction="horizontal" gap={2} className="mt-4">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingApprove}>
|
||||
{t('approve', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingReject}>
|
||||
{t('reject', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingSkip}>
|
||||
{t('skip', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Col>
|
||||
{type === 'tag' && info && reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={{
|
||||
original_text: reviewInfo.original_text,
|
||||
}}
|
||||
oldData={{
|
||||
original_text: info.content,
|
||||
}}
|
||||
opts={{ showTitle: false, showTagUrlSlug: false }}
|
||||
/>
|
||||
)}
|
||||
<Stack direction="horizontal" gap={2} className="mt-4">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingApprove}>
|
||||
{t('approve', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingReject}>
|
||||
{t('reject', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={isLoading}
|
||||
onClick={handlingSkip}>
|
||||
{t('skip', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
{noTasks && (
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
<Empty>{t('empty')}</Empty>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</Container>
|
||||
{noTasks && <Empty>{t('empty')}</Empty>}
|
||||
</Col>
|
||||
|
||||
<Col className="page-right-side mt-4 mt-xl-0" />
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import { Container, Row, Col, ListGroup } from 'react-bootstrap';
|
||||
import { Row, Col, ListGroup } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
|
@ -39,36 +38,34 @@ const Index = () => {
|
|||
title: pageTitle,
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-3">
|
||||
<Head data={extra} />
|
||||
<SearchHead sort={order} count={count} />
|
||||
<ListGroup className="rounded-0 mb-5">
|
||||
{isLoading ? (
|
||||
<ListLoader />
|
||||
) : (
|
||||
list?.map((item) => {
|
||||
return <SearchItem key={item.object.id} data={item} />;
|
||||
})
|
||||
)}
|
||||
</ListGroup>
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
<Head data={extra} />
|
||||
<SearchHead sort={order} count={count} />
|
||||
<ListGroup className="rounded-0 mb-5">
|
||||
{isLoading ? (
|
||||
<ListLoader />
|
||||
) : (
|
||||
list?.map((item) => {
|
||||
return <SearchItem key={item.object.id} data={item} />;
|
||||
})
|
||||
)}
|
||||
</ListGroup>
|
||||
|
||||
{!isLoading && !list?.length && <Empty />}
|
||||
{!isLoading && !list?.length && <Empty />}
|
||||
|
||||
<div className="d-flex justify-content-center">
|
||||
<Pagination
|
||||
currentPage={Number(page)}
|
||||
pageSize={20}
|
||||
totalSize={count}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Tips />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Pagination
|
||||
currentPage={Number(page)}
|
||||
pageSize={20}
|
||||
totalSize={count}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Tips />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { SideNav } from '@/components';
|
||||
|
||||
import '@/common/sideNavLayout.scss';
|
||||
|
||||
const Index: FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Row>
|
||||
<SideNav />
|
||||
<Col xl={10} lg={9} md={12}>
|
||||
<Outlet />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -139,14 +139,10 @@ const Index = () => {
|
|||
});
|
||||
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10} md={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<div className="pt-4 mb-5">
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<Row>
|
||||
<Col className="flex-auto">
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Form.Group controlId="display_name" className="mb-3">
|
||||
<Form.Label>{t('form.fields.display_name.label')}</Form.Label>
|
||||
|
@ -208,7 +204,7 @@ const Index = () => {
|
|||
</div>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Card>
|
||||
<Card.Header>
|
||||
{t('title', { keyPrefix: 'how_to_format' })}
|
||||
|
@ -222,7 +218,7 @@ const Index = () => {
|
|||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, useEffect, useState } from 'react';
|
||||
import { Container, Row, Col, Button } from 'react-bootstrap';
|
||||
import { Row, Col, Button } from 'react-bootstrap';
|
||||
import {
|
||||
useParams,
|
||||
Link,
|
||||
|
@ -100,64 +100,62 @@ const Questions: FC = () => {
|
|||
keywords: keywords.join(','),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
{isLoading || listLoading ? (
|
||||
<div className="tag-box mb-5 placeholder-glow">
|
||||
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
|
||||
<p
|
||||
className="placeholder w-100 d-block align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
{isLoading || listLoading ? (
|
||||
<div className="tag-box mb-5 placeholder-glow">
|
||||
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
|
||||
<p
|
||||
className="placeholder w-100 d-block align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder d-block align-top"
|
||||
style={{ height: '38px', width: '100px' }}
|
||||
/>
|
||||
<div
|
||||
className="placeholder d-block align-top"
|
||||
style={{ height: '38px', width: '100px' }}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="tag-box mb-5">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<p className="text-break">
|
||||
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
||||
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
||||
[{t('more')}]
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="box-ft">
|
||||
{tagInfo.is_follower ? (
|
||||
<Button variant="primary" onClick={() => toggleFollow()}>
|
||||
{t('button_following')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={() => toggleFollow()}>
|
||||
{t('button_follow')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tag-box mb-5">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<p className="text-break">
|
||||
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
||||
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
||||
[{t('more')}]
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="box-ft">
|
||||
{tagInfo.is_follower ? (
|
||||
<Button variant="primary" onClick={() => toggleFollow()}>
|
||||
{t('button_following')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={() => toggleFollow()}>
|
||||
{t('button_follow')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<QuestionList source="tag" data={listData} isLoading={listLoading} />
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<CustomSidebar />
|
||||
<FollowingTags />
|
||||
<HotQuestions />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
)}
|
||||
<QuestionList source="tag" data={listData} isLoading={listLoading} />
|
||||
</Col>
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<CustomSidebar />
|
||||
<FollowingTags />
|
||||
<HotQuestions />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -188,14 +188,10 @@ const Index = () => {
|
|||
title: t('edit_tag', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10} md={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<div className="pt-4 mb-5">
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<Row>
|
||||
<Col className="flex-auto">
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Form.Group controlId="revision" className="mb-3">
|
||||
<Form.Label>{t('form.fields.revision.label')}</Form.Label>
|
||||
|
@ -291,7 +287,7 @@ const Index = () => {
|
|||
</div>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Card>
|
||||
<Card.Header>
|
||||
{t('title', { keyPrefix: 'how_to_format' })}
|
||||
|
@ -305,7 +301,7 @@ const Index = () => {
|
|||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Container, Row, Col, Button, Card } from 'react-bootstrap';
|
||||
import { Row, Col, Button, Card } from 'react-bootstrap';
|
||||
import { useParams, useNavigate, Link, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -132,132 +132,130 @@ const TagIntroduction = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<div className="text-secondary mb-4 fs-14">
|
||||
<FormatTime preFix={t('created_at')} time={tagInfo.created_at} />
|
||||
<FormatTime
|
||||
preFix={t('edited_at')}
|
||||
className="ms-3"
|
||||
time={tagInfo.updated_at}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="content text-break fmt"
|
||||
dangerouslySetInnerHTML={{ __html: tagInfo?.parsed_text }}
|
||||
<div className="text-secondary mb-4 fs-14">
|
||||
<FormatTime preFix={t('created_at')} time={tagInfo.created_at} />
|
||||
<FormatTime
|
||||
preFix={t('edited_at')}
|
||||
className="ms-3"
|
||||
time={tagInfo.updated_at}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
{tagInfo?.member_actions.map((action, index) => {
|
||||
return (
|
||||
<Button
|
||||
key={action.name}
|
||||
variant="link"
|
||||
className={classNames(
|
||||
'link-secondary btn-no-border p-0 fs-14',
|
||||
index > 0 && 'ms-3',
|
||||
)}
|
||||
onClick={() => onAction(action)}>
|
||||
{action.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{isLogged && (
|
||||
<Link
|
||||
to={`/tags/${tagInfo?.tag_id}/timeline`}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="content text-break fmt"
|
||||
dangerouslySetInnerHTML={{ __html: tagInfo?.parsed_text }}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
{tagInfo?.member_actions.map((action, index) => {
|
||||
return (
|
||||
<Button
|
||||
key={action.name}
|
||||
variant="link"
|
||||
className={classNames(
|
||||
'link-secondary btn-no-border p-0 fs-14',
|
||||
tagInfo?.member_actions?.length > 0 && 'ms-3',
|
||||
)}>
|
||||
{t('history')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Card>
|
||||
<Card.Header className="d-flex justify-content-between">
|
||||
<span>{t('synonyms.title')}</span>
|
||||
{isEdit ? (
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 btn-no-border"
|
||||
onClick={handleSave}>
|
||||
{t('synonyms.btn_save')}
|
||||
</Button>
|
||||
) : synonymsData?.member_actions?.find(
|
||||
(v) => v.action === 'edit',
|
||||
) ? (
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 btn-no-border"
|
||||
onClick={handleEdit}>
|
||||
{t('synonyms.btn_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
{isEdit && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
{t('synonyms.text')}{' '}
|
||||
<Tag
|
||||
data={{
|
||||
slug_name: tagName || '',
|
||||
main_tag_slug_name: '',
|
||||
display_name:
|
||||
tagInfo?.display_name || tagInfo?.slug_name || '',
|
||||
recommend: false,
|
||||
reserved: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TagSelector
|
||||
value={synonymsData?.synonyms}
|
||||
onChange={handleTagsChange}
|
||||
hiddenDescription
|
||||
index > 0 && 'ms-3',
|
||||
)}
|
||||
onClick={() => onAction(action)}>
|
||||
{action.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{isLogged && (
|
||||
<Link
|
||||
to={`/tags/${tagInfo?.tag_id}/timeline`}
|
||||
className={classNames(
|
||||
'link-secondary btn-no-border p-0 fs-14',
|
||||
tagInfo?.member_actions?.length > 0 && 'ms-3',
|
||||
)}>
|
||||
{t('history')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<Card>
|
||||
<Card.Header className="d-flex justify-content-between">
|
||||
<span>{t('synonyms.title')}</span>
|
||||
{isEdit ? (
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 btn-no-border"
|
||||
onClick={handleSave}>
|
||||
{t('synonyms.btn_save')}
|
||||
</Button>
|
||||
) : synonymsData?.member_actions?.find(
|
||||
(v) => v.action === 'edit',
|
||||
) ? (
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 btn-no-border"
|
||||
onClick={handleEdit}>
|
||||
{t('synonyms.btn_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
{isEdit && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
{t('synonyms.text')}{' '}
|
||||
<Tag
|
||||
data={{
|
||||
slug_name: tagName || '',
|
||||
main_tag_slug_name: '',
|
||||
display_name:
|
||||
tagInfo?.display_name || tagInfo?.slug_name || '',
|
||||
recommend: false,
|
||||
reserved: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TagSelector
|
||||
value={synonymsData?.synonyms}
|
||||
onChange={handleTagsChange}
|
||||
hiddenDescription
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!isEdit &&
|
||||
(synonymsData?.synonyms && synonymsData.synonyms.length > 0 ? (
|
||||
<div className="m-n1">
|
||||
{synonymsData.synonyms.map((item) => {
|
||||
return (
|
||||
<Tag key={item.tag_id} className="m-1" data={item} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-muted mb-3">{t('synonyms.empty')}</div>
|
||||
{synonymsData?.member_actions?.find(
|
||||
(v) => v.action === 'edit',
|
||||
) && (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={handleEdit}>
|
||||
{t('synonyms.btn_add')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isEdit &&
|
||||
(synonymsData?.synonyms && synonymsData.synonyms.length > 0 ? (
|
||||
<div className="m-n1">
|
||||
{synonymsData.synonyms.map((item) => {
|
||||
return (
|
||||
<Tag key={item.tag_id} className="m-1" data={item} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-muted mb-3">{t('synonyms.empty')}</div>
|
||||
{synonymsData?.member_actions?.find(
|
||||
(v) => v.action === 'edit',
|
||||
) && (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={handleEdit}>
|
||||
{t('synonyms.btn_add')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
))}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
Button,
|
||||
Form,
|
||||
Stack,
|
||||
} from 'react-bootstrap';
|
||||
import { Row, Col, Card, Button, Form, Stack } from 'react-bootstrap';
|
||||
import { useSearchParams, Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -60,90 +52,89 @@ const Tags = () => {
|
|||
title: t('tags', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
return (
|
||||
<Container className="py-3 my-3">
|
||||
<Row className="mb-4 d-flex justify-content-center">
|
||||
<Col xxl={10} sm={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<div className="d-block d-sm-flex justify-content-between align-items-center flex-wrap">
|
||||
<Stack direction="horizontal" gap={3} className="mb-3 mb-sm-0">
|
||||
<Form>
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Control
|
||||
value={searchTag}
|
||||
placeholder={t('search_placeholder')}
|
||||
type="text"
|
||||
onChange={handleChange}
|
||||
size="sm"
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
{role_id === 2 || role_id === 3 ? (
|
||||
<Link
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
to="/tags/create">
|
||||
{t('title', { keyPrefix: 'tag_modal' })}
|
||||
</Link>
|
||||
) : null}
|
||||
</Stack>
|
||||
<QueryGroup
|
||||
data={sortBtns}
|
||||
currentSort={sort || 'popular'}
|
||||
sortKey="sort"
|
||||
i18nKeyPrefix="tags.sort_buttons"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Row className="py-4 mb-4">
|
||||
<Col xxl={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<div className="d-block d-sm-flex justify-content-between align-items-center flex-wrap">
|
||||
<Stack direction="horizontal" gap={3} className="mb-3 mb-sm-0">
|
||||
<Form>
|
||||
<Form.Group controlId="formBasicEmail">
|
||||
<Form.Control
|
||||
value={searchTag}
|
||||
placeholder={t('search_placeholder')}
|
||||
type="text"
|
||||
onChange={handleChange}
|
||||
size="sm"
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
{role_id === 2 || role_id === 3 ? (
|
||||
<Link
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
to="/tags/create">
|
||||
{t('title', { keyPrefix: 'tag_modal' })}
|
||||
</Link>
|
||||
) : null}
|
||||
</Stack>
|
||||
<QueryGroup
|
||||
data={sortBtns}
|
||||
currentSort={sort || 'popular'}
|
||||
sortKey="sort"
|
||||
i18nKeyPrefix="tags.sort_buttons"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col className="mt-4" xxl={10} sm={12}>
|
||||
<Row>
|
||||
{isLoading ? (
|
||||
<TagsLoader />
|
||||
) : (
|
||||
tags?.list?.map((tag) => (
|
||||
<Col
|
||||
key={tag.slug_name}
|
||||
xs={12}
|
||||
lg={3}
|
||||
md={4}
|
||||
sm={6}
|
||||
className="mb-4">
|
||||
<Card className="h-100">
|
||||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<Tag className="mb-3" data={tag} />
|
||||
<Col className="mt-4" xxl={12}>
|
||||
<Row>
|
||||
{isLoading ? (
|
||||
<TagsLoader />
|
||||
) : (
|
||||
tags?.list?.map((tag) => (
|
||||
<Col
|
||||
key={tag.slug_name}
|
||||
xl={3}
|
||||
lg={4}
|
||||
md={4}
|
||||
sm={6}
|
||||
xs={12}
|
||||
className="mb-4">
|
||||
<Card className="h-100">
|
||||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<Tag className="mb-3" data={tag} />
|
||||
|
||||
<div className="fs-14 flex-fill text-break text-wrap text-truncate-3 reset-p mb-3">
|
||||
{escapeRemove(tag.excerpt)}
|
||||
</div>
|
||||
<div className="d-flex align-items-center">
|
||||
<Button
|
||||
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={() => handleFollow(tag)}>
|
||||
{tag.is_follower
|
||||
? t('button_following')
|
||||
: t('button_follow')}
|
||||
</Button>
|
||||
<span className="text-secondary fs-14 text-nowrap">
|
||||
{formatCount(tag.question_count)} {t('tag_label')}
|
||||
</span>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))
|
||||
)}
|
||||
</Row>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalSize={tags?.count || 0}
|
||||
pageSize={pageSize}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<div className="fs-14 flex-fill text-break text-wrap text-truncate-3 reset-p mb-3">
|
||||
{escapeRemove(tag.excerpt)}
|
||||
</div>
|
||||
<div className="d-flex align-items-center">
|
||||
<Button
|
||||
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={() => handleFollow(tag)}>
|
||||
{tag.is_follower
|
||||
? t('button_following')
|
||||
: t('button_follow')}
|
||||
</Button>
|
||||
<span className="text-secondary fs-14 text-nowrap">
|
||||
{formatCount(tag.question_count)} {t('tag_label')}
|
||||
</span>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))
|
||||
)}
|
||||
</Row>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalSize={tags?.count || 0}
|
||||
pageSize={pageSize}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, useState, useEffect } from 'react';
|
||||
import { Container, Row, Col, Form, Table } from 'react-bootstrap';
|
||||
import { Form, Table } from 'react-bootstrap';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -79,54 +79,50 @@ const Index: FC = () => {
|
|||
title: pageTitle,
|
||||
});
|
||||
return (
|
||||
<Container className="py-3">
|
||||
<Row className="py-3 justify-content-center">
|
||||
<Col xxl={10}>
|
||||
<h5 className="mb-4">
|
||||
{timelineData?.object_info.object_type === 'tag'
|
||||
? t('tag_title')
|
||||
: t('title')}{' '}
|
||||
<Link to={linkUrl}>{timelineData?.object_info?.title}</Link>
|
||||
</h5>
|
||||
{timelineData?.object_info.object_type !== 'tag' && (
|
||||
<Form.Check
|
||||
className="mb-4"
|
||||
type="switch"
|
||||
id="custom-switch"
|
||||
label={t('show_votes')}
|
||||
checked={showVotes}
|
||||
onChange={(e) => handleSwitch(e.target.checked)}
|
||||
/>
|
||||
)}
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '20%' }}>{t('datetime')}</th>
|
||||
<th style={{ width: '15%' }}>{t('type')}</th>
|
||||
<th style={{ width: '19%' }}>{t('by')}</th>
|
||||
<th>{t('comment')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timelineData?.timeline?.map((item) => {
|
||||
return (
|
||||
<HistoryItem
|
||||
data={item}
|
||||
objectInfo={timelineData?.object_info}
|
||||
key={item.activity_id}
|
||||
isAdmin={role_id === 2}
|
||||
revisionList={revisionList}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
{!isLoading && Number(timelineData?.timeline?.length) <= 0 && (
|
||||
<Empty>{t('no_data')}</Empty>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<div className="py-4 mb-5">
|
||||
<h5 className="mb-4">
|
||||
{timelineData?.object_info.object_type === 'tag'
|
||||
? t('tag_title')
|
||||
: t('title')}{' '}
|
||||
<Link to={linkUrl}>{timelineData?.object_info?.title}</Link>
|
||||
</h5>
|
||||
{timelineData?.object_info.object_type !== 'tag' && (
|
||||
<Form.Check
|
||||
className="mb-4"
|
||||
type="switch"
|
||||
id="custom-switch"
|
||||
label={t('show_votes')}
|
||||
checked={showVotes}
|
||||
onChange={(e) => handleSwitch(e.target.checked)}
|
||||
/>
|
||||
)}
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '20%' }}>{t('datetime')}</th>
|
||||
<th style={{ width: '15%' }}>{t('type')}</th>
|
||||
<th style={{ width: '19%' }}>{t('by')}</th>
|
||||
<th>{t('comment')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timelineData?.timeline?.map((item) => {
|
||||
return (
|
||||
<HistoryItem
|
||||
data={item}
|
||||
objectInfo={timelineData?.object_info}
|
||||
key={item.activity_id}
|
||||
isAdmin={role_id === 2}
|
||||
revisionList={revisionList}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
{!isLoading && Number(timelineData?.timeline?.length) <= 0 && (
|
||||
<Empty>{t('no_data')}</Empty>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Container, Row, Col, ButtonGroup, Button } from 'react-bootstrap';
|
||||
import { Row, Col, ButtonGroup, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
|
@ -74,62 +74,60 @@ const Notifications = () => {
|
|||
title: t('notifications', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<div className="d-flex justify-content-between mb-3">
|
||||
<ButtonGroup size="sm">
|
||||
<Button
|
||||
as="a"
|
||||
href="/users/notifications/inbox"
|
||||
variant="outline-secondary"
|
||||
active={type === 'inbox'}
|
||||
onClick={(evt) => handleTypeChange(evt, 'inbox')}>
|
||||
{t('inbox')}
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="/users/notifications/achievement"
|
||||
variant="outline-secondary"
|
||||
active={type === 'achievement'}
|
||||
onClick={(evt) => handleTypeChange(evt, 'achievement')}>
|
||||
{t('achievement')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Row className="pt-4 mb-5">
|
||||
<Col className="flex-auto">
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
<div className="d-flex justify-content-between mb-3">
|
||||
<ButtonGroup size="sm">
|
||||
<Button
|
||||
size="sm"
|
||||
as="a"
|
||||
href="/users/notifications/inbox"
|
||||
variant="outline-secondary"
|
||||
onClick={handleUnreadNotification}>
|
||||
{t('all_read')}
|
||||
active={type === 'inbox'}
|
||||
onClick={(evt) => handleTypeChange(evt, 'inbox')}>
|
||||
{t('inbox')}
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="/users/notifications/achievement"
|
||||
variant="outline-secondary"
|
||||
active={type === 'achievement'}
|
||||
onClick={(evt) => handleTypeChange(evt, 'achievement')}>
|
||||
{t('achievement')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline-secondary"
|
||||
onClick={handleUnreadNotification}>
|
||||
{t('all_read')}
|
||||
</Button>
|
||||
</div>
|
||||
{type === 'inbox' && (
|
||||
<Inbox
|
||||
data={notificationData}
|
||||
handleReadNotification={handleReadNotification}
|
||||
/>
|
||||
)}
|
||||
{type === 'achievement' && (
|
||||
<Achievements
|
||||
data={notificationData}
|
||||
handleReadNotification={handleReadNotification}
|
||||
/>
|
||||
)}
|
||||
{(data?.count || 0) > PAGE_SIZE * page && (
|
||||
<div className="d-flex justify-content-center align-items-center py-3">
|
||||
<Button
|
||||
variant="link"
|
||||
className="btn-no-border"
|
||||
onClick={handleLoadMore}>
|
||||
{t('show_more')}
|
||||
</Button>
|
||||
</div>
|
||||
{type === 'inbox' && (
|
||||
<Inbox
|
||||
data={notificationData}
|
||||
handleReadNotification={handleReadNotification}
|
||||
/>
|
||||
)}
|
||||
{type === 'achievement' && (
|
||||
<Achievements
|
||||
data={notificationData}
|
||||
handleReadNotification={handleReadNotification}
|
||||
/>
|
||||
)}
|
||||
{(data?.count || 0) > PAGE_SIZE * page && (
|
||||
<div className="d-flex justify-content-center align-items-center py-3">
|
||||
<Button
|
||||
variant="link"
|
||||
className="btn-no-border"
|
||||
onClick={handleLoadMore}>
|
||||
{t('show_more')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0" />
|
||||
</Row>
|
||||
</Container>
|
||||
)}
|
||||
</Col>
|
||||
<Col className="page-right-side" />
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useSearchParams, Link } from 'react-router-dom';
|
||||
|
||||
|
@ -58,19 +58,19 @@ const Personal: FC = () => {
|
|||
});
|
||||
|
||||
return (
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<div className="pt-4 mb-5">
|
||||
<Row>
|
||||
{userInfo?.status !== 'normal' && userInfo?.status_msg && (
|
||||
<Alert data={userInfo?.status_msg} />
|
||||
)}
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<Col className="flex-auto">
|
||||
<UserInfo data={userInfo as UserInfoRes} />
|
||||
</Col>
|
||||
<Col
|
||||
xxl={3}
|
||||
lg={4}
|
||||
sm={12}
|
||||
className="d-flex justify-content-start justify-content-md-end">
|
||||
className="page-right-side mt-4 mt-xl-0 d-flex justify-content-start justify-content-md-end">
|
||||
{isSelf && (
|
||||
<div className="mb-3">
|
||||
<Link
|
||||
|
@ -83,11 +83,9 @@ const Personal: FC = () => {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10}>
|
||||
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
||||
</Col>
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<Row>
|
||||
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
||||
<Col className="flex-auto">
|
||||
<Overview
|
||||
visible={tabName === 'overview'}
|
||||
introduction={userInfo?.bio_html || ''}
|
||||
|
@ -120,7 +118,7 @@ const Personal: FC = () => {
|
|||
</div>
|
||||
)}
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<h5 className="mb-3">{t('stats')}</h5>
|
||||
{userInfo?.created_at && (
|
||||
<>
|
||||
|
@ -137,7 +135,7 @@ const Personal: FC = () => {
|
|||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Personal;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
.settings-nav {
|
||||
flex: none;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.settings-main {
|
||||
flex: none;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
// lg
|
||||
@media screen and (max-width: 1199.9px) {
|
||||
.settings-nav {
|
||||
width: 30%;
|
||||
}
|
||||
.settings-main {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
// sm
|
||||
@media screen and (max-width: 767.9px) {
|
||||
.settings-main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
|
@ -7,6 +7,8 @@ import { usePageTags } from '@/hooks';
|
|||
|
||||
import Nav from './components/Nav';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'settings.profile',
|
||||
|
@ -16,23 +18,14 @@ const Index: FC = () => {
|
|||
title: t('settings', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
return (
|
||||
<Container className="mt-4 mb-5 pb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10} md={12}>
|
||||
<h3 className="mb-4">{t('page_title', { keyPrefix: 'settings' })}</h3>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col xxl={1} />
|
||||
<Col md={3} lg={2} className="mb-3">
|
||||
<Nav />
|
||||
</Col>
|
||||
<Col md={9} lg={6}>
|
||||
<Outlet />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<Row className="mt-4 mb-5 pb-5">
|
||||
<Col className="settings-nav mb-4">
|
||||
<Nav />
|
||||
</Col>
|
||||
<Col className="settings-main">
|
||||
<Outlet />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Fragment } from 'react';
|
||||
|
@ -22,61 +22,60 @@ const Users = () => {
|
|||
|
||||
const keys = Object.keys(users);
|
||||
return (
|
||||
<Container className="py-3 my-3">
|
||||
<Row className="mb-4 d-flex justify-content-center">
|
||||
<Col xxl={10} sm={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
</Col>
|
||||
<Row className="py-4 mb-4 d-flex justify-content-center">
|
||||
<Col xxl={12}>
|
||||
<h3 className="mb-4">{t('title')}</h3>
|
||||
</Col>
|
||||
|
||||
<Col xxl={10} sm={12}>
|
||||
{keys.map((key, index) => {
|
||||
if (users[key]?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
<Row className="mb-4">
|
||||
<Col>
|
||||
<h6 className="mb-0">{t(key)}</h6>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className={index === keys.length - 1 ? '' : 'mb-4'}>
|
||||
{users[key]?.map((user) => (
|
||||
<Col
|
||||
key={user.username}
|
||||
xs={12}
|
||||
lg={3}
|
||||
md={4}
|
||||
sm={6}
|
||||
className="mb-4">
|
||||
<div className="d-flex">
|
||||
<Col xxl={12}>
|
||||
{keys.map((key, index) => {
|
||||
if (users[key]?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
<Row className="mb-4">
|
||||
<Col>
|
||||
<h6 className="mb-0">{t(key)}</h6>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className={index === keys.length - 1 ? '' : 'mb-4'}>
|
||||
{users[key]?.map((user) => (
|
||||
<Col
|
||||
key={user.username}
|
||||
xl={3}
|
||||
lg={4}
|
||||
md={4}
|
||||
sm={6}
|
||||
xs={12}
|
||||
className="mb-4">
|
||||
<div className="d-flex">
|
||||
<Link to={`/users/${user.username}`}>
|
||||
<Avatar
|
||||
size="48px"
|
||||
avatar={user?.avatar}
|
||||
searchStr="s=96"
|
||||
/>
|
||||
</Link>
|
||||
<div className="ms-2">
|
||||
<Link to={`/users/${user.username}`}>
|
||||
<Avatar
|
||||
size="48px"
|
||||
avatar={user?.avatar}
|
||||
searchStr="s=96"
|
||||
/>
|
||||
{user.display_name}
|
||||
</Link>
|
||||
<div className="ms-2">
|
||||
<Link to={`/users/${user.username}`}>
|
||||
{user.display_name}
|
||||
</Link>
|
||||
<div className="text-secondary fs-14">
|
||||
{key === 'users_with_the_most_vote'
|
||||
? `${user.vote_count} ${t('votes')}`
|
||||
: `${user.rank} ${t('reputation')}`}
|
||||
</div>
|
||||
<div className="text-secondary fs-14">
|
||||
{key === 'users_with_the_most_vote'
|
||||
? `${user.vote_count} ${t('votes')}`
|
||||
: `${user.rank} ${t('reputation')}`}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -40,127 +40,161 @@ const routes: RouteNode[] = [
|
|||
children: [
|
||||
// question and answer
|
||||
{
|
||||
index: true,
|
||||
page: 'pages/Questions',
|
||||
},
|
||||
{
|
||||
path: 'questions',
|
||||
page: 'pages/Questions',
|
||||
},
|
||||
{
|
||||
path: 'questions/ask',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid/:slugPermalink',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid/:slugPermalink/:aid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'posts/:qid/edit',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'posts/:qid/:aid/edit',
|
||||
page: 'pages/Questions/EditAnswer',
|
||||
loader: async ({ params }) => {
|
||||
const ret = await editCheck(params.aid as string, true);
|
||||
return ret;
|
||||
},
|
||||
guard: (args) => {
|
||||
return isEditable(args);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
page: 'pages/Search',
|
||||
},
|
||||
// tags
|
||||
{
|
||||
path: 'tags',
|
||||
page: 'pages/Tags',
|
||||
},
|
||||
{
|
||||
path: 'tags/create',
|
||||
page: 'pages/Tags/Create',
|
||||
guard: () => {
|
||||
return guard.isAdminOrModerator();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagName',
|
||||
page: 'pages/Tags/Detail',
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagName/info',
|
||||
page: 'pages/Tags/Info',
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagId/edit',
|
||||
page: 'pages/Tags/Edit',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
// for users
|
||||
{
|
||||
path: 'users',
|
||||
page: 'pages/Users',
|
||||
},
|
||||
{
|
||||
path: 'users/:username',
|
||||
page: 'pages/Users/Personal',
|
||||
},
|
||||
{
|
||||
path: 'users/:username/:tabName',
|
||||
page: 'pages/Users/Personal',
|
||||
},
|
||||
{
|
||||
path: 'users/settings',
|
||||
page: 'pages/Users/Settings',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
// side nav layout
|
||||
path: '/',
|
||||
page: 'pages/SideNavLayout',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
page: 'pages/Users/Settings/Profile',
|
||||
page: 'pages/Questions',
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
page: 'pages/Users/Settings/Profile',
|
||||
path: 'questions',
|
||||
page: 'pages/Questions',
|
||||
},
|
||||
{
|
||||
path: 'notify',
|
||||
page: 'pages/Users/Settings/Notification',
|
||||
path: 'questions/ask',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
page: 'pages/Users/Settings/Account',
|
||||
path: 'posts/:qid/edit',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'interface',
|
||||
page: 'pages/Users/Settings/Interface',
|
||||
path: 'posts/:qid/:aid/edit',
|
||||
page: 'pages/Questions/EditAnswer',
|
||||
loader: async ({ params }) => {
|
||||
const ret = await editCheck(params.aid as string, true);
|
||||
return ret;
|
||||
},
|
||||
guard: (args) => {
|
||||
return isEditable(args);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid/:slugPermalink',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid/:slugPermalink/:aid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
page: 'pages/Search',
|
||||
},
|
||||
// tags
|
||||
{
|
||||
path: 'tags',
|
||||
page: 'pages/Tags',
|
||||
},
|
||||
{
|
||||
path: 'tags/create',
|
||||
page: 'pages/Tags/Create',
|
||||
guard: () => {
|
||||
return guard.isAdminOrModerator();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagName',
|
||||
page: 'pages/Tags/Detail',
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagName/info',
|
||||
page: 'pages/Tags/Info',
|
||||
},
|
||||
{
|
||||
path: 'tags/:tagId/edit',
|
||||
page: 'pages/Tags/Edit',
|
||||
guard: () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
// for users
|
||||
{
|
||||
path: 'users',
|
||||
page: 'pages/Users',
|
||||
},
|
||||
{
|
||||
path: 'users/:username',
|
||||
page: 'pages/Users/Personal',
|
||||
},
|
||||
{
|
||||
path: 'users/:username/:tabName',
|
||||
page: 'pages/Users/Personal',
|
||||
},
|
||||
{
|
||||
path: 'users/settings',
|
||||
page: 'pages/Users/Settings',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
page: 'pages/Users/Settings/Profile',
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
page: 'pages/Users/Settings/Profile',
|
||||
},
|
||||
{
|
||||
path: 'notify',
|
||||
page: 'pages/Users/Settings/Notification',
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
page: 'pages/Users/Settings/Account',
|
||||
},
|
||||
{
|
||||
path: 'interface',
|
||||
page: 'pages/Users/Settings/Interface',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'users/notifications/:type',
|
||||
page: 'pages/Users/Notifications',
|
||||
},
|
||||
{
|
||||
path: '/posts/:qid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/posts/:qid/:aid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tags/:tid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
// for review
|
||||
{
|
||||
path: 'review',
|
||||
page: 'pages/Review',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'users/notifications/:type',
|
||||
page: 'pages/Users/Notifications',
|
||||
},
|
||||
|
||||
{
|
||||
path: 'users/login',
|
||||
page: 'pages/Users/Login',
|
||||
|
@ -243,27 +277,6 @@ const routes: RouteNode[] = [
|
|||
path: '/users/auth-landing',
|
||||
page: 'pages/Users/AuthCallback',
|
||||
},
|
||||
{
|
||||
path: '/posts/:qid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/posts/:qid/:aid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tags/:tid/timeline',
|
||||
page: 'pages/Timeline',
|
||||
guard: () => {
|
||||
return guard.logged();
|
||||
},
|
||||
},
|
||||
// for admin
|
||||
{
|
||||
path: 'admin',
|
||||
|
@ -374,11 +387,6 @@ const routes: RouteNode[] = [
|
|||
path: '/user-center/auth-failed',
|
||||
page: 'pages/UserCenter/AuthFailed',
|
||||
},
|
||||
// for review
|
||||
{
|
||||
path: 'review',
|
||||
page: 'pages/Review',
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
page: 'pages/404',
|
||||
|
|
|
@ -11,6 +11,7 @@ import customizeStore from './customize';
|
|||
import themeSettingStore from './themeSetting';
|
||||
import loginToContinueStore from './loginToContinue';
|
||||
import errorCodeStore from './errorCode';
|
||||
import sideNavStore from './sideNav';
|
||||
|
||||
export {
|
||||
toastStore,
|
||||
|
@ -26,4 +27,5 @@ export {
|
|||
loginToContinueStore,
|
||||
errorCodeStore,
|
||||
userCenterStore,
|
||||
sideNavStore,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import create from 'zustand';
|
||||
|
||||
type reviewData = {
|
||||
can_revision: boolean;
|
||||
revision: number;
|
||||
};
|
||||
|
||||
interface ErrorCodeType {
|
||||
visible: boolean;
|
||||
can_revision: boolean;
|
||||
revision: number;
|
||||
updateVisible: () => void;
|
||||
updateReiview: (params: reviewData) => void;
|
||||
}
|
||||
|
||||
const Index = create<ErrorCodeType>((set) => ({
|
||||
visible: false,
|
||||
can_revision: false,
|
||||
revision: 0,
|
||||
updateVisible: () => {
|
||||
set((state) => {
|
||||
return { visible: !state.visible };
|
||||
});
|
||||
},
|
||||
updateReiview: (params: reviewData) => {
|
||||
set(() => {
|
||||
return { ...params };
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
export default Index;
|
Loading…
Reference in New Issue