Merge pull request #158 from grant-project/proposal-date-fixes

Various proposal & milestone time fixes
This commit is contained in:
Daniel Ternyak 2019-02-06 17:58:34 -06:00 committed by GitHub
commit 6d2fb135fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 59 additions and 30 deletions

View File

@ -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)

View File

@ -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,
}) })
} }
> >

View File

@ -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>

View File

@ -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,
}, },

View File

@ -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>
)} )}

View File

@ -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>

View File

@ -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>
); );
} }

View File

@ -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,

View File

@ -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,

View File

@ -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;
} }

View File

@ -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 {