Merge pull request #158 from grant-project/proposal-date-fixes
Various proposal & milestone time fixes
This commit is contained in:
commit
6d2fb135fd
|
@ -2,6 +2,7 @@ import datetime
|
||||||
|
|
||||||
from grant.extensions import ma, db
|
from grant.extensions import ma, db
|
||||||
from grant.utils.exceptions import ValidationException
|
from grant.utils.exceptions import ValidationException
|
||||||
|
from grant.utils.misc import dt_to_unix
|
||||||
|
|
||||||
NOT_REQUESTED = 'NOT_REQUESTED'
|
NOT_REQUESTED = 'NOT_REQUESTED'
|
||||||
ONGOING_VOTE = 'ONGOING_VOTE'
|
ONGOING_VOTE = 'ONGOING_VOTE'
|
||||||
|
@ -64,6 +65,15 @@ class MilestoneSchema(ma.Schema):
|
||||||
"date_created",
|
"date_created",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
date_created = ma.Method("get_date_created")
|
||||||
|
date_estimated = ma.Method("get_date_estimated")
|
||||||
|
|
||||||
|
def get_date_created(self, obj):
|
||||||
|
return dt_to_unix(obj.date_created)
|
||||||
|
|
||||||
|
def get_date_estimated(self, obj):
|
||||||
|
return dt_to_unix(obj.date_estimated) if obj.date_estimated else None
|
||||||
|
|
||||||
|
|
||||||
milestone_schema = MilestoneSchema()
|
milestone_schema = MilestoneSchema()
|
||||||
milestones_schema = MilestoneSchema(many=True)
|
milestones_schema = MilestoneSchema(many=True)
|
||||||
|
|
|
@ -18,7 +18,7 @@ const DEFAULT_STATE: State = {
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
dateEstimated: '',
|
dateEstimated: moment().unix(),
|
||||||
payoutPercent: '100',
|
payoutPercent: '100',
|
||||||
immediatePayout: false,
|
immediatePayout: false,
|
||||||
},
|
},
|
||||||
|
@ -159,10 +159,21 @@ const MilestoneFields = ({
|
||||||
<DatePicker.MonthPicker
|
<DatePicker.MonthPicker
|
||||||
style={{ flex: 1, marginRight: '0.5rem' }}
|
style={{ flex: 1, marginRight: '0.5rem' }}
|
||||||
placeholder="Expected completion date"
|
placeholder="Expected completion date"
|
||||||
value={milestone.dateEstimated ? moment(milestone.dateEstimated) : undefined}
|
value={
|
||||||
|
milestone.dateEstimated ? moment(milestone.dateEstimated * 1000) : undefined
|
||||||
|
}
|
||||||
format="MMMM YYYY"
|
format="MMMM YYYY"
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
onChange={(_, dateEstimated) => onChange(index, { ...milestone, dateEstimated })}
|
onChange={time => onChange(index, { ...milestone, dateEstimated: time.unix() })}
|
||||||
|
disabled={milestone.immediatePayout}
|
||||||
|
disabledDate={current =>
|
||||||
|
current
|
||||||
|
? current <
|
||||||
|
moment()
|
||||||
|
.subtract(1, 'month')
|
||||||
|
.endOf('month')
|
||||||
|
: false
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
min={1}
|
min={1}
|
||||||
|
@ -186,6 +197,9 @@ const MilestoneFields = ({
|
||||||
onChange(index, {
|
onChange(index, {
|
||||||
...milestone,
|
...milestone,
|
||||||
immediatePayout: ev.target.checked,
|
immediatePayout: ev.target.checked,
|
||||||
|
dateEstimated: ev.target.checked
|
||||||
|
? moment().unix()
|
||||||
|
: milestone.dateEstimated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -182,7 +182,7 @@ const ReviewMilestones = ({
|
||||||
<div className="ReviewMilestone">
|
<div className="ReviewMilestone">
|
||||||
<div className="ReviewMilestone-title">{m.title}</div>
|
<div className="ReviewMilestone-title">{m.title}</div>
|
||||||
<div className="ReviewMilestone-info">
|
<div className="ReviewMilestone-info">
|
||||||
{moment(m.dateEstimated, 'MMMM YYYY').format('MMMM YYYY')}
|
{moment(m.dateEstimated * 1000).format('MMMM YYYY')}
|
||||||
{' – '}
|
{' – '}
|
||||||
{m.payoutPercent}% of funds
|
{m.payoutPercent}% of funds
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import moment from 'moment';
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import { ProposalDraft } from 'types';
|
import { ProposalDraft } from 'types';
|
||||||
|
|
||||||
|
@ -19,7 +20,9 @@ const createExampleProposal = (): Partial<ProposalDraft> => {
|
||||||
title: 'Initial Funding',
|
title: 'Initial Funding',
|
||||||
content:
|
content:
|
||||||
'This will be used to pay for a professional designer to hand-craft each letter on the shirt.',
|
'This will be used to pay for a professional designer to hand-craft each letter on the shirt.',
|
||||||
dateEstimated: 'October 2018',
|
dateEstimated: moment()
|
||||||
|
.add(1, 'month')
|
||||||
|
.unix(),
|
||||||
payoutPercent: '30',
|
payoutPercent: '30',
|
||||||
immediatePayout: true,
|
immediatePayout: true,
|
||||||
},
|
},
|
||||||
|
@ -27,7 +30,9 @@ const createExampleProposal = (): Partial<ProposalDraft> => {
|
||||||
title: 'Test Prints',
|
title: 'Test Prints',
|
||||||
content:
|
content:
|
||||||
"We'll get test prints from 5 different factories and choose the highest quality shirts. Once we've decided, we'll order a full batch of prints.",
|
"We'll get test prints from 5 different factories and choose the highest quality shirts. Once we've decided, we'll order a full batch of prints.",
|
||||||
dateEstimated: 'November 2018',
|
dateEstimated: moment()
|
||||||
|
.add(2, 'month')
|
||||||
|
.unix(),
|
||||||
payoutPercent: '20',
|
payoutPercent: '20',
|
||||||
immediatePayout: false,
|
immediatePayout: false,
|
||||||
},
|
},
|
||||||
|
@ -35,7 +40,9 @@ const createExampleProposal = (): Partial<ProposalDraft> => {
|
||||||
title: 'All Shirts Printed',
|
title: 'All Shirts Printed',
|
||||||
content:
|
content:
|
||||||
"All of the shirts have been printed, hooray! They'll be given out at conferences and meetups.",
|
"All of the shirts have been printed, hooray! They'll be given out at conferences and meetups.",
|
||||||
dateEstimated: 'December 2018',
|
dateEstimated: moment()
|
||||||
|
.add(3, 'month')
|
||||||
|
.unix(),
|
||||||
payoutPercent: '50',
|
payoutPercent: '50',
|
||||||
immediatePayout: false,
|
immediatePayout: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -49,8 +49,9 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
let content;
|
let content;
|
||||||
if (proposal) {
|
if (proposal) {
|
||||||
const { target, funded, percentFunded } = proposal;
|
const { target, funded, percentFunded } = proposal;
|
||||||
|
const datePublished = proposal.datePublished || Date.now() / 1000;
|
||||||
const isRaiseGoalReached = funded.gte(target);
|
const isRaiseGoalReached = funded.gte(target);
|
||||||
const deadline = (proposal.dateCreated + proposal.deadlineDuration) * 1000;
|
const deadline = (datePublished + proposal.deadlineDuration) * 1000;
|
||||||
// TODO: Get values from proposal
|
// TODO: Get values from proposal
|
||||||
console.warn('TODO: Get isFrozen from proposal data');
|
console.warn('TODO: Get isFrozen from proposal data');
|
||||||
const isFrozen = false;
|
const isFrozen = false;
|
||||||
|
@ -66,7 +67,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
<div className="ProposalCampaignBlock-info">
|
<div className="ProposalCampaignBlock-info">
|
||||||
<div className="ProposalCampaignBlock-info-label">Started</div>
|
<div className="ProposalCampaignBlock-info-label">Started</div>
|
||||||
<div className="ProposalCampaignBlock-info-value">
|
<div className="ProposalCampaignBlock-info-value">
|
||||||
{moment(proposal.datePublished * 1000).fromNow()}
|
{moment(datePublished * 1000).fromNow()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ProposalMilestones extends React.Component<Props, State> {
|
||||||
: milestoneStateToStepState[milestone.state];
|
: milestoneStateToStepState[milestone.state];
|
||||||
|
|
||||||
const className = this.state.step === i ? 'is-active' : 'is-inactive';
|
const className = this.state.step === i ? 'is-active' : 'is-inactive';
|
||||||
const estimatedDate = moment(milestone.dateEstimated).format('MMMM YYYY');
|
const estimatedDate = moment(milestone.dateEstimated * 1000).format('MMMM YYYY');
|
||||||
const reward = (
|
const reward = (
|
||||||
<UnitDisplay value={milestone.amount} symbol="ZEC" displayShortBalance={4} />
|
<UnitDisplay value={milestone.amount} symbol="ZEC" displayShortBalance={4} />
|
||||||
);
|
);
|
||||||
|
@ -121,7 +121,7 @@ class ProposalMilestones extends React.Component<Props, State> {
|
||||||
message={
|
message={
|
||||||
<span>
|
<span>
|
||||||
The team was awarded <strong>{reward}</strong>{' '}
|
The team was awarded <strong>{reward}</strong>{' '}
|
||||||
{milestone.isImmediatePayout
|
{milestone.immediatePayout
|
||||||
? 'as an initial payout'
|
? 'as an initial payout'
|
||||||
: // TODO: Add property for payout date on milestones
|
: // TODO: Add property for payout date on milestones
|
||||||
`on ${moment().format('MMM Do, YYYY')}`}
|
`on ${moment().format('MMM Do, YYYY')}`}
|
||||||
|
@ -164,7 +164,7 @@ class ProposalMilestones extends React.Component<Props, State> {
|
||||||
|
|
||||||
const statuses = (
|
const statuses = (
|
||||||
<div className="ProposalMilestones-milestone-status">
|
<div className="ProposalMilestones-milestone-status">
|
||||||
{!milestone.isImmediatePayout && (
|
{!milestone.immediatePayout && (
|
||||||
<div>
|
<div>
|
||||||
Estimate: <strong>{estimatedDate}</strong>
|
Estimate: <strong>{estimatedDate}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
proposalAddress,
|
proposalAddress,
|
||||||
proposalUrlId,
|
proposalUrlId,
|
||||||
category,
|
category,
|
||||||
|
datePublished,
|
||||||
dateCreated,
|
dateCreated,
|
||||||
team,
|
team,
|
||||||
target,
|
target,
|
||||||
|
@ -28,11 +29,7 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className="ProposalCard" to={`/proposals/${proposalUrlId}`} title={title}>
|
||||||
className="ProposalCard"
|
|
||||||
to={`/proposals/${proposalUrlId}`}
|
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
{contributionMatching > 0 && (
|
{contributionMatching > 0 && (
|
||||||
<div className="ProposalCard-ribbon">
|
<div className="ProposalCard-ribbon">
|
||||||
<span>
|
<span>
|
||||||
|
@ -77,7 +74,7 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ProposalCard-address">{proposalAddress}</div>
|
<div className="ProposalCard-address">{proposalAddress}</div>
|
||||||
<Card.Info category={category} time={dateCreated * 1000} />
|
<Card.Info category={category} time={(datePublished || dateCreated) * 1000} />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,7 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): Proposal {
|
||||||
payoutAddress: '0x0',
|
payoutAddress: '0x0',
|
||||||
dateCreated: Date.now() / 1000,
|
dateCreated: Date.now() / 1000,
|
||||||
datePublished: Date.now() / 1000,
|
datePublished: Date.now() / 1000,
|
||||||
|
dateApproved: Date.now() / 1000,
|
||||||
deadlineDuration: 86400 * 60,
|
deadlineDuration: 86400 * 60,
|
||||||
target: toZat(draft.target),
|
target: toZat(draft.target),
|
||||||
funded: Zat('0'),
|
funded: Zat('0'),
|
||||||
|
@ -198,7 +199,6 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): Proposal {
|
||||||
amount: toZat(target * (parseInt(m.payoutPercent, 10) / 100)),
|
amount: toZat(target * (parseInt(m.payoutPercent, 10) / 100)),
|
||||||
dateEstimated: m.dateEstimated,
|
dateEstimated: m.dateEstimated,
|
||||||
immediatePayout: m.immediatePayout,
|
immediatePayout: m.immediatePayout,
|
||||||
isImmediatePayout: m.immediatePayout,
|
|
||||||
isPaid: false,
|
isPaid: false,
|
||||||
payoutPercent: m.payoutPercent.toString(),
|
payoutPercent: m.payoutPercent.toString(),
|
||||||
state: MILESTONE_STATE.WAITING,
|
state: MILESTONE_STATE.WAITING,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from 'types';
|
} from 'types';
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
const oneZec = new BN('100000000');
|
const oneZec = new BN('100000000');
|
||||||
|
|
||||||
|
@ -101,23 +102,22 @@ export function generateProposal({
|
||||||
const genMilestone = (
|
const genMilestone = (
|
||||||
overrides: Partial<ProposalMilestone> = {},
|
overrides: Partial<ProposalMilestone> = {},
|
||||||
): ProposalMilestone => {
|
): ProposalMilestone => {
|
||||||
const now = new Date();
|
|
||||||
if (overrides.index) {
|
if (overrides.index) {
|
||||||
const estimate = new Date(now.setMonth(now.getMonth() + overrides.index));
|
overrides.dateEstimated = moment()
|
||||||
overrides.dateEstimated = estimate.toISOString();
|
.add(overrides.index, 'month')
|
||||||
|
.unix();
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults: ProposalMilestone = {
|
const defaults: ProposalMilestone = {
|
||||||
title: 'Milestone A',
|
title: 'Milestone A',
|
||||||
content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||||
tempor incididunt ut labore et dolore magna aliqua.`,
|
tempor incididunt ut labore et dolore magna aliqua.`,
|
||||||
dateEstimated: '2018-10-01T00:00:00+00:00',
|
dateEstimated: moment().unix(),
|
||||||
immediatePayout: true,
|
immediatePayout: true,
|
||||||
index: 0,
|
index: 0,
|
||||||
state: MILESTONE_STATE.WAITING,
|
state: MILESTONE_STATE.WAITING,
|
||||||
amount: amountBn,
|
amount: amountBn,
|
||||||
isPaid: false,
|
isPaid: false,
|
||||||
isImmediatePayout: true,
|
|
||||||
payoutPercent: '33',
|
payoutPercent: '33',
|
||||||
};
|
};
|
||||||
return { ...defaults, ...overrides };
|
return { ...defaults, ...overrides };
|
||||||
|
@ -128,7 +128,6 @@ export function generateProposal({
|
||||||
index: i,
|
index: i,
|
||||||
title: genMilestoneTitle(),
|
title: genMilestoneTitle(),
|
||||||
immediatePayout: i === 0,
|
immediatePayout: i === 0,
|
||||||
isImmediatePayout: i === 0,
|
|
||||||
payoutRequestVoteDeadline: i !== 0 ? Date.now() + 3600000 : 0,
|
payoutRequestVoteDeadline: i !== 0 ? Date.now() + 3600000 : 0,
|
||||||
payoutPercent: '' + (1 / milestoneCount) * 100,
|
payoutPercent: '' + (1 / milestoneCount) * 100,
|
||||||
};
|
};
|
||||||
|
@ -149,6 +148,7 @@ export function generateProposal({
|
||||||
payoutAddress: 'z123',
|
payoutAddress: 'z123',
|
||||||
dateCreated: created / 1000,
|
dateCreated: created / 1000,
|
||||||
datePublished: created / 1000,
|
datePublished: created / 1000,
|
||||||
|
dateApproved: created / 1000,
|
||||||
deadlineDuration: 86400 * 60,
|
deadlineDuration: 86400 * 60,
|
||||||
target: amountBn,
|
target: amountBn,
|
||||||
funded: fundedBn,
|
funded: fundedBn,
|
||||||
|
|
|
@ -12,13 +12,12 @@ export interface Milestone {
|
||||||
state: MILESTONE_STATE;
|
state: MILESTONE_STATE;
|
||||||
amount: Zat;
|
amount: Zat;
|
||||||
isPaid: boolean;
|
isPaid: boolean;
|
||||||
isImmediatePayout: boolean;
|
immediatePayout: boolean;
|
||||||
|
dateEstimated: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProposalMilestone extends Milestone {
|
export interface ProposalMilestone extends Milestone {
|
||||||
content: string;
|
content: string;
|
||||||
immediatePayout: boolean;
|
|
||||||
dateEstimated: string;
|
|
||||||
payoutPercent: string;
|
payoutPercent: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +25,7 @@ export interface ProposalMilestone extends Milestone {
|
||||||
export interface CreateMilestone {
|
export interface CreateMilestone {
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
dateEstimated: string;
|
dateEstimated: number;
|
||||||
payoutPercent: string;
|
payoutPercent: string;
|
||||||
immediatePayout: boolean;
|
immediatePayout: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
|
||||||
percentFunded: number;
|
percentFunded: number;
|
||||||
contributionMatching: number;
|
contributionMatching: number;
|
||||||
milestones: ProposalMilestone[];
|
milestones: ProposalMilestone[];
|
||||||
datePublished: number;
|
datePublished: number | null;
|
||||||
|
dateApproved: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TeamInviteWithProposal extends TeamInvite {
|
export interface TeamInviteWithProposal extends TeamInvite {
|
||||||
|
|
Loading…
Reference in New Issue