Merge pull request #237 from grant-project/milestone-validation

Better Milestone Validation
This commit is contained in:
Daniel Ternyak 2019-02-20 15:49:36 -06:00 committed by GitHub
commit 825e7a84bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 37 deletions

View File

@ -19,7 +19,7 @@ const DEFAULT_STATE: State = {
title: '', title: '',
content: '', content: '',
dateEstimated: moment().unix(), dateEstimated: moment().unix(),
payoutPercent: '100', payoutPercent: '',
immediatePayout: false, immediatePayout: false,
}, },
], ],
@ -52,19 +52,7 @@ export default class CreateFlowMilestones extends React.Component<Props, State>
addMilestone = () => { addMilestone = () => {
const { milestones: oldMilestones } = this.state; const { milestones: oldMilestones } = this.state;
const lastMilestone = oldMilestones[oldMilestones.length - 1]; const milestones = [...oldMilestones, { ...DEFAULT_STATE.milestones[0] }];
const halfPayout = parseInt(lastMilestone.payoutPercent, 10) / 2;
const milestones = [
...oldMilestones,
{
...DEFAULT_STATE.milestones[0],
payoutPercent: halfPayout.toString(),
},
];
milestones[milestones.length - 2] = {
...lastMilestone,
payoutPercent: halfPayout.toString(),
};
this.setState({ milestones }); this.setState({ milestones });
}; };
@ -124,7 +112,7 @@ const MilestoneFields = ({
<div style={{ display: 'flex', marginBottom: '0.5rem', alignItems: 'center' }}> <div style={{ display: 'flex', marginBottom: '0.5rem', alignItems: 'center' }}>
<Input <Input
size="large" size="large"
placeholder="Title" placeholder="Milestone title"
type="text" type="text"
name="title" name="title"
value={milestone.title} value={milestone.title}
@ -147,7 +135,7 @@ const MilestoneFields = ({
<Input.TextArea <Input.TextArea
rows={3} rows={3}
name="content" name="content"
placeholder="Description of the deliverable" placeholder="Description of what will be delivered"
value={milestone.content} value={milestone.content}
onChange={ev => onChange={ev =>
onChange(index, { ...milestone, content: ev.currentTarget.value }) onChange(index, { ...milestone, content: ev.currentTarget.value })
@ -176,14 +164,12 @@ const MilestoneFields = ({
} }
/> />
<Input <Input
min={1}
max={100}
type="number"
value={milestone.payoutPercent} value={milestone.payoutPercent}
placeholder="Payout"
onChange={ev => onChange={ev =>
onChange(index, { onChange(index, {
...milestone, ...milestone,
payoutPercent: ev.currentTarget.value || '0', payoutPercent: ev.currentTarget.value,
}) })
} }
addonAfter="%" addonAfter="%"

View File

@ -71,6 +71,10 @@
&-title { &-title {
font-size: 1.4rem; font-size: 1.4rem;
margin: 0 0 0.3rem; margin: 0 0 0.3rem;
em {
opacity: 0.7;
}
} }
&-info { &-info {
@ -82,6 +86,10 @@
&-description { &-description {
font-size: 1rem; font-size: 1rem;
em {
opacity: 0.7;
}
} }
} }

View File

@ -180,13 +180,15 @@ const ReviewMilestones = ({
{milestones.map(m => ( {milestones.map(m => (
<Timeline.Item key={m.title}> <Timeline.Item key={m.title}>
<div className="ReviewMilestone"> <div className="ReviewMilestone">
<div className="ReviewMilestone-title">{m.title}</div> <div className="ReviewMilestone-title">{m.title || <em>No title</em>}</div>
<div className="ReviewMilestone-info"> <div className="ReviewMilestone-info">
{moment(m.dateEstimated * 1000).format('MMMM YYYY')} {moment(m.dateEstimated * 1000).format('MMMM YYYY')}
{' '} {' '}
{m.payoutPercent}% of funds {m.payoutPercent}% of funds
</div> </div>
<div className="ReviewMilestone-description">{m.content}</div> <div className="ReviewMilestone-description">
{m.content || <em>No description</em>}
</div>
</div> </div>
</Timeline.Item> </Timeline.Item>
))} ))}

View File

@ -79,7 +79,7 @@ const STEP_INFO: { [key in CREATE_STEP]: StepInfo } = {
title: 'Set up milestones for deliverables', title: 'Set up milestones for deliverables',
subtitle: 'Make a timeline of when youll complete tasks, and receive funds', subtitle: 'Make a timeline of when youll complete tasks, and receive funds',
help: help:
'Contributors are more willing to fund proposals with funding spread across multiple deadlines', 'Contributors are more willing to fund proposals with funding spread across multiple milestones',
component: Milestones, component: Milestones,
}, },
[CREATE_STEP.PAYMENT]: { [CREATE_STEP.PAYMENT]: {

View File

@ -96,31 +96,42 @@ export function getCreateErrors(
// Milestones // Milestones
if (milestones) { if (milestones) {
let didMilestoneError = false;
let cumulativeMilestonePct = 0; let cumulativeMilestonePct = 0;
const milestoneErrors = milestones.map((ms, idx) => { const milestoneErrors = milestones.map((ms, idx) => {
if (!ms.title || !ms.content || !ms.dateEstimated || !ms.payoutPercent) { if (!ms.title) {
didMilestoneError = true; return 'Title is required';
return ''; } else if (ms.title.length > 40) {
return 'Title length can only be 40 characters maximum';
} }
let err = ''; if (!ms.content) {
if (ms.title.length > 40) { return 'Description is required';
err = 'Title length can only be 40 characters maximum';
} else if (ms.content.length > 200) { } else if (ms.content.length > 200) {
err = 'Description can only be 200 characters maximum'; return 'Description can only be 200 characters maximum';
}
if (!ms.dateEstimated) {
return 'Estimate date is required';
}
if (!ms.payoutPercent) {
return 'Payout percent is required';
} else if (Number.isNaN(parseInt(ms.payoutPercent, 10))) {
return 'Payout percent must be a valid number';
} }
// Last one shows percentage errors // Last one shows percentage errors
cumulativeMilestonePct += parseInt(ms.payoutPercent, 10); cumulativeMilestonePct += parseInt(ms.payoutPercent, 10);
if (idx === milestones.length - 1 && cumulativeMilestonePct !== 100) { if (
err = `Payout percentages doesnt add up to 100% (currently ${cumulativeMilestonePct}%)`; idx === milestones.length - 1 &&
cumulativeMilestonePct !== 100 &&
!Number.isNaN(cumulativeMilestonePct)
) {
return `Payout percentages dont add up to 100% (currently ${cumulativeMilestonePct}%)`;
} }
return '';
didMilestoneError = didMilestoneError || !!err;
return err;
}); });
if (didMilestoneError) { if (milestoneErrors.find(err => !!err)) {
errors.milestones = milestoneErrors; errors.milestones = milestoneErrors;
} }
} }