Merge pull request #33 from dternyak/proposal-rework-amounts
Proposal rework amounts
This commit is contained in:
commit
a0d115a703
|
@ -236,6 +236,7 @@ class ProposalSchema(ma.Schema):
|
||||||
"brief",
|
"brief",
|
||||||
"proposal_id",
|
"proposal_id",
|
||||||
"target",
|
"target",
|
||||||
|
"funded",
|
||||||
"content",
|
"content",
|
||||||
"comments",
|
"comments",
|
||||||
"updates",
|
"updates",
|
||||||
|
@ -250,6 +251,7 @@ class ProposalSchema(ma.Schema):
|
||||||
|
|
||||||
date_created = ma.Method("get_date_created")
|
date_created = ma.Method("get_date_created")
|
||||||
proposal_id = ma.Method("get_proposal_id")
|
proposal_id = ma.Method("get_proposal_id")
|
||||||
|
funded = ma.Method("get_funded")
|
||||||
|
|
||||||
comments = ma.Nested("CommentSchema", many=True)
|
comments = ma.Nested("CommentSchema", many=True)
|
||||||
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
||||||
|
@ -264,6 +266,10 @@ class ProposalSchema(ma.Schema):
|
||||||
def get_date_created(self, obj):
|
def get_date_created(self, obj):
|
||||||
return dt_to_unix(obj.date_created)
|
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()
|
proposal_schema = ProposalSchema()
|
||||||
proposals_schema = ProposalSchema(many=True)
|
proposals_schema = ProposalSchema(many=True)
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class AddressInput extends React.Component<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputProps = {
|
const inputProps = {
|
||||||
placeholder: DONATION.ETH,
|
placeholder: DONATION.ZCASH_SPROUT,
|
||||||
prefix: value &&
|
prefix: value &&
|
||||||
showIdenticon && (
|
showIdenticon && (
|
||||||
<Identicon className="AddressInput-input-identicon" address={value} />
|
<Identicon className="AddressInput-input-identicon" address={value} />
|
||||||
|
|
|
@ -111,7 +111,7 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
type="number"
|
type="number"
|
||||||
value={target}
|
value={target}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
addonAfter="ETH"
|
addonAfter="ZEC"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class CreateFlowPayment extends React.Component<Props, State> {
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size="large"
|
||||||
name="payoutAddress"
|
name="payoutAddress"
|
||||||
placeholder={DONATION.ETH}
|
placeholder={DONATION.ZCASH_SPROUT}
|
||||||
type="text"
|
type="text"
|
||||||
value={payoutAddress}
|
value={payoutAddress}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
|
|
|
@ -64,7 +64,7 @@ class CreateReview extends React.Component<Props> {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'target',
|
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,
|
error: errors.target,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -21,9 +21,9 @@ export default class Profile extends React.Component<OwnProps> {
|
||||||
</Link>
|
</Link>
|
||||||
<div className="ProfileProposal-brief">{brief}</div>
|
<div className="ProfileProposal-brief">{brief}</div>
|
||||||
<div className="ProfileProposal-raised">
|
<div className="ProfileProposal-raised">
|
||||||
<UnitDisplay value={funded} symbol="ETH" displayShortBalance={4} />{' '}
|
<UnitDisplay value={funded} symbol="ZEC" displayShortBalance={4} />{' '}
|
||||||
<small>raised</small> of{' '}
|
<small>raised</small> of{' '}
|
||||||
<UnitDisplay value={target} symbol="ETH" displayShortBalance={4} /> goal
|
<UnitDisplay value={target} symbol="ZEC" displayShortBalance={4} /> goal
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ProfileProposal-block">
|
<div className="ProfileProposal-block">
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import BN from 'bn.js';
|
|
||||||
import { Spin, Form, Input, Button, Icon } from 'antd';
|
import { Spin, Form, Input, Button, Icon } from 'antd';
|
||||||
import { Proposal } from 'types';
|
import { Proposal } from 'types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { fromWei } from 'utils/units';
|
import { fromZat } from 'utils/units';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { compose } from 'recompose';
|
import { compose } from 'recompose';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import ShortAddress from 'components/ShortAddress';
|
|
||||||
import UnitDisplay from 'components/UnitDisplay';
|
import UnitDisplay from 'components/UnitDisplay';
|
||||||
import { getAmountError } from 'utils/validators';
|
import { getAmountError } from 'utils/validators';
|
||||||
import { CATEGORY_UI } from 'api/constants';
|
import { CATEGORY_UI } from 'api/constants';
|
||||||
|
@ -51,8 +49,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get values from proposal
|
// TODO: Get values from proposal
|
||||||
const target = new BN(0);
|
const { target, funded } = this.props.proposal;
|
||||||
const funded = new BN(0);
|
|
||||||
const remainingTarget = target.sub(funded);
|
const remainingTarget = target.sub(funded);
|
||||||
const amount = parseFloat(value);
|
const amount = parseFloat(value);
|
||||||
let amountError = null;
|
let amountError = null;
|
||||||
|
@ -60,8 +57,8 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
if (Number.isNaN(amount)) {
|
if (Number.isNaN(amount)) {
|
||||||
// They're entering some garbage, they’ll work it out
|
// They're entering some garbage, they’ll work it out
|
||||||
} else {
|
} else {
|
||||||
const remainingEthNum = parseFloat(fromWei(remainingTarget, 'ether'));
|
const remainingTargetNum = parseFloat(fromZat(remainingTarget));
|
||||||
amountError = getAmountError(amount, remainingEthNum);
|
amountError = getAmountError(amount, remainingTargetNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ amountToRaise: value, amountError });
|
this.setState({ amountToRaise: value, amountError });
|
||||||
|
@ -80,22 +77,16 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
const amountFloat = parseFloat(amountToRaise) || 0;
|
const amountFloat = parseFloat(amountToRaise) || 0;
|
||||||
let content;
|
let content;
|
||||||
if (proposal) {
|
if (proposal) {
|
||||||
|
const { target, funded, percentFunded } = proposal;
|
||||||
|
const isRaiseGoalReached = funded.gte(target);
|
||||||
// TODO: Get values from proposal
|
// TODO: Get values from proposal
|
||||||
console.warn('TODO: Get real values from proposal for CampaignBlock');
|
console.warn('TODO: Get deadline and isFrozen from proposal data');
|
||||||
const isRaiseGoalReached = false;
|
|
||||||
const deadline = 0;
|
const deadline = 0;
|
||||||
const isFrozen = false;
|
const isFrozen = false;
|
||||||
const target = new BN(0);
|
|
||||||
const funded = new BN(0);
|
|
||||||
const percentFunded = 0;
|
|
||||||
const beneficiary = 'z123';
|
|
||||||
|
|
||||||
const isFundingOver =
|
const isFundingOver = isRaiseGoalReached || deadline < Date.now() || isFrozen;
|
||||||
isRaiseGoalReached ||
|
|
||||||
deadline < Date.now() ||
|
|
||||||
isFrozen;
|
|
||||||
const isDisabled = isFundingOver || !!amountError || !amountFloat || isPreview;
|
const isDisabled = isFundingOver || !!amountError || !amountFloat || isPreview;
|
||||||
const remainingEthNum = parseFloat(fromWei(target.sub(funded), 'ether'));
|
const remainingTargetNum = parseFloat(fromZat(target.sub(funded)));
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -123,17 +114,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
||||||
<div className="ProposalCampaignBlock-info-value">
|
<div className="ProposalCampaignBlock-info-value">
|
||||||
<UnitDisplay value={funded} /> /{' '}
|
<UnitDisplay value={funded} /> / <UnitDisplay value={target} symbol="ZEC" />
|
||||||
<UnitDisplay value={target} symbol="ETH" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -175,7 +159,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
size="large"
|
size="large"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="0.5"
|
placeholder="0.5"
|
||||||
addonAfter="ETH"
|
addonAfter="ZEC"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -193,10 +177,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
value={amountToRaise}
|
value={amountToRaise}
|
||||||
placeholder="0.5"
|
placeholder="0.5"
|
||||||
min={0}
|
min={0}
|
||||||
max={remainingEthNum}
|
max={remainingTargetNum}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
onChange={this.handleAmountChange}
|
onChange={this.handleAmountChange}
|
||||||
addonAfter="ETH"
|
addonAfter="ZEC"
|
||||||
disabled={isPreview}
|
disabled={isPreview}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -15,7 +15,9 @@ const ContributorsBlock = () => {
|
||||||
<AddressRow
|
<AddressRow
|
||||||
key={contributor.address}
|
key={contributor.address}
|
||||||
address={contributor.address}
|
address={contributor.address}
|
||||||
secondary={<UnitDisplay value={contributor.contributionAmount} symbol="ETH" />}
|
secondary={
|
||||||
|
<UnitDisplay value={contributor.contributionAmount} symbol="ZEC" />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import BN from 'bn.js';
|
|
||||||
import { Alert, Steps, Spin } from 'antd';
|
import { Alert, Steps, Spin } from 'antd';
|
||||||
import { Proposal, MILESTONE_STATE } from 'types';
|
import { Proposal, MILESTONE_STATE } from 'types';
|
||||||
import UnitDisplay from 'components/UnitDisplay';
|
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 className = this.state.step === i ? 'is-active' : 'is-inactive';
|
||||||
const estimatedDate = moment(milestone.dateEstimated).format('MMMM YYYY');
|
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 = (
|
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' };
|
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>{' '}
|
The team was awarded <strong>{reward}</strong>{' '}
|
||||||
{milestone.isImmediatePayout
|
{milestone.isImmediatePayout
|
||||||
? 'as an initial payout'
|
? 'as an initial payout'
|
||||||
: `on ${moment(milestone.payoutRequestVoteDeadline).format(
|
// TODO: Add property for payout date on milestones
|
||||||
'MMM Do, YYYY',
|
: `on ${moment().format('MMM Do, YYYY')}`}
|
||||||
)}`}
|
|
||||||
.
|
.
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@ -139,12 +134,10 @@ class ProposalMilestones extends React.Component<Props, State> {
|
||||||
notification = (
|
notification = (
|
||||||
<Alert
|
<Alert
|
||||||
type="info"
|
type="info"
|
||||||
message={
|
message={`
|
||||||
<span>
|
The team has requested a payout for this milestone. It is
|
||||||
Payout vote is in progress! The approval period ends{' '}
|
currently under review.
|
||||||
{moment(milestone.payoutRequestVoteDeadline).from(new Date())}.
|
`}
|
||||||
</span>
|
|
||||||
}
|
|
||||||
style={alertStyle}
|
style={alertStyle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -155,10 +148,11 @@ class ProposalMilestones extends React.Component<Props, State> {
|
||||||
type="warning"
|
type="warning"
|
||||||
message={
|
message={
|
||||||
<span>
|
<span>
|
||||||
Payout was voted against on{' '}
|
Payout for this milestone was rejected on{' '}
|
||||||
{moment(milestone.payoutRequestVoteDeadline).format('MMM Do, YYYY')}.
|
{/* TODO: add property for payout rejection date on milestones */}
|
||||||
{isTrustee ? ' You ' : ' The team '} can request another payout vote at
|
{moment().format('MMM Do, YYYY')}.
|
||||||
any time.
|
{isTrustee ? ' You ' : ' The team '} can request another
|
||||||
|
review for payout at any time.
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
style={alertStyle}
|
style={alertStyle}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import BN from 'bn.js';
|
|
||||||
import { Progress, Icon } from 'antd';
|
import { Progress, Icon } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
@ -23,12 +22,10 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
category,
|
category,
|
||||||
dateCreated,
|
dateCreated,
|
||||||
team,
|
team,
|
||||||
|
target,
|
||||||
|
funded,
|
||||||
|
percentFunded,
|
||||||
} = this.props;
|
} = 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -38,8 +35,8 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
<h3 className="ProposalCard-title">{title}</h3>
|
<h3 className="ProposalCard-title">{title}</h3>
|
||||||
<div className="ProposalCard-funding">
|
<div className="ProposalCard-funding">
|
||||||
<div className="ProposalCard-funding-raised">
|
<div className="ProposalCard-funding-raised">
|
||||||
<UnitDisplay value={funded} symbol="ETH" /> <small>raised</small> of{' '}
|
<UnitDisplay value={funded} symbol="ZEC" /> <small>raised</small> of{' '}
|
||||||
<UnitDisplay value={target} symbol="ETH" /> goal
|
<UnitDisplay value={target} symbol="ZEC" /> goal
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames({
|
className={classnames({
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
// Adapted from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/components/ui/UnitDisplay.tsx
|
// Adapted from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/components/ui/UnitDisplay.tsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
fromTokenBase,
|
baseToConvertedUnit,
|
||||||
getDecimalFromEtherUnit,
|
getDecimalFromUnitKey,
|
||||||
UnitKey,
|
UnitKey,
|
||||||
Wei,
|
Zat,
|
||||||
TokenValue,
|
|
||||||
} from 'utils/units';
|
} from 'utils/units';
|
||||||
import { formatNumber } from 'utils/formatters';
|
import { formatNumber } from 'utils/formatters';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
* @description base value of the token / ether, incase of waiting for API calls, we can return '???'
|
* @description base value of the unit
|
||||||
* @type {TokenValue | Wei}
|
* @type {Zat}
|
||||||
* @memberof Props
|
* @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}
|
* @type {string}
|
||||||
* @memberof Props
|
* @memberof Props
|
||||||
*/
|
*/
|
||||||
|
@ -32,22 +37,13 @@ interface Props {
|
||||||
checkOffline?: boolean;
|
checkOffline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EthProps extends Props {
|
const UnitDisplay: React.SFC<Props> = params => {
|
||||||
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 { value, symbol, displayShortBalance, displayTrailingZeroes } = params;
|
const { value, symbol, displayShortBalance, displayTrailingZeroes } = params;
|
||||||
|
|
||||||
const convertedValue = isTokenUnit(params)
|
const convertedValue = baseToConvertedUnit(
|
||||||
? fromTokenBase(value, params.decimal)
|
value.toString(),
|
||||||
: fromTokenBase(value, getDecimalFromEtherUnit(params.unit || 'ether'));
|
getDecimalFromUnitKey(params.unit || 'zcash'),
|
||||||
|
);
|
||||||
|
|
||||||
let formattedValue;
|
let formattedValue;
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,11 @@ import { ProposalDraft, CreateMilestone } from 'types';
|
||||||
import { User } from 'types';
|
import { User } from 'types';
|
||||||
import { getAmountError } from 'utils/validators';
|
import { getAmountError } from 'utils/validators';
|
||||||
import { MILESTONE_STATE, Proposal } from 'types';
|
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 { ONE_DAY } from 'utils/time';
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
|
|
||||||
// TODO: Raise this limit
|
export const TARGET_ZEC_LIMIT = 1000;
|
||||||
export const TARGET_ETH_LIMIT = 1000;
|
|
||||||
|
|
||||||
interface CreateFormErrors {
|
interface CreateFormErrors {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -75,7 +74,7 @@ export function getCreateErrors(
|
||||||
// Amount to raise
|
// Amount to raise
|
||||||
const targetFloat = target ? parseFloat(target) : 0;
|
const targetFloat = target ? parseFloat(target) : 0;
|
||||||
if (target && !Number.isNaN(targetFloat)) {
|
if (target && !Number.isNaN(targetFloat)) {
|
||||||
const targetErr = getAmountError(targetFloat, TARGET_ETH_LIMIT);
|
const targetErr = getAmountError(targetFloat, TARGET_ZEC_LIMIT);
|
||||||
if (targetErr) {
|
if (targetErr) {
|
||||||
errors.target = targetErr;
|
errors.target = targetErr;
|
||||||
}
|
}
|
||||||
|
@ -148,19 +147,19 @@ export function getCreateWarnings(form: Partial<ProposalDraft>): string[] {
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Wei) {
|
function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Zat) {
|
||||||
return raiseGoal.divn(100).mul(Wei(milestone.payoutPercent));
|
return raiseGoal.divn(100).mul(Zat(milestone.payoutPercent));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function proposalToContractData(form: ProposalDraft): any {
|
export function proposalToContractData(form: ProposalDraft): any {
|
||||||
const targetInWei = toWei(form.target, 'ether');
|
const targetInZat = toZat(form.target);
|
||||||
const milestoneAmounts = form.milestones.map(m =>
|
const milestoneAmounts = form.milestones.map(m =>
|
||||||
milestoneToMilestoneAmount(m, targetInWei),
|
milestoneToMilestoneAmount(m, targetInZat),
|
||||||
);
|
);
|
||||||
const immediateFirstMilestonePayout = form.milestones[0]!.immediatePayout;
|
const immediateFirstMilestonePayout = form.milestones[0]!.immediatePayout;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ethAmount: targetInWei,
|
ethAmount: targetInZat,
|
||||||
payoutAddress: form.payoutAddress,
|
payoutAddress: form.payoutAddress,
|
||||||
trusteesAddresses: [],
|
trusteesAddresses: [],
|
||||||
milestoneAmounts,
|
milestoneAmounts,
|
||||||
|
@ -180,10 +179,15 @@ export function makeProposalPreviewFromDraft(
|
||||||
proposalId: 0,
|
proposalId: 0,
|
||||||
proposalUrlId: '0-title',
|
proposalUrlId: '0-title',
|
||||||
proposalAddress: '0x0',
|
proposalAddress: '0x0',
|
||||||
|
payoutAddress: '0x0',
|
||||||
dateCreated: Date.now(),
|
dateCreated: Date.now(),
|
||||||
title: draft.title,
|
title: draft.title,
|
||||||
brief: draft.brief,
|
brief: draft.brief,
|
||||||
content: draft.content,
|
content: draft.content,
|
||||||
|
deadlineDuration: 86400 * 60,
|
||||||
|
target: toZat(draft.target),
|
||||||
|
funded: Zat('0'),
|
||||||
|
percentFunded: 0,
|
||||||
stage: 'preview',
|
stage: 'preview',
|
||||||
category: draft.category || PROPOSAL_CATEGORY.DAPP,
|
category: draft.category || PROPOSAL_CATEGORY.DAPP,
|
||||||
team: draft.team,
|
team: draft.team,
|
||||||
|
@ -191,17 +195,13 @@ export function makeProposalPreviewFromDraft(
|
||||||
index: idx,
|
index: idx,
|
||||||
title: m.title,
|
title: m.title,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
amount: toWei(target * (parseInt(m.payoutPercent, 10) / 100), 'ether'),
|
amount: toZat(target * (parseInt(m.payoutPercent, 10) / 100)),
|
||||||
amountAgainstPayout: Wei('0'),
|
|
||||||
percentAgainstPayout: 0,
|
|
||||||
payoutRequestVoteDeadline: Date.now(),
|
|
||||||
dateEstimated: m.dateEstimated,
|
dateEstimated: m.dateEstimated,
|
||||||
immediatePayout: m.immediatePayout,
|
immediatePayout: m.immediatePayout,
|
||||||
isImmediatePayout: 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,
|
||||||
stage: MILESTONE_STATE.WAITING,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
@ -1,7 +1,8 @@
|
||||||
import BN from 'bn.js';
|
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 { UserState } from 'modules/users/reducers';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { toZat } from './units';
|
||||||
|
|
||||||
export function formatUserForPost(user: User) {
|
export function formatUserForPost(user: User) {
|
||||||
return {
|
return {
|
||||||
|
@ -21,8 +22,22 @@ export function formatUserFromGet(user: UserState) {
|
||||||
return user;
|
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.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;
|
return proposal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +63,16 @@ export function extractProposalIdFromUrl(slug: string) {
|
||||||
|
|
||||||
// pre-hydration massage (BNify JSONed BNs)
|
// pre-hydration massage (BNify JSONed BNs)
|
||||||
export function massageSerializedState(state: AppState) {
|
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
|
// users
|
||||||
const bnUserProp = (p: UserProposal) => {
|
const bnUserProp = (p: UserProposal) => {
|
||||||
p.funded = new BN(p.funded, 16);
|
p.funded = new BN(p.funded, 16);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export const DONATION = {
|
export const DONATION = {
|
||||||
ETH: '0x8B0B72F8bDE212991135668922fD5acE557DE6aB',
|
ZCASH_TRANSPARENT: 't1aib2cbwPVrFfrjGGkhWD67imdBet1xDTr',
|
||||||
|
ZCASH_SPROUT: 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen',
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,42 +2,16 @@
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import { stripHexPrefix } from 'utils/formatters';
|
import { stripHexPrefix } from 'utils/formatters';
|
||||||
|
|
||||||
type UnitKey = keyof typeof Units;
|
export const ZCASH_DECIMAL = 8;
|
||||||
type Wei = BN;
|
|
||||||
type TokenValue = BN;
|
|
||||||
type Address = Buffer;
|
|
||||||
type Nonce = BN;
|
|
||||||
type Data = Buffer;
|
|
||||||
|
|
||||||
export const ETH_DECIMAL = 18;
|
|
||||||
|
|
||||||
export const Units = {
|
export const Units = {
|
||||||
wei: '1',
|
zat: '1',
|
||||||
kwei: '1000',
|
zcash: '100000000',
|
||||||
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',
|
|
||||||
};
|
};
|
||||||
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') {
|
if (typeof input === 'string') {
|
||||||
return input.startsWith('0x') ? new BN(stripHexPrefix(input), 16) : new BN(input);
|
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);
|
export const Zat = (input: string | BN): Zat => 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;
|
|
||||||
|
|
||||||
const stripRightZeros = (str: string) => {
|
const stripRightZeros = (str: string) => {
|
||||||
const strippedStr = str.replace(/0+$/, '');
|
const strippedStr = str.replace(/0+$/, '');
|
||||||
return strippedStr === '' ? null : strippedStr;
|
return strippedStr === '' ? null : strippedStr;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseToConvertedUnit = (value: string, decimal: number) => {
|
export const baseToConvertedUnit = (value: string, decimal: number) => {
|
||||||
if (decimal === 0) {
|
if (decimal === 0) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -83,52 +51,14 @@ const convertedToBaseUnit = (value: string, decimal: number) => {
|
||||||
return `${integerPart}${paddedFraction}`;
|
return `${integerPart}${paddedFraction}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromWei = (wei: Wei, unit: UnitKey) => {
|
export const fromZat = (zat: Zat) => {
|
||||||
const decimal = getDecimalFromEtherUnit(unit);
|
return baseToConvertedUnit(zat.toString(), ZCASH_DECIMAL);
|
||||||
return baseToConvertedUnit(wei.toString(), decimal);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toWei = (value: string | number, unitType: number | UnitKey): Wei => {
|
export const toZat = (value: string | number): Zat => {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
let decimal;
|
const zat = convertedToBaseUnit(value, ZCASH_DECIMAL);
|
||||||
if (typeof unitType === 'string') {
|
return Zat(zat);
|
||||||
decimal = getDecimalFromEtherUnit(unitType);
|
|
||||||
} else {
|
|
||||||
decimal = unitType;
|
|
||||||
}
|
|
||||||
const wei = convertedToBaseUnit(value, decimal);
|
|
||||||
return Wei(wei);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromTokenBase = (value: TokenValue, decimal: number) =>
|
export const getDecimalFromUnitKey = (key: UnitKey) => Units[key].length - 1;
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ export function getAmountError(amount: number, max: number = Infinity) {
|
||||||
) {
|
) {
|
||||||
return 'Must be in increments of 0.001';
|
return 'Must be in increments of 0.001';
|
||||||
} else if (amount > max) {
|
} else if (amount > max) {
|
||||||
return `Cannot exceed maximum (${max} ETH)`;
|
return `Cannot exceed maximum (${max} ZEC)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Example extends React.Component<ExampleProps, ExampleState> {
|
||||||
constructor(props: ExampleProps) {
|
constructor(props: ExampleProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ETH,
|
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ZCASH_SPROUT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,21 @@ import Contributors from 'components/Proposal/Contributors';
|
||||||
import 'styles/style.less';
|
import 'styles/style.less';
|
||||||
import 'components/Proposal/style.less';
|
import 'components/Proposal/style.less';
|
||||||
import 'components/Proposal/Governance/style.less';
|
import 'components/Proposal/Governance/style.less';
|
||||||
import { getProposalWithCrowdFund } from './props';
|
import { generateProposal } from './props';
|
||||||
|
|
||||||
const propsNoFunding = getProposalWithCrowdFund({
|
const propsNoFunding = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 0,
|
funded: 0,
|
||||||
});
|
});
|
||||||
const propsHalfFunded = getProposalWithCrowdFund({
|
const propsHalfFunded = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 2.5,
|
funded: 2.5,
|
||||||
});
|
});
|
||||||
const propsFunded = getProposalWithCrowdFund({
|
const propsFunded = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
});
|
});
|
||||||
const propsNotFundedExpired = getProposalWithCrowdFund({
|
const propsNotFundedExpired = generateProposal({
|
||||||
created: Date.now() - 10,
|
created: Date.now() - 10,
|
||||||
deadline: Date.now() - 1,
|
deadline: Date.now() - 1,
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,21 +6,21 @@ import { ProposalCard } from 'components/Proposals/ProposalCard';
|
||||||
import 'styles/style.less';
|
import 'styles/style.less';
|
||||||
import 'components/Proposal/style.less';
|
import 'components/Proposal/style.less';
|
||||||
import 'components/Proposal/Governance/style.less';
|
import 'components/Proposal/Governance/style.less';
|
||||||
import { getProposalWithCrowdFund } from './props';
|
import { generateProposal } from './props';
|
||||||
|
|
||||||
const propsNoFunding = getProposalWithCrowdFund({
|
const propsNoFunding = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 0,
|
funded: 0,
|
||||||
});
|
});
|
||||||
const propsHalfFunded = getProposalWithCrowdFund({
|
const propsHalfFunded = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 2.5,
|
funded: 2.5,
|
||||||
});
|
});
|
||||||
const propsFunded = getProposalWithCrowdFund({
|
const propsFunded = generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
});
|
});
|
||||||
const propsNotFundedExpired = getProposalWithCrowdFund({
|
const propsNotFundedExpired = generateProposal({
|
||||||
created: Date.now() - 1000 * 60 * 60 * 10,
|
created: Date.now() - 1000 * 60 * 60 * 10,
|
||||||
deadline: Date.now() - 1,
|
deadline: Date.now() - 1,
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ const { WAITING, ACTIVE, PAID, REJECTED } = MILESTONE_STATE;
|
||||||
import 'styles/style.less';
|
import 'styles/style.less';
|
||||||
import 'components/Proposal/style.less';
|
import 'components/Proposal/style.less';
|
||||||
import 'components/Proposal/Governance/style.less';
|
import 'components/Proposal/Governance/style.less';
|
||||||
import { getProposalWithCrowdFund } from './props';
|
import { generateProposal } from './props';
|
||||||
|
|
||||||
const msWaiting = { state: WAITING, isPaid: false };
|
const msWaiting = { state: WAITING, isPaid: false };
|
||||||
const msPaid = { state: PAID, isPaid: true };
|
const msPaid = { state: PAID, isPaid: true };
|
||||||
|
@ -22,96 +22,96 @@ const trustee = 'z123';
|
||||||
const contributor = 'z456';
|
const contributor = 'z456';
|
||||||
|
|
||||||
const geometryCases = [...Array(10).keys()].map(i =>
|
const geometryCases = [...Array(10).keys()].map(i =>
|
||||||
getProposalWithCrowdFund({ milestoneCount: i + 1 }),
|
generateProposal({ milestoneCount: i + 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const cases: { [index: string]: any } = {
|
const cases: { [index: string]: any } = {
|
||||||
// trustee - first
|
// trustee - first
|
||||||
['not funded']: getProposalWithCrowdFund({
|
['not funded']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 0,
|
funded: 0,
|
||||||
}),
|
}),
|
||||||
['first - waiting']: getProposalWithCrowdFund({
|
['first - waiting']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
}),
|
}),
|
||||||
['first - not paid']: getProposalWithCrowdFund({
|
['first - not paid']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [
|
milestoneOverrides: [
|
||||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
{ state: PAID, isPaid: false },
|
||||||
msWaiting,
|
msWaiting,
|
||||||
msWaiting,
|
msWaiting,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// trustee - second
|
// trustee - second
|
||||||
['second - waiting']: getProposalWithCrowdFund({
|
['second - waiting']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msWaiting, msWaiting],
|
milestoneOverrides: [msPaid, msWaiting, msWaiting],
|
||||||
}),
|
}),
|
||||||
['second - active']: getProposalWithCrowdFund({
|
['second - active']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msActive, msWaiting],
|
milestoneOverrides: [msPaid, msActive, msWaiting],
|
||||||
}),
|
}),
|
||||||
['second - not paid']: getProposalWithCrowdFund({
|
['second - not paid']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [
|
milestoneOverrides: [
|
||||||
msPaid,
|
msPaid,
|
||||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
{ state: PAID, isPaid: false },
|
||||||
msWaiting,
|
msWaiting,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
['second - no vote']: getProposalWithCrowdFund({
|
['second - no vote']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [
|
milestoneOverrides: [
|
||||||
msPaid,
|
msPaid,
|
||||||
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
|
{ state: ACTIVE, isPaid: false },
|
||||||
msWaiting,
|
msWaiting,
|
||||||
],
|
],
|
||||||
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
||||||
}),
|
}),
|
||||||
['second - rejected']: getProposalWithCrowdFund({
|
['second - rejected']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msRejected, msWaiting],
|
milestoneOverrides: [msPaid, msRejected, msWaiting],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// trustee - third
|
// trustee - third
|
||||||
['final - waiting']: getProposalWithCrowdFund({
|
['final - waiting']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msPaid, msWaiting],
|
milestoneOverrides: [msPaid, msPaid, msWaiting],
|
||||||
}),
|
}),
|
||||||
['final - active']: getProposalWithCrowdFund({
|
['final - active']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msPaid, msActive],
|
milestoneOverrides: [msPaid, msPaid, msActive],
|
||||||
}),
|
}),
|
||||||
['final - not paid']: getProposalWithCrowdFund({
|
['final - not paid']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [
|
milestoneOverrides: [
|
||||||
msPaid,
|
msPaid,
|
||||||
msPaid,
|
msPaid,
|
||||||
{ state: PAID, isPaid: false, payoutRequestVoteDeadline: Date.now() },
|
{ state: PAID, isPaid: false },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
['final - no vote']: getProposalWithCrowdFund({
|
['final - no vote']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [
|
milestoneOverrides: [
|
||||||
msPaid,
|
msPaid,
|
||||||
msPaid,
|
msPaid,
|
||||||
{ state: ACTIVE, isPaid: false, percentAgainstPayout: 33 },
|
{ state: ACTIVE, isPaid: false },
|
||||||
],
|
],
|
||||||
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
contributorOverrides: [{ milestoneNoVotes: [false, true, false] }],
|
||||||
}),
|
}),
|
||||||
['final - rejected']: getProposalWithCrowdFund({
|
['final - rejected']: generateProposal({
|
||||||
amount: 5,
|
amount: 5,
|
||||||
funded: 5,
|
funded: 5,
|
||||||
milestoneOverrides: [msPaid, msPaid, msRejected],
|
milestoneOverrides: [msPaid, msPaid, msRejected],
|
||||||
|
|
|
@ -4,30 +4,30 @@ import BN from 'bn.js';
|
||||||
|
|
||||||
import UnitDisplay from '../client/components/UnitDisplay';
|
import UnitDisplay from '../client/components/UnitDisplay';
|
||||||
|
|
||||||
const oneEth = new BN('1000000000000000000');
|
const oneZEC = new BN('1000000000000000000');
|
||||||
|
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
disp: 'basic',
|
disp: 'basic',
|
||||||
props: { value: oneEth.mul(new BN(25)), symbol: 'ETH' },
|
props: { value: oneZEC.mul(new BN(25)), symbol: 'ZEC' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'fraction',
|
disp: 'fraction',
|
||||||
props: { value: oneEth.div(new BN(3)), symbol: 'ETH' },
|
props: { value: oneZEC.div(new BN(3)), symbol: 'ZEC' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'fraction - displayShortBalance: true',
|
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',
|
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',
|
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: false',
|
||||||
props: {
|
props: {
|
||||||
value: oneEth.div(new BN(2)),
|
value: oneZEC.div(new BN(2)),
|
||||||
symbol: 'ETH',
|
symbol: 'ZEC',
|
||||||
displayShortBalance: 4,
|
displayShortBalance: 4,
|
||||||
displayTrailingZeroes: false,
|
displayTrailingZeroes: false,
|
||||||
},
|
},
|
||||||
|
@ -35,21 +35,21 @@ const cases = [
|
||||||
{
|
{
|
||||||
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: true',
|
disp: 'fraction - displayShortBalance: 4, displayTrailingZeros: true',
|
||||||
props: {
|
props: {
|
||||||
value: oneEth.div(new BN(2)),
|
value: oneZEC.div(new BN(2)),
|
||||||
symbol: 'ETH',
|
symbol: 'ZEC',
|
||||||
displayShortBalance: 4,
|
displayShortBalance: 4,
|
||||||
displayTrailingZeroes: true,
|
displayTrailingZeroes: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'tiny',
|
disp: 'tiny',
|
||||||
props: { value: new BN(1), symbol: 'ETH' },
|
props: { value: new BN(1), symbol: 'ZEC' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'tiny - displayShortBalance: true',
|
disp: 'tiny - displayShortBalance: true',
|
||||||
props: {
|
props: {
|
||||||
value: new BN(1),
|
value: new BN(1),
|
||||||
symbol: 'ETH',
|
symbol: 'ZEC',
|
||||||
displayShortBalance: true,
|
displayShortBalance: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -57,7 +57,7 @@ const cases = [
|
||||||
disp: 'tiny - displayShortBalance: 2',
|
disp: 'tiny - displayShortBalance: 2',
|
||||||
props: {
|
props: {
|
||||||
value: new BN(1),
|
value: new BN(1),
|
||||||
symbol: 'ETH',
|
symbol: 'ZEC',
|
||||||
displayShortBalance: 2,
|
displayShortBalance: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,7 +34,7 @@ const cases: Case[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'ETH Address Only User',
|
disp: 'ZEC Address Only User',
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user,
|
||||||
|
@ -43,7 +43,7 @@ const cases: Case[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disp: 'No Avatar, No ETH Address User',
|
disp: 'No Avatar, No ZEC Address User',
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
|
|
||||||
const oneEth = new BN('1000000000000000000');
|
const oneZec = new BN('100000000');
|
||||||
|
|
||||||
export function getGovernanceMilestonesProps({
|
export function getGovernanceMilestonesProps({
|
||||||
isContributor = true,
|
isContributor = true,
|
||||||
|
@ -26,7 +26,7 @@ export function getGovernanceMilestonesProps({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProposalWithCrowdFund({
|
export function generateProposal({
|
||||||
amount = 10,
|
amount = 10,
|
||||||
funded = 5,
|
funded = 5,
|
||||||
created = Date.now(),
|
created = Date.now(),
|
||||||
|
@ -42,8 +42,9 @@ export function getProposalWithCrowdFund({
|
||||||
contributorOverrides?: Array<Partial<Contributor>>;
|
contributorOverrides?: Array<Partial<Contributor>>;
|
||||||
milestoneCount?: number;
|
milestoneCount?: number;
|
||||||
}) {
|
}) {
|
||||||
const amountBn = oneEth.mul(new BN(amount));
|
const amountBn = oneZec.mul(new BN(amount));
|
||||||
const fundedBn = oneEth.mul(new BN(funded));
|
const fundedBn = oneZec.mul(new BN(funded));
|
||||||
|
const percentFunded = amount / funded;
|
||||||
|
|
||||||
let contributors = [
|
let contributors = [
|
||||||
{
|
{
|
||||||
|
@ -96,34 +97,27 @@ export function getProposalWithCrowdFund({
|
||||||
return ts.slice(rand).join('');
|
return ts.slice(rand).join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const genMilestone = (overrides: Partial<ProposalMilestone> = {}) => {
|
const genMilestone = (overrides: Partial<ProposalMilestone> = {}): ProposalMilestone => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
if (overrides.index) {
|
if (overrides.index) {
|
||||||
const estimate = new Date(now.setMonth(now.getMonth() + overrides.index));
|
const estimate = new Date(now.setMonth(now.getMonth() + overrides.index));
|
||||||
overrides.dateEstimated = estimate.toISOString();
|
overrides.dateEstimated = estimate.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign(
|
const defaults: ProposalMilestone = {
|
||||||
{
|
title: 'Milestone A',
|
||||||
title: 'Milestone A',
|
content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||||
body: `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',
|
||||||
content: '',
|
immediatePayout: true,
|
||||||
dateEstimated: '2018-10-01T00:00:00+00:00',
|
index: 0,
|
||||||
immediatePayout: true,
|
state: MILESTONE_STATE.WAITING,
|
||||||
index: 0,
|
amount: amountBn,
|
||||||
state: MILESTONE_STATE.WAITING,
|
isPaid: false,
|
||||||
amount: amountBn,
|
isImmediatePayout: true,
|
||||||
amountAgainstPayout: new BN(0),
|
payoutPercent: '33',
|
||||||
percentAgainstPayout: 0,
|
};
|
||||||
payoutRequestVoteDeadline: 0,
|
return { ...defaults, ...overrides };
|
||||||
isPaid: false,
|
|
||||||
isImmediatePayout: true,
|
|
||||||
payoutPercent: '33',
|
|
||||||
stage: 'NOT_REQUESTED',
|
|
||||||
},
|
|
||||||
overrides,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const milestones = [...Array(milestoneCount).keys()].map(i => {
|
const milestones = [...Array(milestoneCount).keys()].map(i => {
|
||||||
|
@ -148,7 +142,12 @@ export function getProposalWithCrowdFund({
|
||||||
proposalId: 12345,
|
proposalId: 12345,
|
||||||
proposalUrlId: '12345-crowdfund-title',
|
proposalUrlId: '12345-crowdfund-title',
|
||||||
proposalAddress: '0x033fDc6C01DC2385118C7bAAB88093e22B8F0710',
|
proposalAddress: '0x033fDc6C01DC2385118C7bAAB88093e22B8F0710',
|
||||||
|
payoutAddress: 'z123',
|
||||||
dateCreated: created / 1000,
|
dateCreated: created / 1000,
|
||||||
|
deadlineDuration: 86400 * 60,
|
||||||
|
target: amountBn,
|
||||||
|
funded: fundedBn,
|
||||||
|
percentFunded,
|
||||||
title: 'Crowdfund Title',
|
title: 'Crowdfund Title',
|
||||||
brief: 'A cool test crowdfund',
|
brief: 'A cool test crowdfund',
|
||||||
content: 'body',
|
content: 'body',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Wei } from 'utils/units';
|
import { Zat } from 'utils/units';
|
||||||
|
|
||||||
export enum MILESTONE_STATE {
|
export enum MILESTONE_STATE {
|
||||||
WAITING = 'WAITING',
|
WAITING = 'WAITING',
|
||||||
|
@ -10,10 +10,7 @@ export enum MILESTONE_STATE {
|
||||||
export interface Milestone {
|
export interface Milestone {
|
||||||
index: number;
|
index: number;
|
||||||
state: MILESTONE_STATE;
|
state: MILESTONE_STATE;
|
||||||
amount: Wei;
|
amount: Zat;
|
||||||
amountAgainstPayout: Wei;
|
|
||||||
percentAgainstPayout: number;
|
|
||||||
payoutRequestVoteDeadline: number;
|
|
||||||
isPaid: boolean;
|
isPaid: boolean;
|
||||||
isImmediatePayout: boolean;
|
isImmediatePayout: boolean;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +20,6 @@ export interface ProposalMilestone extends Milestone {
|
||||||
immediatePayout: boolean;
|
immediatePayout: boolean;
|
||||||
dateEstimated: string;
|
dateEstimated: string;
|
||||||
payoutPercent: string;
|
payoutPercent: string;
|
||||||
stage: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import {
|
import {
|
||||||
CreateMilestone,
|
CreateMilestone,
|
||||||
ProposalMilestone,
|
|
||||||
Update,
|
Update,
|
||||||
User,
|
User,
|
||||||
Comment,
|
Comment,
|
||||||
} from 'types';
|
} from 'types';
|
||||||
|
import { ProposalMilestone } from './milestone';
|
||||||
|
|
||||||
export interface TeamInvite {
|
export interface TeamInvite {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -17,7 +18,7 @@ export interface TeamInvite {
|
||||||
|
|
||||||
export interface Contributor {
|
export interface Contributor {
|
||||||
address: string;
|
address: string;
|
||||||
contributionAmount: Wei;
|
contributionAmount: Zat;
|
||||||
refundVote: boolean;
|
refundVote: boolean;
|
||||||
refunded: boolean;
|
refunded: boolean;
|
||||||
proportionalContribution: string;
|
proportionalContribution: string;
|
||||||
|
@ -40,18 +41,14 @@ export interface ProposalDraft {
|
||||||
invites: TeamInvite[];
|
invites: TeamInvite[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Proposal {
|
|
||||||
proposalId: number;
|
export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
|
||||||
proposalAddress: string;
|
proposalAddress: string;
|
||||||
proposalUrlId: string;
|
proposalUrlId: string;
|
||||||
dateCreated: number;
|
target: BN;
|
||||||
title: string;
|
funded: BN;
|
||||||
brief: string;
|
percentFunded: number;
|
||||||
content: string;
|
|
||||||
stage: string;
|
|
||||||
category: PROPOSAL_CATEGORY;
|
|
||||||
milestones: ProposalMilestone[];
|
milestones: ProposalMilestone[];
|
||||||
team: User[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TeamInviteWithProposal extends TeamInvite {
|
export interface TeamInviteWithProposal extends TeamInvite {
|
||||||
|
@ -74,6 +71,6 @@ export interface UserProposal {
|
||||||
title: string;
|
title: string;
|
||||||
brief: string;
|
brief: string;
|
||||||
team: User[];
|
team: User[];
|
||||||
funded: Wei;
|
funded: BN;
|
||||||
target: Wei;
|
target: BN;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue