zcash-grant-system/frontend/client/components/Profile/index.tsx

285 lines
8.9 KiB
TypeScript
Raw Normal View History

import React from 'react';
import {
withRouter,
RouteComponentProps,
Redirect,
Switch,
Route,
} from 'react-router-dom';
import { connect } from 'react-redux';
import { compose } from 'recompose';
2019-01-23 07:15:59 -08:00
import { Tabs, Badge } from 'antd';
import { usersActions } from 'modules/users';
import { AppState } from 'store/reducers';
import HeaderDetails from 'components/HeaderDetails';
import ProfileUser from './ProfileUser';
import ProfileEdit from './ProfileEdit';
import ProfilePendingList from './ProfilePendingList';
import ProfileProposal from './ProfileProposal';
import ProfileContribution from './ProfileContribution';
import ProfileComment from './ProfileComment';
import ProfileInvite from './ProfileInvite';
import Placeholder from 'components/Placeholder';
2019-01-23 07:15:59 -08:00
import Loader from 'components/Loader';
import ExceptionPage from 'components/ExceptionPage';
import ContributionModal from 'components/ContributionModal';
import LinkableTabs from 'components/LinkableTabs';
import { UserContribution } from 'types';
2019-02-06 14:37:45 -08:00
import ProfileArbitrated from './ProfileArbitrated';
import './style.less';
interface StateProps {
usersMap: AppState['users']['map'];
authUser: AppState['auth']['user'];
}
interface DispatchProps {
fetchUser: typeof usersActions['fetchUser'];
fetchUserInvites: typeof usersActions['fetchUserInvites'];
}
type Props = RouteComponentProps<any> & StateProps & DispatchProps;
interface State {
activeContribution: UserContribution | null;
}
class Profile extends React.Component<Props, State> {
state: State = {
activeContribution: null,
};
componentDidMount() {
this.fetchData();
}
2019-01-23 08:35:03 -08:00
componentDidUpdate(prevProps: Props) {
const userLookupId = this.props.match.params.id;
const prevUserLookupId = prevProps.match.params.id;
if (userLookupId !== prevUserLookupId) {
window.scrollTo(0, 0);
this.fetchData();
}
}
2019-01-23 08:35:03 -08:00
render() {
const { authUser, match, location } = this.props;
const { activeContribution } = this.state;
const userLookupParam = match.params.id;
if (!userLookupParam) {
2018-12-14 11:36:22 -08:00
if (authUser && authUser.userid) {
return <Redirect to={{ ...location, pathname: `/profile/${authUser.userid}` }} />;
} else {
return <Redirect to={{ ...location, pathname: '/auth' }} />;
}
}
const user = this.props.usersMap[userLookupParam];
const waiting = !user || !user.hasFetched;
2018-12-14 11:36:22 -08:00
const isAuthedUser = user && authUser && user.userid === authUser.userid;
if (waiting) {
2019-01-23 08:35:03 -08:00
return <Loader size="large" />;
}
if (user.fetchError) {
return <ExceptionPage code="404" desc="No user could be found" />;
}
2019-02-06 14:37:45 -08:00
const {
proposals,
pendingProposals,
contributions,
comments,
invites,
arbitrated,
} = user;
const isLoading = user.isFetching;
const nonePending = pendingProposals.length === 0;
const noneCreated = proposals.length === 0;
const noneFunded = contributions.length === 0;
const noneCommented = comments.length === 0;
2019-02-06 14:37:45 -08:00
const noneArbitrated = arbitrated.length === 0;
const noneInvites = user.hasFetchedInvites && invites.length === 0;
return (
<div className="Profile">
<HeaderDetails
2019-01-22 10:40:20 -08:00
title={`${user.displayName} is funding projects on ZF Grants`}
description={`Join ${user.displayName} in funding the future!`}
2018-11-26 17:14:00 -08:00
image={user.avatar ? user.avatar.imageUrl : undefined}
/>
<Switch>
<Route
path={`${match.path}`}
exact={true}
render={() => <ProfileUser user={user} />}
/>
<Route
path={`${match.path}/edit`}
exact={true}
render={() => <ProfileEdit user={user} />}
/>
</Switch>
<div className="Profile-tabs">
<LinkableTabs defaultActiveKey={(isAuthedUser && 'pending') || 'created'}>
{isAuthedUser && (
<Tabs.TabPane
tab={TabTitle('Pending', pendingProposals.length)}
key="pending"
>
<div>
{nonePending && (
<Placeholder
loading={isLoading}
title="No pending proposals"
subtitle="You do not have any proposals awaiting approval."
/>
)}
<ProfilePendingList proposals={pendingProposals} />
</div>
</Tabs.TabPane>
)}
<Tabs.TabPane tab={TabTitle('Created', proposals.length)} key="created">
<div>
{noneCreated && (
<Placeholder
loading={isLoading}
title="No created proposals"
subtitle="There have not been any created proposals."
/>
)}
{proposals.map(p => (
<ProfileProposal key={p.proposalId} proposal={p} />
))}
</div>
</Tabs.TabPane>
<Tabs.TabPane tab={TabTitle('Funded', contributions.length)} key="funded">
<div>
{noneFunded && (
<Placeholder
loading={isLoading}
title="No proposals funded"
subtitle="There have not been any proposals funded."
/>
)}
{contributions.map(c => (
<ProfileContribution
key={c.id}
userId={user.userid}
contribution={c}
showSendInstructions={this.openContributionModal}
/>
))}
</div>
</Tabs.TabPane>
<Tabs.TabPane tab={TabTitle('Comments', comments.length)} key="comments">
<div>
{noneCommented && (
<Placeholder
loading={isLoading}
title="No comments"
subtitle="There have not been any comments made"
/>
)}
{comments.map(c => (
<ProfileComment key={c.id} userName={user.displayName} comment={c} />
))}
</div>
</Tabs.TabPane>
{isAuthedUser && (
<Tabs.TabPane
tab={TabTitle('Invites', invites.length)}
key="invites"
disabled={!user.hasFetchedInvites}
>
<div>
{noneInvites && (
<Placeholder
loading={isLoading}
title="No invitations"
subtitle="Youll be notified when youve been invited to join a proposal"
/>
)}
{invites.map(invite => (
<ProfileInvite key={invite.id} userId={user.userid} invite={invite} />
))}
</div>
</Tabs.TabPane>
)}
2019-02-06 14:37:45 -08:00
{isAuthedUser && (
<Tabs.TabPane
tab={TabTitle('Arbitrations', arbitrated.length)}
key="arbitrations"
>
{noneArbitrated && (
<Placeholder
loading={isLoading}
2019-02-06 14:37:45 -08:00
title="No arbitrations"
subtitle="You are not an arbiter of any proposals"
/>
)}
{arbitrated.map(arb => (
<ProfileArbitrated key={arb.proposal.proposalId} arbiter={arb} />
2019-02-06 14:37:45 -08:00
))}
</Tabs.TabPane>
)}
</LinkableTabs>
</div>
<ContributionModal
isVisible={!!activeContribution}
proposalId={
activeContribution ? activeContribution.proposal.proposalId : undefined
}
contributionId={activeContribution ? activeContribution.id : undefined}
hasNoButtons
handleClose={this.closeContributionModal}
/>
</div>
);
}
private fetchData() {
const { match } = this.props;
const userLookupId = match.params.id;
if (userLookupId) {
this.props.fetchUser(userLookupId);
this.props.fetchUserInvites(userLookupId);
}
}
private openContributionModal = (c: UserContribution) =>
this.setState({ activeContribution: c });
private closeContributionModal = () => this.setState({ activeContribution: null });
}
const TabTitle = (disp: string, count: number) => (
<div>
{disp}
<Badge
className={`Profile-tabBadge ${count > 0 ? 'is-not-zero' : 'is-zero'}`}
showZero={true}
count={count}
/>
</div>
);
const withConnect = connect<StateProps, DispatchProps, {}, AppState>(
state => ({
usersMap: state.users.map,
authUser: state.auth.user,
}),
{
fetchUser: usersActions.fetchUser,
fetchUserInvites: usersActions.fetchUserInvites,
},
);
export default compose<Props, {}>(
withRouter,
withConnect,
)(Profile);