diff --git a/backend/grant/milestone/models.py b/backend/grant/milestone/models.py index fc3a2e3a..e1f62d12 100644 --- a/backend/grant/milestone/models.py +++ b/backend/grant/milestone/models.py @@ -2,6 +2,7 @@ import datetime from grant.extensions import ma, db from grant.utils.exceptions import ValidationException +from grant.utils.misc import dt_to_unix NOT_REQUESTED = 'NOT_REQUESTED' ONGOING_VOTE = 'ONGOING_VOTE' @@ -64,6 +65,15 @@ class MilestoneSchema(ma.Schema): "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() milestones_schema = MilestoneSchema(many=True) diff --git a/frontend/client/components/CreateFlow/Milestones.tsx b/frontend/client/components/CreateFlow/Milestones.tsx index 9e642f98..aca5d948 100644 --- a/frontend/client/components/CreateFlow/Milestones.tsx +++ b/frontend/client/components/CreateFlow/Milestones.tsx @@ -18,7 +18,7 @@ const DEFAULT_STATE: State = { { title: '', content: '', - dateEstimated: '', + dateEstimated: moment().unix(), payoutPercent: '100', immediatePayout: false, }, @@ -159,10 +159,21 @@ const MilestoneFields = ({ 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 + } /> diff --git a/frontend/client/components/CreateFlow/Review.tsx b/frontend/client/components/CreateFlow/Review.tsx index 92971393..6d7cc901 100644 --- a/frontend/client/components/CreateFlow/Review.tsx +++ b/frontend/client/components/CreateFlow/Review.tsx @@ -182,7 +182,7 @@ const ReviewMilestones = ({
{m.title}
- {moment(m.dateEstimated, 'MMMM YYYY').format('MMMM YYYY')} + {moment(m.dateEstimated * 1000).format('MMMM YYYY')} {' – '} {m.payoutPercent}% of funds
diff --git a/frontend/client/components/CreateFlow/example.ts b/frontend/client/components/CreateFlow/example.ts index 2876621a..31e4a6ae 100644 --- a/frontend/client/components/CreateFlow/example.ts +++ b/frontend/client/components/CreateFlow/example.ts @@ -1,3 +1,4 @@ +import moment from 'moment'; import { PROPOSAL_CATEGORY } from 'api/constants'; import { ProposalDraft } from 'types'; @@ -19,7 +20,9 @@ const createExampleProposal = (): Partial => { title: 'Initial Funding', content: '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', immediatePayout: true, }, @@ -27,7 +30,9 @@ const createExampleProposal = (): Partial => { title: 'Test Prints', 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.", - dateEstimated: 'November 2018', + dateEstimated: moment() + .add(2, 'month') + .unix(), payoutPercent: '20', immediatePayout: false, }, @@ -35,7 +40,9 @@ const createExampleProposal = (): Partial => { title: 'All Shirts Printed', content: "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', immediatePayout: false, }, diff --git a/frontend/client/components/Proposal/CampaignBlock/index.tsx b/frontend/client/components/Proposal/CampaignBlock/index.tsx index 775dffee..d935f62b 100644 --- a/frontend/client/components/Proposal/CampaignBlock/index.tsx +++ b/frontend/client/components/Proposal/CampaignBlock/index.tsx @@ -49,8 +49,9 @@ export class ProposalCampaignBlock extends React.Component { let content; if (proposal) { const { target, funded, percentFunded } = proposal; + const datePublished = proposal.datePublished || Date.now() / 1000; const isRaiseGoalReached = funded.gte(target); - const deadline = (proposal.dateCreated + proposal.deadlineDuration) * 1000; + const deadline = (datePublished + proposal.deadlineDuration) * 1000; // TODO: Get values from proposal console.warn('TODO: Get isFrozen from proposal data'); const isFrozen = false; @@ -66,7 +67,7 @@ export class ProposalCampaignBlock extends React.Component {
Started
- {moment(proposal.datePublished * 1000).fromNow()} + {moment(datePublished * 1000).fromNow()}
)} diff --git a/frontend/client/components/Proposal/Milestones/index.tsx b/frontend/client/components/Proposal/Milestones/index.tsx index 1dec2d86..9fbe18b4 100644 --- a/frontend/client/components/Proposal/Milestones/index.tsx +++ b/frontend/client/components/Proposal/Milestones/index.tsx @@ -98,7 +98,7 @@ class ProposalMilestones extends React.Component { : milestoneStateToStepState[milestone.state]; 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 = ( ); @@ -121,7 +121,7 @@ class ProposalMilestones extends React.Component { message={ The team was awarded {reward}{' '} - {milestone.isImmediatePayout + {milestone.immediatePayout ? 'as an initial payout' : // TODO: Add property for payout date on milestones `on ${moment().format('MMM Do, YYYY')}`} @@ -164,7 +164,7 @@ class ProposalMilestones extends React.Component { const statuses = (
- {!milestone.isImmediatePayout && ( + {!milestone.immediatePayout && (
Estimate: {estimatedDate}
diff --git a/frontend/client/components/Proposals/ProposalCard/index.tsx b/frontend/client/components/Proposals/ProposalCard/index.tsx index f2f99fc1..2df2aa7b 100644 --- a/frontend/client/components/Proposals/ProposalCard/index.tsx +++ b/frontend/client/components/Proposals/ProposalCard/index.tsx @@ -19,6 +19,7 @@ export class ProposalCard extends React.Component { proposalAddress, proposalUrlId, category, + datePublished, dateCreated, team, target, @@ -28,11 +29,7 @@ export class ProposalCard extends React.Component { } = this.props; return ( - + {contributionMatching > 0 && (
@@ -77,7 +74,7 @@ export class ProposalCard extends React.Component {
{proposalAddress}
- + ); } diff --git a/frontend/client/modules/create/utils.ts b/frontend/client/modules/create/utils.ts index 5aecb576..9ba03af1 100644 --- a/frontend/client/modules/create/utils.ts +++ b/frontend/client/modules/create/utils.ts @@ -183,6 +183,7 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): Proposal { payoutAddress: '0x0', dateCreated: Date.now() / 1000, datePublished: Date.now() / 1000, + dateApproved: Date.now() / 1000, deadlineDuration: 86400 * 60, target: toZat(draft.target), funded: Zat('0'), @@ -198,7 +199,6 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): Proposal { amount: toZat(target * (parseInt(m.payoutPercent, 10) / 100)), dateEstimated: m.dateEstimated, immediatePayout: m.immediatePayout, - isImmediatePayout: m.immediatePayout, isPaid: false, payoutPercent: m.payoutPercent.toString(), state: MILESTONE_STATE.WAITING, diff --git a/frontend/stories/props.tsx b/frontend/stories/props.tsx index cd5293c4..ac60ef25 100644 --- a/frontend/stories/props.tsx +++ b/frontend/stories/props.tsx @@ -8,6 +8,7 @@ import { } from 'types'; import { PROPOSAL_CATEGORY } from 'api/constants'; import BN from 'bn.js'; +import moment from 'moment'; const oneZec = new BN('100000000'); @@ -101,23 +102,22 @@ export function generateProposal({ const genMilestone = ( overrides: Partial = {}, ): ProposalMilestone => { - const now = new Date(); if (overrides.index) { - const estimate = new Date(now.setMonth(now.getMonth() + overrides.index)); - overrides.dateEstimated = estimate.toISOString(); + overrides.dateEstimated = moment() + .add(overrides.index, 'month') + .unix(); } const defaults: ProposalMilestone = { title: 'Milestone A', content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, - dateEstimated: '2018-10-01T00:00:00+00:00', + dateEstimated: moment().unix(), immediatePayout: true, index: 0, state: MILESTONE_STATE.WAITING, amount: amountBn, isPaid: false, - isImmediatePayout: true, payoutPercent: '33', }; return { ...defaults, ...overrides }; @@ -128,7 +128,6 @@ export function generateProposal({ index: i, title: genMilestoneTitle(), immediatePayout: i === 0, - isImmediatePayout: i === 0, payoutRequestVoteDeadline: i !== 0 ? Date.now() + 3600000 : 0, payoutPercent: '' + (1 / milestoneCount) * 100, }; @@ -149,6 +148,7 @@ export function generateProposal({ payoutAddress: 'z123', dateCreated: created / 1000, datePublished: created / 1000, + dateApproved: created / 1000, deadlineDuration: 86400 * 60, target: amountBn, funded: fundedBn, diff --git a/frontend/types/milestone.ts b/frontend/types/milestone.ts index 49cbad12..46661931 100644 --- a/frontend/types/milestone.ts +++ b/frontend/types/milestone.ts @@ -12,13 +12,12 @@ export interface Milestone { state: MILESTONE_STATE; amount: Zat; isPaid: boolean; - isImmediatePayout: boolean; + immediatePayout: boolean; + dateEstimated: number; } export interface ProposalMilestone extends Milestone { content: string; - immediatePayout: boolean; - dateEstimated: string; payoutPercent: string; title: string; } @@ -26,7 +25,7 @@ export interface ProposalMilestone extends Milestone { export interface CreateMilestone { title: string; content: string; - dateEstimated: string; + dateEstimated: number; payoutPercent: string; immediatePayout: boolean; } diff --git a/frontend/types/proposal.ts b/frontend/types/proposal.ts index 5200cab7..f2fec571 100644 --- a/frontend/types/proposal.ts +++ b/frontend/types/proposal.ts @@ -47,7 +47,8 @@ export interface Proposal extends Omit { percentFunded: number; contributionMatching: number; milestones: ProposalMilestone[]; - datePublished: number; + datePublished: number | null; + dateApproved: number | null; } export interface TeamInviteWithProposal extends TeamInvite {