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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ const STEP_INFO: { [key in CREATE_STEP]: StepInfo } = {
title: 'Set up milestones for deliverables',
subtitle: 'Make a timeline of when youll complete tasks, and receive funds',
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,
},
[CREATE_STEP.PAYMENT]: {

View File

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