mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/ui-0.7.0' of git.backyard.segmentfault.com:opensource/answer into feat/ui-0.7.0
This commit is contained in:
commit
32f009b096
|
@ -248,6 +248,7 @@ ui:
|
|||
install: Answer Installation
|
||||
upgrade: Answer Upgrade
|
||||
maintenance: Website Maintenance
|
||||
users: Users
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
|
@ -1104,7 +1105,7 @@ ui:
|
|||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: display_name must be at maximum 30 characters in length.
|
||||
msg: display_name must be at 4 - 30 characters in length.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
|
@ -1346,3 +1347,12 @@ ui:
|
|||
by: By
|
||||
comment: Comment
|
||||
no_data: "We couldn't find anything."
|
||||
users:
|
||||
title: Users
|
||||
users_with_the_most_reputation: Users with the highest reputation scores
|
||||
users_with_the_most_vote: Users who voted the most
|
||||
staffs: Our community staff
|
||||
reputation: reputation
|
||||
votes: votes
|
||||
|
||||
|
||||
|
|
|
@ -503,3 +503,11 @@ export interface MemberActionItem {
|
|||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
username: string;
|
||||
rank: number;
|
||||
vote_count: number;
|
||||
display_name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import { marked } from 'marked';
|
|||
import { htmlRender } from './utils';
|
||||
|
||||
let scrollTop = 0;
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
});
|
||||
|
||||
const Index = ({ value }, ref) => {
|
||||
const [html, setHtml] = useState('');
|
||||
|
|
|
@ -46,7 +46,7 @@ const Index: FC = () => {
|
|||
{t('save')}
|
||||
</Button>
|
||||
</Card.Header>
|
||||
<Card.Body className="my-n1">
|
||||
<Card.Body>
|
||||
<TagSelector
|
||||
value={followingTags}
|
||||
onChange={handleTagsChange}
|
||||
|
@ -67,14 +67,14 @@ const Index: FC = () => {
|
|||
{t('edit')}
|
||||
</Button>
|
||||
</Card.Header>
|
||||
<Card.Body className="m-n1">
|
||||
<Card.Body>
|
||||
{followingTags?.length ? (
|
||||
<>
|
||||
<div className="m-n1">
|
||||
{followingTags.map((item) => {
|
||||
const slugName = item?.slug_name;
|
||||
return <Tag key={slugName} className="m-1" data={item} />;
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-muted">{t('follow_tag_tip')}</div>
|
||||
|
|
|
@ -144,6 +144,9 @@ const Header: FC = () => {
|
|||
<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" />
|
||||
|
|
|
@ -181,7 +181,7 @@ const TagSelector: FC<IProps> = ({
|
|||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={handleKeyDown}>
|
||||
<div className="d-flex flex-wrap mx-n1">
|
||||
<div className="d-flex flex-wrap m-n1">
|
||||
{initialValue?.map((item, index) => {
|
||||
return (
|
||||
<Button
|
||||
|
|
|
@ -45,7 +45,10 @@ const useAddUserModal = (props: IProps = {}) => {
|
|||
display_name: {
|
||||
'ui:options': {
|
||||
validator: (value) => {
|
||||
if (value.length > 30) {
|
||||
const MIN_LENGTH = 4;
|
||||
const MAX_LENGTH = 30;
|
||||
|
||||
if (value.length < MIN_LENGTH || value.length > MAX_LENGTH) {
|
||||
return t('form.fields.display_name.msg');
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -413,7 +413,7 @@ const Index: React.FC = () => {
|
|||
<Form.Label>{t('website.label')}</Form.Label>
|
||||
<Form.Control
|
||||
required
|
||||
type="text"
|
||||
type="url"
|
||||
placeholder={t('website.placeholder')}
|
||||
value={formData.website.value}
|
||||
isInvalid={formData.website.isInvalid}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { usePageTags } from '@/hooks';
|
||||
import { useQueryContributeUsers } from '@/services';
|
||||
import { Avatar } from '@/components';
|
||||
|
||||
const Users = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'users' });
|
||||
|
||||
const { data: users } = useQueryContributeUsers();
|
||||
|
||||
usePageTags({
|
||||
title: t('users', { keyPrefix: 'page_title' }),
|
||||
});
|
||||
|
||||
if (!users) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<Col xxl={10} sm={12}>
|
||||
{keys.map((key, index) => {
|
||||
if (users[key]?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<Avatar size="48px" avatar={user?.avatar} />
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
|
@ -92,6 +92,10 @@ const routes: RouteNode[] = [
|
|||
},
|
||||
},
|
||||
// for users
|
||||
{
|
||||
path: 'users',
|
||||
page: 'pages/Users',
|
||||
},
|
||||
{
|
||||
path: 'users/:username',
|
||||
page: 'pages/Users/Personal',
|
||||
|
|
|
@ -8,3 +8,4 @@ export * from './settings';
|
|||
export * from './legal';
|
||||
export * from './timeline';
|
||||
export * from './revision';
|
||||
export * from './user';
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import useSWR from 'swr';
|
||||
|
||||
import request from '@/utils/request';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
export const useQueryContributeUsers = () => {
|
||||
const apiUrl = '/answer/api/v1/user/ranking';
|
||||
return useSWR<{
|
||||
users_with_the_most_reputation: Type.User[];
|
||||
users_with_the_most_vote: Type.User[];
|
||||
staffs: Type.User[];
|
||||
}>(apiUrl, request.instance.get);
|
||||
};
|
Loading…
Reference in New Issue