You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

280 lines
8.3 KiB

/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import React, { Component } from 'react';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
UserOperation,
Community,
ListCommunitiesResponse,
CommunityResponse,
FollowCommunityForm,
SortType,
WebSocketJsonResponse,
} from '../interfaces';
import { WebSocketService } from '../services';
import { wsJsonToRes, toast, getPageFromProps, siteName, api } from '../utils';
import { CommunityLink } from './community-link';
import { i18n } from '../i18next';
import { linkEvent } from '../linkEvent';
import { SpinnerSection } from './Spinner';
import Button from './elements/Button';
import { Heading } from 'theme-ui';
import Block from './elements/Block';
import { AxiosError } from 'axios';
const communityLimit = 100;
interface CommunitiesState {
communities: Array<Community>;
page: number;
loading: boolean;
}
interface CommunitiesProps {
page: number;
}
export class Communities extends Component<any, CommunitiesState> {
private subscription: Subscription;
private emptyState: CommunitiesState = {
communities: [],
loading: true,
page: getPageFromProps(this.props),
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
document.title = `${i18n.t('communities')} - ${siteName}`;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
msg => this.parseMessage(msg),
err => console.error(err),
() => console.log('complete')
);
this.fetchCommunityList();
}
componentWillUnmount(): void {
this.subscription.unsubscribe();
}
static getDerivedStateFromProps(props: any): CommunitiesProps {
return {
page: getPageFromProps(props),
};
}
componentDidUpdate(_: any, lastState: CommunitiesState): void {
if (lastState.page !== this.state.page) {
this.setState({ loading: true });
this.fetchCommunityList();
}
}
render(): JSX.Element {
const { subscribed, notSubscribed } = this.state.communities.reduce(
(result, community) => {
if (community.subscribed) {
result.subscribed.push(community);
} else {
result.notSubscribed.push(community);
}
return result;
},
{
subscribed: [],
notSubscribed: [],
}
);
return (
<div className="container">
{this.state.loading ? (
<SpinnerSection />
) : (
<Block>
<Heading as="h5" mb={4}>
{i18n.t('list_of_communities')}
</Heading>
{[notSubscribed, subscribed].map((communities, index) => {
return (
<>
<Heading as="h3" my={2}>
{index === 0 ? 'Not Subscribed' : 'Subscribed'}
</Heading>
{communities.map(community => (
<Block
key={community.id}
display="flex"
flexDirection="column"
my={2}
maxWidth="600px"
>
<Block
display="flex"
justifyContent="space-between"
flexWrap="wrap"
>
<Block as="h4">
<CommunityLink community={community} />
</Block>
<Block mb={1}>
{community.subscribed ? (
<Button
onClick={linkEvent(
community.id,
this.handleUnsubscribe
)}
>
{i18n.t('unsubscribe')}
</Button>
) : (
<Button
onClick={linkEvent(
community.id,
this.handleSubscribe
)}
>
{i18n.t('subscribe')}
</Button>
)}
</Block>
</Block>
<Block as="h5" display="inline" mb={1}>
{community.title}
</Block>
<Block display="flex" justifyContent="space-between">
<Block>{community.category_name}</Block>
<Block>
{community.number_of_subscribers}{' '}
{i18n.t('subscribers')}
</Block>
</Block>
<Block display="flex" justifyContent="space-between">
<Block>
{community.number_of_posts} {i18n.t('posts')}
</Block>
<Block>
{community.number_of_comments} {i18n.t('comments')}
</Block>
</Block>
</Block>
))}
</>
);
})}
{this.paginator()}
</Block>
)}
</div>
);
}
paginator(): JSX.Element {
return (
<div className="mt-2">
{this.state.page > 1 && (
<Button
className="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
</Button>
)}
{this.state.communities.length > 0 && (
<Button
className="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
</Button>
)}
</div>
);
}
updateUrl(paramUpdates: CommunitiesProps): void {
const page = paramUpdates.page || this.state.page;
this.props.history.push(`/communities/page/${page}`);
}
nextPage(i: Communities): void {
i.updateUrl({ page: i.state.page + 1 });
}
prevPage(i: Communities): void {
i.updateUrl({ page: i.state.page - 1 });
}
handleUnsubscribe(communityId: number): void {
let form: FollowCommunityForm = {
community_id: communityId,
follow: false,
};
WebSocketService.Instance.followCommunity(form);
}
handleSubscribe(communityId: number): void {
let form: FollowCommunityForm = {
community_id: communityId,
follow: true,
};
WebSocketService.Instance.followCommunity(form);
}
async fetchCommunityList(): Promise<void> {
const params = new URLSearchParams({
sort: SortType[SortType.New],
limit: communityLimit,
page: this.state.page,
} as any);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
const data = res.data;
this.setState(
{
communities: data.communities.sort(
(a, b) => b.number_of_subscribers - a.number_of_subscribers
),
loading: false,
},
() => {
window.scrollTo(0, 0);
// XXX: Sort this table?
let table = document.querySelector('#community_table');
return table;
}
);
})
.catch((err: Error | AxiosError) => {
const res = (err as AxiosError).response;
if (res) {
const data = res.data as { error: string };
toast(i18n.t(data.error), 'danger');
} else {
console.log(err);
}
});
}
parseMessage(msg: WebSocketJsonResponse) {
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
return;
} else if (res.op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse;
let found = this.state.communities.find(c => c.id == data.community.id);
found.subscribed = data.community.subscribed;
found.number_of_subscribers = data.community.number_of_subscribers;
this.setState(this.state);
}
}
}