2019-09-18 19:52:39 +02:00
|
|
|
import React, { Component } from "react";
|
|
|
|
import {
|
|
|
|
withStyles,
|
|
|
|
Typography,
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
Paper,
|
|
|
|
ListItemText,
|
|
|
|
Avatar,
|
|
|
|
ListItemSecondaryAction,
|
|
|
|
ListItemAvatar,
|
|
|
|
ListSubheader,
|
|
|
|
CircularProgress,
|
|
|
|
IconButton,
|
|
|
|
Divider,
|
|
|
|
Tooltip
|
|
|
|
} from "@material-ui/core";
|
|
|
|
import { styles } from "./PageLayout.styles";
|
|
|
|
import Mastodon from "megalodon";
|
|
|
|
import { Account } from "../types/Account";
|
|
|
|
import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides";
|
|
|
|
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
|
|
|
|
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
|
|
|
import PersonAddIcon from "@material-ui/icons/PersonAdd";
|
|
|
|
import CheckIcon from "@material-ui/icons/Check";
|
|
|
|
import CloseIcon from "@material-ui/icons/Close";
|
|
|
|
import { withSnackbar, withSnackbarProps } from "notistack";
|
2019-04-21 19:45:47 +02:00
|
|
|
|
|
|
|
interface IRecommendationsPageProps extends withSnackbarProps {
|
|
|
|
classes: any;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IRecommendationsPageState {
|
|
|
|
viewIsLoading: boolean;
|
|
|
|
viewDidLoad?: boolean;
|
|
|
|
viewDidError?: Boolean;
|
|
|
|
viewDidErrorCode?: string;
|
|
|
|
requestedFollows?: [Account];
|
|
|
|
followSuggestions?: [Account];
|
|
|
|
}
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
class RecommendationsPage extends Component<
|
|
|
|
IRecommendationsPageProps,
|
|
|
|
IRecommendationsPageState
|
|
|
|
> {
|
2019-04-21 19:45:47 +02:00
|
|
|
client: Mastodon;
|
|
|
|
|
|
|
|
constructor(props: any) {
|
|
|
|
super(props);
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client = new Mastodon(
|
|
|
|
localStorage.getItem("access_token") as string,
|
|
|
|
localStorage.getItem("baseurl") + "/api/v1"
|
|
|
|
);
|
2019-04-21 19:45:47 +02:00
|
|
|
this.state = {
|
|
|
|
viewIsLoading: true
|
2019-09-18 19:52:39 +02:00
|
|
|
};
|
2019-04-21 19:45:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.get("/follow_requests")
|
|
|
|
.then((resp: any) => {
|
|
|
|
let requestedFollows: [Account] = resp.data;
|
|
|
|
this.setState({ requestedFollows });
|
|
|
|
})
|
|
|
|
.catch((err: Error) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.name
|
|
|
|
});
|
|
|
|
console.error(err.message);
|
2019-04-21 19:45:47 +02:00
|
|
|
});
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.get("/suggestions")
|
|
|
|
.then((resp: any) => {
|
|
|
|
let followSuggestions: [Account] = resp.data;
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidLoad: true,
|
|
|
|
followSuggestions
|
|
|
|
});
|
2019-04-21 19:45:47 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.name
|
|
|
|
});
|
|
|
|
console.error(err.message);
|
2019-04-21 19:45:47 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
followMember(acct: Account) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.post(`/accounts/${acct.id}/follow`)
|
|
|
|
.then((resp: any) => {
|
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
"You are now following this account."
|
|
|
|
);
|
|
|
|
this.client.del(`/suggestions/${acct.id}`).then((resp: any) => {
|
|
|
|
let followSuggestions = this.state.followSuggestions;
|
|
|
|
if (followSuggestions) {
|
|
|
|
followSuggestions.forEach(
|
|
|
|
(suggestion: Account, index: number) => {
|
|
|
|
if (
|
|
|
|
followSuggestions &&
|
|
|
|
suggestion.id === acct.id
|
|
|
|
) {
|
|
|
|
followSuggestions.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
this.setState({ followSuggestions });
|
|
|
|
}
|
|
|
|
});
|
2019-04-21 19:45:47 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
"Couldn't follow account: " + err.name,
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
|
|
|
console.error(err.message);
|
|
|
|
});
|
2019-04-21 19:45:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleFollowRequest(acct: Account, type: "authorize" | "reject") {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.post(`/follow_requests/${acct.id}/${type}`)
|
|
|
|
.then((resp: any) => {
|
|
|
|
let requestedFollows = this.state.requestedFollows;
|
|
|
|
if (requestedFollows) {
|
|
|
|
requestedFollows.forEach(
|
|
|
|
(request: Account, index: number) => {
|
|
|
|
if (requestedFollows && request.id === acct.id) {
|
|
|
|
requestedFollows.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.setState({ requestedFollows });
|
2019-04-21 19:45:47 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
let verb: string = type;
|
|
|
|
verb === "authorize"
|
|
|
|
? (verb = "authorized")
|
|
|
|
: (verb = "rejected");
|
|
|
|
this.props.enqueueSnackbar(`You have ${verb} this request.`);
|
|
|
|
})
|
|
|
|
.catch((err: Error) => {
|
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
`Couldn't ${type} this request: ${err.name}`,
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
|
|
|
console.error(err.message);
|
|
|
|
});
|
2019-04-21 19:45:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
showFollowRequests() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2019-04-21 19:45:47 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<ListSubheader>Follow requests</ListSubheader>
|
2019-09-18 19:52:39 +02:00
|
|
|
<Paper className={classes.pageListConstraints}>
|
|
|
|
<List>
|
|
|
|
{this.state.requestedFollows
|
|
|
|
? this.state.requestedFollows.map(
|
|
|
|
(request: Account) => {
|
|
|
|
return (
|
|
|
|
<ListItem key={request.id}>
|
|
|
|
<ListItemAvatar>
|
|
|
|
<LinkableAvatar
|
|
|
|
to={`/profile/${request.id}`}
|
|
|
|
alt={request.username}
|
|
|
|
src={
|
|
|
|
request.avatar_static
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</ListItemAvatar>
|
|
|
|
<ListItemText
|
|
|
|
primary={
|
|
|
|
request.display_name ||
|
|
|
|
request.acct
|
|
|
|
}
|
|
|
|
secondary={request.acct}
|
|
|
|
/>
|
|
|
|
<ListItemSecondaryAction>
|
|
|
|
<Tooltip title="Accept request">
|
|
|
|
<IconButton
|
|
|
|
onClick={() =>
|
|
|
|
this.handleFollowRequest(
|
|
|
|
request,
|
|
|
|
"authorize"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<CheckIcon />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Reject request">
|
|
|
|
<IconButton
|
|
|
|
onClick={() =>
|
|
|
|
this.handleFollowRequest(
|
|
|
|
request,
|
|
|
|
"reject"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<CloseIcon />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="View profile">
|
|
|
|
<LinkableIconButton
|
|
|
|
to={`/profile/${request.id}`}
|
|
|
|
>
|
|
|
|
<AccountCircleIcon />
|
|
|
|
</LinkableIconButton>
|
|
|
|
</Tooltip>
|
|
|
|
</ListItemSecondaryAction>
|
|
|
|
</ListItem>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
)
|
|
|
|
: null}
|
|
|
|
</List>
|
|
|
|
</Paper>
|
|
|
|
<br />
|
2019-04-21 19:45:47 +02:00
|
|
|
</div>
|
2019-09-18 19:52:39 +02:00
|
|
|
);
|
2019-04-21 19:45:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
showFollowSuggestions() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2019-04-21 19:45:47 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<ListSubheader>Suggested accounts</ListSubheader>
|
2019-09-18 19:52:39 +02:00
|
|
|
<Paper className={classes.pageListConstraints}>
|
|
|
|
<List>
|
|
|
|
{this.state.followSuggestions
|
|
|
|
? this.state.followSuggestions.map(
|
|
|
|
(suggestion: Account) => {
|
|
|
|
return (
|
|
|
|
<ListItem key={suggestion.id}>
|
|
|
|
<ListItemAvatar>
|
|
|
|
<LinkableAvatar
|
|
|
|
to={`/profile/${suggestion.id}`}
|
|
|
|
alt={suggestion.username}
|
|
|
|
src={
|
|
|
|
suggestion.avatar_static
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</ListItemAvatar>
|
|
|
|
<ListItemText
|
|
|
|
primary={
|
|
|
|
suggestion.display_name ||
|
|
|
|
suggestion.acct
|
|
|
|
}
|
|
|
|
secondary={suggestion.acct}
|
|
|
|
/>
|
|
|
|
<ListItemSecondaryAction>
|
|
|
|
<Tooltip title="View profile">
|
|
|
|
<LinkableIconButton
|
|
|
|
to={`/profile/${suggestion.id}`}
|
|
|
|
>
|
|
|
|
<AssignmentIndIcon />
|
|
|
|
</LinkableIconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Follow">
|
|
|
|
<IconButton
|
|
|
|
onClick={() =>
|
|
|
|
this.followMember(
|
|
|
|
suggestion
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<PersonAddIcon />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
</ListItemSecondaryAction>
|
|
|
|
</ListItem>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
)
|
|
|
|
: null}
|
|
|
|
</List>
|
|
|
|
</Paper>
|
|
|
|
<br />
|
2019-04-21 19:45:47 +02:00
|
|
|
</div>
|
2019-09-18 19:52:39 +02:00
|
|
|
);
|
2019-04-21 19:45:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2019-04-21 19:45:47 +02:00
|
|
|
return (
|
|
|
|
<div className={classes.pageLayoutConstraints}>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.viewDidLoad ? (
|
|
|
|
<div>
|
|
|
|
{this.state.requestedFollows &&
|
|
|
|
this.state.requestedFollows.length > 0 ? (
|
|
|
|
this.showFollowRequests()
|
|
|
|
) : (
|
|
|
|
<div
|
|
|
|
className={
|
|
|
|
classes.pageLayoutEmptyTextConstraints
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<Typography variant="h6">
|
|
|
|
You don't have any follow requests.
|
|
|
|
</Typography>
|
|
|
|
<br />
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
<Divider />
|
|
|
|
<br />
|
|
|
|
{this.state.followSuggestions &&
|
|
|
|
this.state.followSuggestions.length > 0 ? (
|
|
|
|
this.showFollowSuggestions()
|
|
|
|
) : (
|
|
|
|
<div
|
|
|
|
className={
|
|
|
|
classes.pageLayoutEmptyTextConstraints
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<Typography variant="h5">
|
|
|
|
We don't have any suggestions for you.
|
|
|
|
</Typography>
|
|
|
|
<Typography paragraph>
|
|
|
|
Why not interact with the fediverse a bit by
|
|
|
|
creating a new post?
|
|
|
|
</Typography>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
{this.state.viewDidError ? (
|
|
|
|
<Paper className={classes.errorCard}>
|
|
|
|
<Typography variant="h4">Bummer.</Typography>
|
|
|
|
<Typography variant="h6">
|
|
|
|
Something went wrong when loading this timeline.
|
|
|
|
</Typography>
|
|
|
|
<Typography>
|
|
|
|
{this.state.viewDidErrorCode
|
|
|
|
? this.state.viewDidErrorCode
|
|
|
|
: ""}
|
|
|
|
</Typography>
|
|
|
|
</Paper>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
|
|
|
{this.state.viewIsLoading ? (
|
|
|
|
<div style={{ textAlign: "center" }}>
|
|
|
|
<CircularProgress
|
|
|
|
className={classes.progress}
|
|
|
|
color="primary"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
2019-04-21 19:45:47 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
export default withStyles(styles)(withSnackbar(RecommendationsPage));
|