Merge pull request #33 from dternyak/proposal-rework-amounts

Proposal rework amounts
This commit is contained in:
Daniel Ternyak 2019-01-07 19:09:33 -06:00 committed by GitHub
commit a0d115a703
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 207 additions and 279 deletions

View File

@ -236,6 +236,7 @@ class ProposalSchema(ma.Schema):
"brief",
"proposal_id",
"target",
"funded",
"content",
"comments",
"updates",
@ -250,6 +251,7 @@ class ProposalSchema(ma.Schema):
date_created = ma.Method("get_date_created")
proposal_id = ma.Method("get_proposal_id")
funded = ma.Method("get_funded")
comments = ma.Nested("CommentSchema", many=True)
updates = ma.Nested("ProposalUpdateSchema", many=True)
@ -264,6 +266,10 @@ class ProposalSchema(ma.Schema):
def get_date_created(self, obj):
return dt_to_unix(obj.date_created)
def get_funded(self, obj):
# TODO: Add up all contributions and return that
return "0"
proposal_schema = ProposalSchema()
proposals_schema = ProposalSchema(many=True)

View File

@ -34,7 +34,7 @@ export default class AddressInput extends React.Component<Props> {
};
const inputProps = {
placeholder: DONATION.ETH,
placeholder: DONATION.ZCASH_SPROUT,
prefix: value &&
showIdenticon && (
<Identicon className="AddressInput-input-identicon" address={value} />

View File

@ -111,7 +111,7 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
type="number"
value={target}
onChange={this.handleInputChange}
addonAfter="ETH"
addonAfter="ZEC"
/>
</Form.Item>
</Form>

View File

@ -40,7 +40,7 @@ export default class CreateFlowPayment extends React.Component<Props, State> {
<Input
size="large"
name="payoutAddress"
placeholder={DONATION.ETH}
placeholder={DONATION.ZCASH_SPROUT}
type="text"
value={payoutAddress}
onChange={this.handleInputChange}

View File

@ -64,7 +64,7 @@ class CreateReview extends React.Component<Props> {
},
{
key: 'target',
content: <div style={{ fontSize: '1.2rem' }}>{form.target} ETH</div>,
content: <div style={{ fontSize: '1.2rem' }}>{form.target} ZEC</div>,
error: errors.target,
},
],

View File

@ -21,9 +21,9 @@ export default class Profile extends React.Component<OwnProps> {
</Link>
<div className="ProfileProposal-brief">{brief}</div>
<div className="ProfileProposal-raised">
<UnitDisplay value={funded} symbol="ETH" displayShortBalance={4} />{' '}
<UnitDisplay value={funded} symbol="ZEC" displayShortBalance={4} />{' '}
<small>raised</small> of{' '}
<UnitDisplay value={target} symbol="ETH" displayShortBalance={4} /> goal
<UnitDisplay value={target} symbol="ZEC" displayShortBalance={4} /> goal
</div>
</div>
<div className="ProfileProposal-block">

View File

@ -1,15 +1,13 @@
import React from 'react';
import moment from 'moment';
import BN from 'bn.js';
import { Spin, Form, Input, Button, Icon } from 'antd';
import { Proposal } from 'types';
import classnames from 'classnames';
import { fromWei } from 'utils/units';
import { fromZat } from 'utils/units';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { AppState } from 'store/reducers';
import { withRouter } from 'react-router';
import ShortAddress from 'components/ShortAddress';
import UnitDisplay from 'components/UnitDisplay';
import { getAmountError } from 'utils/validators';
import { CATEGORY_UI } from 'api/constants';
@ -51,8 +49,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
}
// TODO: Get values from proposal
const target = new BN(0);
const funded = new BN(0);
const { target, funded } = this.props.proposal;
const remainingTarget = target.sub(funded);
const amount = parseFloat(value);
let amountError = null;
@ -60,8 +57,8 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
if (Number.isNaN(amount)) {
// They're entering some garbage, theyll work it out
} else {
const remainingEthNum = parseFloat(fromWei(remainingTarget, 'ether'));
amountError = getAmountError(amount, remainingEthNum);
const remainingTargetNum = parseFloat(fromZat(remainingTarget));
amountError = getAmountError(amount, remainingTargetNum);
}
this.setState({ amountToRaise: value, amountError });
@ -80,22 +77,16 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
const amountFloat = parseFloat(amountToRaise) || 0;
let content;
if (proposal) {
const { target, funded, percentFunded } = proposal;
const isRaiseGoalReached = funded.gte(target);
// TODO: Get values from proposal
console.warn('TODO: Get real values from proposal for CampaignBlock');
const isRaiseGoalReached = false;
console.warn('TODO: Get deadline and isFrozen from proposal data');
const deadline = 0;
const isFrozen = false;
const target = new BN(0);
const funded = new BN(0);
const percentFunded = 0;
const beneficiary = 'z123';
const isFundingOver =
isRaiseGoalReached ||
deadline < Date.now() ||
isFrozen;
const isFundingOver = isRaiseGoalReached || deadline < Date.now() || isFrozen;
const isDisabled = isFundingOver || !!amountError || !amountFloat || isPreview;
const remainingEthNum = parseFloat(fromWei(target.sub(funded), 'ether'));
const remainingTargetNum = parseFloat(fromZat(target.sub(funded)));
content = (
<React.Fragment>
@ -123,17 +114,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
</div>
</div>
)}
<div className="ProposalCampaignBlock-info">
<div className="ProposalCampaignBlock-info-label">Beneficiary</div>
<div className="ProposalCampaignBlock-info-value">
<ShortAddress address={beneficiary} />
</div>
</div>
<div className="ProposalCampaignBlock-info">
<div className="ProposalCampaignBlock-info-label">Funding</div>
<div className="ProposalCampaignBlock-info-value">
<UnitDisplay value={funded} /> /{' '}
<UnitDisplay value={target} symbol="ETH" />
<UnitDisplay value={funded} /> / <UnitDisplay value={target} symbol="ZEC" />
</div>
</div>
@ -175,7 +159,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
size="large"
type="number"
placeholder="0.5"
addonAfter="ETH"
addonAfter="ZEC"
disabled={true}
/>
</Form.Item>
@ -193,10 +177,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
value={amountToRaise}
placeholder="0.5"
min={0}
max={remainingEthNum}
max={remainingTargetNum}
step={0.1}
onChange={this.handleAmountChange}
addonAfter="ETH"
addonAfter="ZEC"
disabled={isPreview}
/>
</Form.Item>

View File

@ -15,7 +15,9 @@ const ContributorsBlock = () => {
<AddressRow
key={contributor.address}
address={contributor.address}
secondary={<UnitDisplay value={contributor.contributionAmount} symbol="ETH" />}
secondary={
<UnitDisplay value={contributor.contributionAmount} symbol="ZEC" />
}
/>
));
} else {

View File

@ -1,7 +1,6 @@
import lodash from 'lodash';
import React from 'react';
import moment from 'moment';
import BN from 'bn.js';
import { Alert, Steps, Spin } from 'antd';
import { Proposal, MILESTONE_STATE } from 'types';
import UnitDisplay from 'components/UnitDisplay';
@ -98,11 +97,8 @@ class ProposalMilestones extends React.Component<Props, State> {
const className = this.state.step === i ? 'is-active' : 'is-inactive';
const estimatedDate = moment(milestone.dateEstimated).format('MMMM YYYY');
// TODO: Real milestone amount
console.warn('TODO: Real milestone amount in Proposal/Milestones/index.tsx');
const amount = new BN(0);
const reward = (
<UnitDisplay value={amount} symbol="ETH" displayShortBalance={4} />
<UnitDisplay value={milestone.amount} symbol="ZEC" displayShortBalance={4} />
);
const alertStyle = { width: 'fit-content', margin: '0 0 1rem 0' };
@ -125,9 +121,8 @@ class ProposalMilestones extends React.Component<Props, State> {
The team was awarded <strong>{reward}</strong>{' '}
{milestone.isImmediatePayout
? 'as an initial payout'
: `on ${moment(milestone.payoutRequestVoteDeadline).format(
'MMM Do, YYYY',
)}`}
// TODO: Add property for payout date on milestones
: `on ${moment().format('MMM Do, YYYY')}`}
.
</span>
}
@ -139,12 +134,10 @@ class ProposalMilestones extends React.Component<Props, State> {
notification = (
<Alert
type="info"
message={
<span>
Payout vote is in progress! The approval period ends{' '}
{moment(milestone.payoutRequestVoteDeadline).from(new Date())}.
</span>
}
message={`
The team has requested a payout for this milestone. It is
currently under review.
`}
style={alertStyle}
/>
);
@ -155,10 +148,11 @@ class ProposalMilestones extends React.Component<Props, State> {
type="warning"
message={
<span>
Payout was voted against on{' '}
{moment(milestone.payoutRequestVoteDeadline).format('MMM Do, YYYY')}.
{isTrustee ? ' You ' : ' The team '} can request another payout vote at
any time.
Payout for this milestone was rejected on{' '}
{/* TODO: add property for payout rejection date on milestones */}
{moment().format('MMM Do, YYYY')}.
{isTrustee ? ' You ' : ' The team '} can request another
review for payout at any time.
</span>
}
style={alertStyle}

View File

@ -1,6 +1,5 @@
import React from 'react';
import classnames from 'classnames';
import BN from 'bn.js';
import { Progress, Icon } from 'antd';
import moment from 'moment';
import { Redirect } from 'react-router-dom';
@ -23,12 +22,10 @@ export class ProposalCard extends React.Component<Proposal> {
category,
dateCreated,
team,
target,
funded,
percentFunded,
} = this.props;
// TODO: Real values from proposal
console.warn('TODO: Real values for ProposalCard');
const target = new BN(0);
const funded = new BN(0);
const percentFunded = 0;
return (
<div
@ -38,8 +35,8 @@ export class ProposalCard extends React.Component<Proposal> {
<h3 className="ProposalCard-title">{title}</h3>
<div className="ProposalCard-funding">
<div className="ProposalCard-funding-raised">
<UnitDisplay value={funded} symbol="ETH" /> <small>raised</small> of{' '}
<UnitDisplay value={target} symbol="ETH" /> goal
<UnitDisplay value={funded} symbol="ZEC" /> <small>raised</small> of{' '}
<UnitDisplay value={target} symbol="ZEC" /> goal
</div>
<div
className={classnames({

View File

@ -1,23 +1,28 @@
// Adapted from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/components/ui/UnitDisplay.tsx
import React from 'react';
import {
fromTokenBase,
getDecimalFromEtherUnit,
baseToConvertedUnit,
getDecimalFromUnitKey,
UnitKey,
Wei,
TokenValue,
Zat,
} from 'utils/units';
import { formatNumber } from 'utils/formatters';
interface Props {
/**
* @description base value of the token / ether, incase of waiting for API calls, we can return '???'
* @type {TokenValue | Wei}
* @description base value of the unit
* @type {Zat}
* @memberof Props
*/
value: TokenValue | Wei;
value: Zat;
/**
* @description Symbol to display to the right of the value, such as 'ETH'
* @description Name of the unit to display, defaults to 'zcash'
* @type {UnitKey}
* @memberof Props
*/
unit?: UnitKey;
/**
* @description Symbol to display to the right of the value, such as 'ZEC'
* @type {string}
* @memberof Props
*/
@ -32,22 +37,13 @@ interface Props {
checkOffline?: boolean;
}
interface EthProps extends Props {
unit?: UnitKey;
}
interface TokenProps extends Props {
decimal: number;
}
const isTokenUnit = (param: EthProps | TokenProps): param is TokenProps =>
!!(param as TokenProps).decimal;
const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
const UnitDisplay: React.SFC<Props> = params => {
const { value, symbol, displayShortBalance, displayTrailingZeroes } = params;
const convertedValue = isTokenUnit(params)
? fromTokenBase(value, params.decimal)
: fromTokenBase(value, getDecimalFromEtherUnit(params.unit || 'ether'));
const convertedValue = baseToConvertedUnit(
value.toString(),
getDecimalFromUnitKey(params.unit || 'zcash'),
);
let formattedValue;

View File

@ -2,12 +2,11 @@ import { ProposalDraft, CreateMilestone } from 'types';
import { User } from 'types';
import { getAmountError } from 'utils/validators';
import { MILESTONE_STATE, Proposal } from 'types';
import { Wei, toWei } from 'utils/units';
import { Zat, toZat } from 'utils/units';
import { ONE_DAY } from 'utils/time';
import { PROPOSAL_CATEGORY } from 'api/constants';
// TODO: Raise this limit
export const TARGET_ETH_LIMIT = 1000;
export const TARGET_ZEC_LIMIT = 1000;
interface CreateFormErrors {
title?: string;
@ -75,7 +74,7 @@ export function getCreateErrors(
// Amount to raise
const targetFloat = target ? parseFloat(target) : 0;
if (target && !Number.isNaN(targetFloat)) {
const targetErr = getAmountError(targetFloat, TARGET_ETH_LIMIT);
const targetErr = getAmountError(targetFloat, TARGET_ZEC_LIMIT);
if (targetErr) {
errors.target = targetErr;
}
@ -148,19 +147,19 @@ export function getCreateWarnings(form: Partial<ProposalDraft>): string[] {
return warnings;
}
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Wei) {
return raiseGoal.divn(100).mul(Wei(milestone.payoutPercent));
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Zat) {
return raiseGoal.divn(100).mul(Zat(milestone.payoutPercent));
}
export function proposalToContractData(form: ProposalDraft): any {
const targetInWei = toWei(form.target, 'ether');
const targetInZat = toZat(form.target);
const milestoneAmounts = form.milestones.map(m =>
milestoneToMilestoneAmount(m, targetInWei),
milestoneToMilestoneAmount(m, targetInZat),
);
const immediateFirstMilestonePayout = form.milestones[0]!.immediatePayout;
return {
ethAmount: targetInWei,
ethAmount: targetInZat,
payoutAddress: form.payoutAddress,
trusteesAddresses: [],
milestoneAmounts,
@ -180,10 +179,15 @@ export function makeProposalPreviewFromDraft(
proposalId: 0,
proposalUrlId: '0-title',
proposalAddress: '0x0',
payoutAddress: '0x0',
dateCreated: Date.now(),
title: draft.title,
brief: draft.brief,
content: draft.content,
deadlineDuration: 86400 * 60,
target: toZat(draft.target),
funded: Zat('0'),
percentFunded: 0,
stage: 'preview',
category: draft.category || PROPOSAL_CATEGORY.DAPP,
team: draft.team,
@ -191,17 +195,13 @@ export function makeProposalPreviewFromDraft(
index: idx,
title: m.title,
content: m.content,
amount: toWei(target * (parseInt(m.payoutPercent, 10) / 100), 'ether'),
amountAgainstPayout: Wei('0'),
percentAgainstPayout: 0,
payoutRequestVoteDeadline: Date.now(),
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,
stage: MILESTONE_STATE.WAITING,
})),
};
}

1
frontend/client/typings/omit.d.ts vendored Normal file
View File

@ -0,0 +1 @@
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

View File

@ -1,7 +1,8 @@
import BN from 'bn.js';
import { User, Proposal, UserProposal } from 'types';
import { User, Proposal, UserProposal, MILESTONE_STATE } from 'types';
import { UserState } from 'modules/users/reducers';
import { AppState } from 'store/reducers';
import { toZat } from './units';
export function formatUserForPost(user: User) {
return {
@ -21,8 +22,22 @@ export function formatUserFromGet(user: UserState) {
return user;
}
export function formatProposalFromGet(proposal: Proposal) {
export function formatProposalFromGet(p: any): Proposal {
const proposal = { ...p } as Proposal;
proposal.proposalUrlId = generateProposalUrl(proposal.proposalId, proposal.title);
proposal.target = toZat(p.target);
proposal.funded = toZat(p.funded);
proposal.percentFunded = proposal.funded.div(proposal.target.divn(100)).toNumber();
proposal.milestones = proposal.milestones.map((m: any, index: number) => {
return {
...m,
index,
amount: proposal.target.mul(new BN(m.payoutPercent)).divn(100),
// TODO: Get data from backend
state: MILESTONE_STATE.WAITING,
isPaid: false,
};
});
return proposal;
}
@ -48,6 +63,16 @@ export function extractProposalIdFromUrl(slug: string) {
// pre-hydration massage (BNify JSONed BNs)
export function massageSerializedState(state: AppState) {
// proposals
state.proposal.proposals = state.proposal.proposals.map(p => ({
...p,
target: new BN((p.target as any) as string, 16),
funded: new BN((p.funded as any) as string, 16),
milestones: p.milestones.map(m => ({
...m,
amount: new BN((m.amount as any) as string, 16),
})),
}));
// users
const bnUserProp = (p: UserProposal) => {
p.funded = new BN(p.funded, 16);

View File

@ -1,3 +1,4 @@
export const DONATION = {
ETH: '0x8B0B72F8bDE212991135668922fD5acE557DE6aB',
ZCASH_TRANSPARENT: 't1aib2cbwPVrFfrjGGkhWD67imdBet1xDTr',
ZCASH_SPROUT: 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen',
};

View File

@ -2,42 +2,16 @@
import BN from 'bn.js';
import { stripHexPrefix } from 'utils/formatters';
type UnitKey = keyof typeof Units;
type Wei = BN;
type TokenValue = BN;
type Address = Buffer;
type Nonce = BN;
type Data = Buffer;
export const ETH_DECIMAL = 18;
export const ZCASH_DECIMAL = 8;
export const Units = {
wei: '1',
kwei: '1000',
ada: '1000',
femtoether: '1000',
mwei: '1000000',
babbage: '1000000',
picoether: '1000000',
gwei: '1000000000',
shannon: '1000000000',
nanoether: '1000000000',
nano: '1000000000',
szabo: '1000000000000',
microether: '1000000000000',
micro: '1000000000000',
finney: '1000000000000000',
milliether: '1000000000000000',
milli: '1000000000000000',
ether: '1000000000000000000',
kether: '1000000000000000000000',
grand: '1000000000000000000000',
einstein: '1000000000000000000000',
mether: '1000000000000000000000000',
gether: '1000000000000000000000000000',
tether: '1000000000000000000000000000000',
zat: '1',
zcash: '100000000',
};
const handleValues = (input: string | BN) => {
export type Zat = BN;
export type UnitKey = keyof typeof Units;
export const handleValues = (input: string | BN) => {
if (typeof input === 'string') {
return input.startsWith('0x') ? new BN(stripHexPrefix(input), 16) : new BN(input);
}
@ -51,20 +25,14 @@ const handleValues = (input: string | BN) => {
}
};
const Nonce = (input: string | BN) => handleValues(input);
const Wei = (input: string | BN): Wei => handleValues(input);
const TokenValue = (input: string | BN) => handleValues(input);
const getDecimalFromEtherUnit = (key: UnitKey) => Units[key].length - 1;
export const Zat = (input: string | BN): Zat => handleValues(input);
const stripRightZeros = (str: string) => {
const strippedStr = str.replace(/0+$/, '');
return strippedStr === '' ? null : strippedStr;
};
const baseToConvertedUnit = (value: string, decimal: number) => {
export const baseToConvertedUnit = (value: string, decimal: number) => {
if (decimal === 0) {
return value;
}
@ -83,52 +51,14 @@ const convertedToBaseUnit = (value: string, decimal: number) => {
return `${integerPart}${paddedFraction}`;
};
const fromWei = (wei: Wei, unit: UnitKey) => {
const decimal = getDecimalFromEtherUnit(unit);
return baseToConvertedUnit(wei.toString(), decimal);
export const fromZat = (zat: Zat) => {
return baseToConvertedUnit(zat.toString(), ZCASH_DECIMAL);
};
const toWei = (value: string | number, unitType: number | UnitKey): Wei => {
export const toZat = (value: string | number): Zat => {
value = value.toString();
let decimal;
if (typeof unitType === 'string') {
decimal = getDecimalFromEtherUnit(unitType);
} else {
decimal = unitType;
}
const wei = convertedToBaseUnit(value, decimal);
return Wei(wei);
const zat = convertedToBaseUnit(value, ZCASH_DECIMAL);
return Zat(zat);
};
const fromTokenBase = (value: TokenValue, decimal: number) =>
baseToConvertedUnit(value.toString(), decimal);
const toTokenBase = (value: string, decimal: number) =>
TokenValue(convertedToBaseUnit(value, decimal));
const convertTokenBase = (value: TokenValue, oldDecimal: number, newDecimal: number) => {
if (oldDecimal === newDecimal) {
return value;
}
return toTokenBase(fromTokenBase(value, oldDecimal), newDecimal);
};
const gasPriceToBase = (price: number) =>
toWei(price.toString(), getDecimalFromEtherUnit('gwei'));
export {
Data,
Address,
TokenValue,
fromWei,
toWei,
toTokenBase,
fromTokenBase,
convertTokenBase,
Wei,
getDecimalFromEtherUnit,
UnitKey,
Nonce,
handleValues,
gasPriceToBase,
};
export const getDecimalFromUnitKey = (key: UnitKey) => Units[key].length - 1;

View File

@ -7,7 +7,7 @@ export function getAmountError(amount: number, max: number = Infinity) {
) {
return 'Must be in increments of 0.001';
} else if (amount > max) {
return `Cannot exceed maximum (${max} ETH)`;
return `Cannot exceed maximum (${max} ZEC)`;
}
return null;

View File

@ -64,7 +64,7 @@ class Example extends React.Component<ExampleProps, ExampleState> {
constructor(props: ExampleProps) {
super(props);
this.state = {
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ETH,
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ZCASH_SPROUT,
};
}

View File

@ -7,21 +7,21 @@ import Contributors from 'components/Proposal/Contributors';
import 'styles/style.less';
import 'components/Proposal/style.less';
import 'components/Proposal/Governance/style.less';
import { getProposalWithCrowdFund } from './props';
import { generateProposal } from './props';
const propsNoFunding = getProposalWithCrowdFund({
const propsNoFunding = generateProposal({
amount: 5,
funded: 0,
});
const propsHalfFunded = getProposalWithCrowdFund({
const propsHalfFunded = generateProposal({
amount: 5,
funded: 2.5,
});
const propsFunded = getProposalWithCrowdFund({
const propsFunded = generateProposal({
amount: 5,
funded: 5,
});
const propsNotFundedExpired = getProposalWithCrowdFund({
const propsNotFundedExpired = generateProposal({
created: Date.now() - 10,
deadline: Date.now() - 1,
});

View File

@ -6,21 +6,21 @@ import { ProposalCard } from 'components/Proposals/ProposalCard';
import 'styles/style.less';
import 'components/Proposal/style.less';
import 'components/Proposal/Governance/style.less';
import { getProposalWithCrowdFund } from './props';
import { generateProposal } from './props';
const propsNoFunding = getProposalWithCrowdFund({
const propsNoFunding = generateProposal({
amount: 5,
funded: 0,
});
const propsHalfFunded = getProposalWithCrowdFund({
const propsHalfFunded = generateProposal({
amount: 5,
funded: 2.5,
});
const propsFunded = getProposalWithCrowdFund({
const propsFunded = generateProposal({
amount: 5,
funded: 5,
});
const propsNotFundedExpired = getProposalWithCrowdFund({
const propsNotFundedExpired = generateProposal({
created: Date.now() - 1000 * 60 * 60 * 10,
deadline: Date.now() - 1,
});

View File

@ -11,7 +11,7 @@ const { WAITING, ACTIVE, PAID, REJECTED } = MILESTONE_STATE;
import 'styles/style.less';
import 'components/Proposal/style.less';
import 'components/Proposal/Governance/style.less';
import { getProposalWithCrowdFund } from './props';
import { generateProposal } from './props';
const msWaiting = { state: WAITING, isPaid: false };
const msPaid = { state: PAID, isPaid: true };
@ -22,96 +22,96 @@ const trustee = 'z123';
const contributor = 'z456';
const geometryCases = [...Array(10).keys()].map(i =>
getProposalWithCrowdFund({ milestoneCount: i + 1 }),
generateProposal({ milestoneCount: i + 1 }),
);
const cases: { [index: string]: any } = {
// trustee - first
['not funded']: getProposalWithCrowdFund({
['not funded']: generateProposal({
amount: 5,
funded: 0,
}),
['first - waiting']: getProposalWithCrowdFund({
['first - waiting']: generateProposal({
amount: 5,
funded: 5,
}),
['first - not paid']: getProposalWithCrowdFund({
['first - not paid']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
{ state: PAID, isPaid: false },
msWaiting,
msWaiting,
],
}),
// trustee - second
['second - waiting']: getProposalWithCrowdFund({
['second - waiting']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msWaiting, msWaiting],
}),
['second - active']: getProposalWithCrowdFund({
['second - active']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msActive, msWaiting],
}),
['second - not paid']: getProposalWithCrowdFund({
['second - not paid']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [
msPaid,
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
{ state: PAID, isPaid: false },
msWaiting,
],
}),
['second - no vote']: getProposalWithCrowdFund({
['second - no vote']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [
msPaid,
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
{ state: ACTIVE, isPaid: false },
msWaiting,
],
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
}),
['second - rejected']: getProposalWithCrowdFund({
['second - rejected']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msRejected, msWaiting],
}),
// trustee - third
['final - waiting']: getProposalWithCrowdFund({
['final - waiting']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msPaid, msWaiting],
}),
['final - active']: getProposalWithCrowdFund({
['final - active']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msPaid, msActive],
}),
['final - not paid']: getProposalWithCrowdFund({
['final - not paid']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [
msPaid,
msPaid,
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
{ state: PAID, isPaid: false },
],
}),
['final - no vote']: getProposalWithCrowdFund({
['final - no vote']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [
msPaid,
msPaid,
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
{ state: ACTIVE, isPaid: false },
],
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
}),
['final - rejected']: getProposalWithCrowdFund({
['final - rejected']: generateProposal({
amount: 5,
funded: 5,
milestoneOverrides: [msPaid, msPaid, msRejected],

View File

@ -4,30 +4,30 @@ import BN from 'bn.js';
import UnitDisplay from '../client/components/UnitDisplay';
const oneEth = new BN('1000000000000000000');
const oneZEC = new BN('1000000000000000000');
const cases = [
{
disp: 'basic',
props: { value: oneEth.mul(new BN(25)), symbol: 'ETH' },
props: { value: oneZEC.mul(new BN(25)), symbol: 'ZEC' },
},
{
disp: 'fraction',
props: { value: oneEth.div(new BN(3)), symbol: 'ETH' },
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC' },
},
{
disp: 'fraction - displayShortBalance: true',
props: { value: oneEth.div(new BN(3)), symbol: 'ETH', displayShortBalance: true },
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC', displayShortBalance: true },
},
{
disp: 'fraction - displayShortBalance: 2',
props: { value: oneEth.div(new BN(3)), symbol: 'ETH', displayShortBalance: 2 },
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC', displayShortBalance: 2 },
},
{
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: false',
props: {
value: oneEth.div(new BN(2)),
symbol: 'ETH',
value: oneZEC.div(new BN(2)),
symbol: 'ZEC',
displayShortBalance: 4,
displayTrailingZeroes: false,
},
@ -35,21 +35,21 @@ const cases = [
{
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: true',
props: {
value: oneEth.div(new BN(2)),
symbol: 'ETH',
value: oneZEC.div(new BN(2)),
symbol: 'ZEC',
displayShortBalance: 4,
displayTrailingZeroes: true,
},
},
{
disp: 'tiny',
props: { value: new BN(1), symbol: 'ETH' },
props: { value: new BN(1), symbol: 'ZEC' },
},
{
disp: 'tiny - displayShortBalance: true',
props: {
value: new BN(1),
symbol: 'ETH',
symbol: 'ZEC',
displayShortBalance: true,
},
},
@ -57,7 +57,7 @@ const cases = [
disp: 'tiny - displayShortBalance: 2',
props: {
value: new BN(1),
symbol: 'ETH',
symbol: 'ZEC',
displayShortBalance: 2,
},
},

View File

@ -34,7 +34,7 @@ const cases: Case[] = [
},
},
{
disp: 'ETH Address Only User',
disp: 'ZEC Address Only User',
props: {
user: {
...user,
@ -43,7 +43,7 @@ const cases: Case[] = [
},
},
{
disp: 'No Avatar, No ETH Address User',
disp: 'No Avatar, No ZEC Address User',
props: {
user: {
...user,

View File

@ -8,7 +8,7 @@ import {
import { PROPOSAL_CATEGORY } from 'api/constants';
import BN from 'bn.js';
const oneEth = new BN('1000000000000000000');
const oneZec = new BN('100000000');
export function getGovernanceMilestonesProps({
isContributor = true,
@ -26,7 +26,7 @@ export function getGovernanceMilestonesProps({
};
}
export function getProposalWithCrowdFund({
export function generateProposal({
amount = 10,
funded = 5,
created = Date.now(),
@ -42,8 +42,9 @@ export function getProposalWithCrowdFund({
contributorOverrides?: Array<Partial<Contributor>>;
milestoneCount?: number;
}) {
const amountBn = oneEth.mul(new BN(amount));
const fundedBn = oneEth.mul(new BN(funded));
const amountBn = oneZec.mul(new BN(amount));
const fundedBn = oneZec.mul(new BN(funded));
const percentFunded = amount / funded;
let contributors = [
{
@ -96,34 +97,27 @@ export function getProposalWithCrowdFund({
return ts.slice(rand).join('');
};
const genMilestone = (overrides: Partial<ProposalMilestone> = {}) => {
const genMilestone = (overrides: Partial<ProposalMilestone> = {}): ProposalMilestone => {
const now = new Date();
if (overrides.index) {
const estimate = new Date(now.setMonth(now.getMonth() + overrides.index));
overrides.dateEstimated = estimate.toISOString();
}
return Object.assign(
{
title: 'Milestone A',
body: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.`,
content: '',
dateEstimated: '2018-10-01T00:00:00+00:00',
immediatePayout: true,
index: 0,
state: MILESTONE_STATE.WAITING,
amount: amountBn,
amountAgainstPayout: new BN(0),
percentAgainstPayout: 0,
payoutRequestVoteDeadline: 0,
isPaid: false,
isImmediatePayout: true,
payoutPercent: '33',
stage: 'NOT_REQUESTED',
},
overrides,
);
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',
immediatePayout: true,
index: 0,
state: MILESTONE_STATE.WAITING,
amount: amountBn,
isPaid: false,
isImmediatePayout: true,
payoutPercent: '33',
};
return { ...defaults, ...overrides };
};
const milestones = [...Array(milestoneCount).keys()].map(i => {
@ -148,7 +142,12 @@ export function getProposalWithCrowdFund({
proposalId: 12345,
proposalUrlId: '12345-crowdfund-title',
proposalAddress: '0x033fDc6C01DC2385118C7bAAB88093e22B8F0710',
payoutAddress: 'z123',
dateCreated: created / 1000,
deadlineDuration: 86400 * 60,
target: amountBn,
funded: fundedBn,
percentFunded,
title: 'Crowdfund Title',
brief: 'A cool test crowdfund',
content: 'body',

View File

@ -1,4 +1,4 @@
import { Wei } from 'utils/units';
import { Zat } from 'utils/units';
export enum MILESTONE_STATE {
WAITING = 'WAITING',
@ -10,10 +10,7 @@ export enum MILESTONE_STATE {
export interface Milestone {
index: number;
state: MILESTONE_STATE;
amount: Wei;
amountAgainstPayout: Wei;
percentAgainstPayout: number;
payoutRequestVoteDeadline: number;
amount: Zat;
isPaid: boolean;
isImmediatePayout: boolean;
}
@ -23,7 +20,6 @@ export interface ProposalMilestone extends Milestone {
immediatePayout: boolean;
dateEstimated: string;
payoutPercent: string;
stage: string;
title: string;
}

View File

@ -1,12 +1,13 @@
import { Wei } from 'utils/units';
import BN from 'bn.js';
import { Zat } from 'utils/units';
import { PROPOSAL_CATEGORY } from 'api/constants';
import {
CreateMilestone,
ProposalMilestone,
Update,
User,
Comment,
} from 'types';
import { ProposalMilestone } from './milestone';
export interface TeamInvite {
id: number;
@ -17,7 +18,7 @@ export interface TeamInvite {
export interface Contributor {
address: string;
contributionAmount: Wei;
contributionAmount: Zat;
refundVote: boolean;
refunded: boolean;
proportionalContribution: string;
@ -40,18 +41,14 @@ export interface ProposalDraft {
invites: TeamInvite[];
}
export interface Proposal {
proposalId: number;
export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
proposalAddress: string;
proposalUrlId: string;
dateCreated: number;
title: string;
brief: string;
content: string;
stage: string;
category: PROPOSAL_CATEGORY;
target: BN;
funded: BN;
percentFunded: number;
milestones: ProposalMilestone[];
team: User[];
}
export interface TeamInviteWithProposal extends TeamInvite {
@ -74,6 +71,6 @@ export interface UserProposal {
title: string;
brief: string;
team: User[];
funded: Wei;
target: Wei;
funded: BN;
target: BN;
}