zcash-grant-system/frontend/client/components/CreateFlow/Basics.tsx

216 lines
6.6 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { connect } from 'react-redux';
import { Input, Form, Alert, Popconfirm, message, Radio } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio';
import { ProposalDraft, RFP } from 'types';
import { getCreateErrors } from 'modules/create/utils';
import { Link } from 'react-router-dom';
import { unlinkProposalRFP } from 'modules/create/actions';
import { AppState } from 'store/reducers';
interface OwnProps {
proposalId: number;
initialState?: Partial<State>;
updateForm(form: Partial<ProposalDraft>): void;
}
interface StateProps {
isUnlinkingProposalRFP: AppState['create']['isUnlinkingProposalRFP'];
unlinkProposalRFPError: AppState['create']['unlinkProposalRFPError'];
}
interface DispatchProps {
unlinkProposalRFP: typeof unlinkProposalRFP;
}
type Props = OwnProps & StateProps & DispatchProps;
interface State extends Partial<ProposalDraft> {
title: string;
brief: string;
target: string;
rfp?: RFP;
}
class CreateFlowBasics extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
title: '',
brief: '',
target: '',
...(props.initialState || {}),
};
}
componentDidUpdate(prevProps: Props) {
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
if (
unlinkProposalRFPError &&
unlinkProposalRFPError !== prevProps.unlinkProposalRFPError
) {
console.error('Failed to unlink request:', unlinkProposalRFPError);
message.error('Failed to unlink request');
} else if (!isUnlinkingProposalRFP && prevProps.isUnlinkingProposalRFP) {
this.setState({ rfp: undefined });
message.success('Unlinked proposal from request');
}
}
render() {
const { isUnlinkingProposalRFP } = this.props;
const { title, brief, target, rfp, rfpOptIn } = this.state;
const errors = getCreateErrors(this.state, true);
// Don't show target error at zero since it defaults to that
// Error just shows up at the end to prevent submission
if (target === '0') {
errors.target = undefined;
}
const rfpOptInRequired =
rfp && (rfp.matching || (rfp.bounty && parseFloat(rfp.bounty.toString()) > 0));
return (
<Form layout="vertical" style={{ maxWidth: 600, margin: '0 auto' }}>
{rfp && (
<Alert
className="CreateFlow-rfpAlert"
type="info"
message="This proposal is linked to a request"
description={
<>
This proposal is for the open request{' '}
<Link to={`/requests/${rfp.id}`} target="_blank">
{rfp.title}
</Link>
. If you didnt mean to do this, or want to unlink it,{' '}
<Popconfirm
title="Are you sure? This cannot be undone."
onConfirm={this.unlinkRfp}
okButtonProps={{ loading: isUnlinkingProposalRFP }}
>
<a>click here</a>
</Popconfirm>{' '}
to do so.
</>
}
showIcon
/>
)}
{rfpOptInRequired && (
<Alert
className="CreateFlow-rfpAlert"
type="warning"
message="KYC (know your customer)"
description={
<>
<div>
This RFP offers either a bounty or matching. This will require ZFGrants
to fulfill{' '}
<a
target="_blank"
href="https://en.wikipedia.org/wiki/Know_your_customer"
>
KYC
</a>{' '}
due dilligence. In the event your proposal is successful, you will need
to provide identifying information to ZFGrants.
<Radio.Group onChange={this.handleRfpOptIn}>
<Radio value={true} checked={rfpOptIn && rfpOptIn === true}>
<b>Yes</b>, I am willing to provide KYC information
</Radio>
<Radio
value={false}
checked={rfpOptIn !== null && rfpOptIn === false}
>
<b>No</b>, I do not wish to provide KYC information and understand I
will not receive any matching or bounty funds from ZFGrants
</Radio>
</Radio.Group>
</div>
</>
}
/>
)}
<Form.Item
label="Title"
validateStatus={errors.title ? 'error' : undefined}
help={errors.title}
>
<Input
size="large"
name="title"
placeholder="Short and sweet"
type="text"
value={title}
onChange={this.handleInputChange}
maxLength={200}
/>
</Form.Item>
<Form.Item
label="Brief"
validateStatus={errors.brief ? 'error' : undefined}
help={errors.brief}
>
<Input.TextArea
name="brief"
placeholder="An elevator-pitch version of your proposal, max 140 chars"
value={brief}
onChange={this.handleInputChange}
rows={3}
maxLength={200}
/>
</Form.Item>
<Form.Item
label="Target amount"
validateStatus={errors.target ? 'error' : undefined}
help={errors.target || 'This cannot be changed once your proposal starts'}
>
<Input
size="large"
name="target"
placeholder="1.5"
type="number"
value={target}
onChange={this.handleInputChange}
2018-12-27 09:41:26 -08:00
addonAfter="ZEC"
2019-03-18 11:47:46 -07:00
maxLength={16}
/>
</Form.Item>
</Form>
);
}
private handleInputChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { value, name } = event.currentTarget;
this.setState({ [name]: value } as any, () => {
this.props.updateForm(this.state);
});
};
private handleRfpOptIn = (e: RadioChangeEvent) => {
this.setState({ rfpOptIn: e.target.value }, () => {
this.props.updateForm(this.state);
});
};
private unlinkRfp = () => {
this.props.unlinkProposalRFP(this.props.proposalId);
};
}
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
state => ({
isUnlinkingProposalRFP: state.create.isUnlinkingProposalRFP,
unlinkProposalRFPError: state.create.unlinkProposalRFPError,
}),
{ unlinkProposalRFP },
)(CreateFlowBasics);