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