2018-11-13 08:07:09 -08:00
|
|
|
import React from 'react';
|
|
|
|
import { connect } from 'react-redux';
|
2018-11-13 14:51:02 -08:00
|
|
|
import { Link } from 'react-router-dom';
|
2019-01-28 16:51:10 -08:00
|
|
|
import { Button, Divider, List, message, Popconfirm, Spin } from 'antd';
|
2018-11-15 08:02:16 -08:00
|
|
|
import Placeholder from 'components/Placeholder';
|
2019-01-28 16:51:10 -08:00
|
|
|
import { getIsVerified } from 'modules/auth/selectors';
|
2019-01-23 07:15:59 -08:00
|
|
|
import Loader from 'components/Loader';
|
2019-01-09 10:23:08 -08:00
|
|
|
import { ProposalDraft, STATUS } from 'types';
|
2019-02-01 11:13:30 -08:00
|
|
|
import {
|
|
|
|
createDraft,
|
|
|
|
deleteDraft,
|
|
|
|
fetchDrafts,
|
|
|
|
fetchAndCreateDrafts,
|
|
|
|
} from 'modules/create/actions';
|
2018-11-13 14:51:02 -08:00
|
|
|
import { AppState } from 'store/reducers';
|
|
|
|
import './style.less';
|
2018-11-13 08:07:09 -08:00
|
|
|
|
2018-11-13 14:51:02 -08:00
|
|
|
interface StateProps {
|
|
|
|
drafts: AppState['create']['drafts'];
|
|
|
|
isFetchingDrafts: AppState['create']['isFetchingDrafts'];
|
|
|
|
fetchDraftsError: AppState['create']['fetchDraftsError'];
|
|
|
|
isCreatingDraft: AppState['create']['isCreatingDraft'];
|
|
|
|
createDraftError: AppState['create']['createDraftError'];
|
2018-11-15 08:02:16 -08:00
|
|
|
isDeletingDraft: AppState['create']['isDeletingDraft'];
|
|
|
|
deleteDraftError: AppState['create']['deleteDraftError'];
|
2019-01-28 16:51:10 -08:00
|
|
|
isVerified: ReturnType<typeof getIsVerified>;
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
interface DispatchProps {
|
|
|
|
fetchDrafts: typeof fetchDrafts;
|
|
|
|
createDraft: typeof createDraft;
|
2018-11-15 08:02:16 -08:00
|
|
|
deleteDraft: typeof deleteDraft;
|
2019-02-01 11:13:30 -08:00
|
|
|
fetchAndCreateDrafts: typeof fetchAndCreateDrafts;
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
interface OwnProps {
|
|
|
|
createIfNone?: boolean;
|
2019-02-01 11:13:30 -08:00
|
|
|
createWithRfpId?: number;
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type Props = StateProps & DispatchProps & OwnProps;
|
|
|
|
|
2018-11-15 08:02:16 -08:00
|
|
|
interface State {
|
|
|
|
deletingId: number | null;
|
|
|
|
}
|
|
|
|
|
2019-11-24 07:07:03 -08:00
|
|
|
const EMAIL_VERIFIED_RELOAD_TIMEOUT = 10000;
|
|
|
|
|
2018-11-15 08:02:16 -08:00
|
|
|
class DraftList extends React.Component<Props, State> {
|
|
|
|
state: State = {
|
|
|
|
deletingId: null,
|
|
|
|
};
|
|
|
|
|
2019-11-24 07:07:03 -08:00
|
|
|
private reloadTimeout: number | null = null;
|
|
|
|
|
2019-02-01 11:13:30 -08:00
|
|
|
componentDidMount() {
|
2019-11-24 07:07:03 -08:00
|
|
|
const { createIfNone, createWithRfpId, isVerified } = this.props;
|
2019-02-01 11:13:30 -08:00
|
|
|
if (createIfNone || createWithRfpId) {
|
|
|
|
this.props.fetchAndCreateDrafts({
|
|
|
|
rfpId: createWithRfpId,
|
|
|
|
redirect: true,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.props.fetchDrafts();
|
|
|
|
}
|
2019-11-24 07:07:03 -08:00
|
|
|
|
|
|
|
if (!isVerified) {
|
|
|
|
this.reloadTimeout = window.setTimeout(() => {
|
|
|
|
window.location.reload();
|
|
|
|
}, EMAIL_VERIFIED_RELOAD_TIMEOUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
if (this.reloadTimeout !== null) {
|
|
|
|
window.clearTimeout(this.reloadTimeout);
|
|
|
|
}
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: Props) {
|
2019-02-01 11:13:30 -08:00
|
|
|
const { isDeletingDraft, deleteDraftError, createDraftError } = this.props;
|
2018-11-15 08:02:16 -08:00
|
|
|
if (prevProps.isDeletingDraft && !isDeletingDraft) {
|
|
|
|
this.setState({ deletingId: null });
|
|
|
|
}
|
|
|
|
if (deleteDraftError && prevProps.deleteDraftError !== deleteDraftError) {
|
2019-03-12 20:35:38 -07:00
|
|
|
message.error(deleteDraftError, 3);
|
2018-11-15 08:02:16 -08:00
|
|
|
}
|
|
|
|
if (createDraftError && prevProps.createDraftError !== createDraftError) {
|
2019-03-12 20:35:38 -07:00
|
|
|
message.error(createDraftError, 3);
|
2018-11-15 08:02:16 -08:00
|
|
|
}
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2019-02-01 11:13:30 -08:00
|
|
|
const { drafts, isCreatingDraft, isFetchingDrafts, isVerified } = this.props;
|
2018-11-15 08:02:16 -08:00
|
|
|
const { deletingId } = this.state;
|
2018-11-13 14:51:02 -08:00
|
|
|
|
2019-01-28 16:51:10 -08:00
|
|
|
if (!isVerified) {
|
|
|
|
return (
|
|
|
|
<div className="DraftList">
|
|
|
|
<Placeholder
|
|
|
|
title="Your email is not verified"
|
|
|
|
subtitle="Please confirm your email before making a proposal."
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-15 08:02:16 -08:00
|
|
|
if (!drafts || isCreatingDraft) {
|
2019-01-23 08:35:03 -08:00
|
|
|
return <Loader size="large" />;
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
2018-11-15 08:02:16 -08:00
|
|
|
let draftsEl;
|
|
|
|
if (drafts.length) {
|
|
|
|
draftsEl = (
|
2018-11-13 14:51:02 -08:00
|
|
|
<List
|
|
|
|
itemLayout="horizontal"
|
|
|
|
dataSource={drafts}
|
2019-02-01 11:13:30 -08:00
|
|
|
loading={isFetchingDrafts}
|
2018-11-13 14:51:02 -08:00
|
|
|
renderItem={(d: ProposalDraft) => {
|
|
|
|
const actions = [
|
|
|
|
<Link key="edit" to={`/proposals/${d.proposalId}/edit`}>
|
|
|
|
Edit
|
|
|
|
</Link>,
|
2018-11-15 08:02:16 -08:00
|
|
|
<Popconfirm
|
|
|
|
key="delete"
|
|
|
|
title="Are you sure?"
|
|
|
|
onConfirm={() => this.deleteDraft(d.proposalId)}
|
|
|
|
>
|
|
|
|
<a>Delete</a>
|
|
|
|
</Popconfirm>,
|
2018-11-13 14:51:02 -08:00
|
|
|
];
|
|
|
|
return (
|
2019-01-23 08:35:03 -08:00
|
|
|
<Spin tip="deleting..." spinning={deletingId === d.proposalId}>
|
|
|
|
<List.Item actions={actions}>
|
|
|
|
<List.Item.Meta
|
|
|
|
title={
|
|
|
|
<>
|
|
|
|
{d.title || <em>Untitled proposal</em>}
|
2019-11-20 13:37:58 -08:00
|
|
|
{d.status === STATUS.REJECTED && <em> (changes requested)</em>}
|
2019-01-23 08:35:03 -08:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
description={d.brief || <em>No description</em>}
|
|
|
|
/>
|
|
|
|
</List.Item>
|
|
|
|
</Spin>
|
2018-11-13 14:51:02 -08:00
|
|
|
);
|
|
|
|
}}
|
|
|
|
/>
|
2018-11-15 08:02:16 -08:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
draftsEl = (
|
|
|
|
<Placeholder
|
|
|
|
title="You have no drafts"
|
|
|
|
subtitle="Why not make one now? Click below to start."
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="DraftList">
|
|
|
|
<h2 className="DraftList-title">Your drafts</h2>
|
|
|
|
{draftsEl}
|
2018-11-13 14:51:02 -08:00
|
|
|
<Divider>or</Divider>
|
|
|
|
<Button
|
|
|
|
className="DraftList-create"
|
|
|
|
type="primary"
|
|
|
|
size="large"
|
|
|
|
block
|
2019-02-01 11:13:30 -08:00
|
|
|
onClick={() => this.createDraft()}
|
2018-11-13 14:51:02 -08:00
|
|
|
loading={isCreatingDraft}
|
|
|
|
>
|
|
|
|
Create a new Proposal
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-01 11:13:30 -08:00
|
|
|
private createDraft = (rfpId?: number) => {
|
|
|
|
this.props.createDraft({ rfpId, redirect: true });
|
2018-11-13 14:51:02 -08:00
|
|
|
};
|
2018-11-15 08:02:16 -08:00
|
|
|
|
|
|
|
private deleteDraft = (proposalId: number) => {
|
|
|
|
this.props.deleteDraft(proposalId);
|
|
|
|
this.setState({ deletingId: proposalId });
|
|
|
|
};
|
2018-11-13 14:51:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
|
|
|
|
state => ({
|
|
|
|
drafts: state.create.drafts,
|
|
|
|
isFetchingDrafts: state.create.isFetchingDrafts,
|
|
|
|
fetchDraftsError: state.create.fetchDraftsError,
|
|
|
|
isCreatingDraft: state.create.isCreatingDraft,
|
|
|
|
createDraftError: state.create.createDraftError,
|
2018-11-15 08:02:16 -08:00
|
|
|
isDeletingDraft: state.create.isDeletingDraft,
|
|
|
|
deleteDraftError: state.create.deleteDraftError,
|
2019-01-28 16:51:10 -08:00
|
|
|
isVerified: getIsVerified(state),
|
2018-11-13 14:51:02 -08:00
|
|
|
}),
|
|
|
|
{
|
|
|
|
fetchDrafts,
|
|
|
|
createDraft,
|
2018-11-15 08:02:16 -08:00
|
|
|
deleteDraft,
|
2019-02-01 11:13:30 -08:00
|
|
|
fetchAndCreateDrafts,
|
2018-11-13 14:51:02 -08:00
|
|
|
},
|
|
|
|
)(DraftList);
|