Merge pull request #49 from grant-project/show-contribution-amounts-frontend
Show Proposal Contribution Amounts
This commit is contained in:
commit
210b656939
|
@ -12,11 +12,11 @@ import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
import TrusteeFields from './TrusteeFields';
|
import TrusteeFields from './TrusteeFields';
|
||||||
import MilestoneFields, { Milestone } from './MilestoneFields';
|
import MilestoneFields, { Milestone } from './MilestoneFields';
|
||||||
import CreateSuccess from './CreateSuccess';
|
import CreateSuccess from './CreateSuccess';
|
||||||
import { computePercentage } from 'utils/helpers';
|
|
||||||
import { getAmountError } from 'utils/validators';
|
import { getAmountError } from 'utils/validators';
|
||||||
import MarkdownEditor from 'components/MarkdownEditor';
|
import MarkdownEditor from 'components/MarkdownEditor';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
|
import { Wei, toWei } from 'utils/units';
|
||||||
|
import BN from 'bn.js';
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
crowdFundLoading: AppState['web3']['crowdFundLoading'];
|
crowdFundLoading: AppState['web3']['crowdFundLoading'];
|
||||||
crowdFundError: AppState['web3']['crowdFundError'];
|
crowdFundError: AppState['web3']['crowdFundError'];
|
||||||
|
@ -74,8 +74,8 @@ const DEFAULT_STATE: State = {
|
||||||
milestoneDeadline: 60 * 60 * 24 * 7,
|
milestoneDeadline: 60 * 60 * 24 * 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
function milestoneToMilestoneAmount(milestone: Milestone, raiseGoal: number) {
|
function milestoneToMilestoneAmount(milestone: Milestone, raiseGoal: Wei) {
|
||||||
return computePercentage(raiseGoal, milestone.payoutPercent);
|
return raiseGoal.divn(100).mul(new BN(milestone.payoutPercent));
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateProposal extends React.Component<Props, State> {
|
class CreateProposal extends React.Component<Props, State> {
|
||||||
|
@ -159,7 +159,7 @@ class CreateProposal extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
createCrowdFund = async () => {
|
createCrowdFund = async () => {
|
||||||
const { contract, createCrowdFund, web3 } = this.props;
|
const { contract, createCrowdFund } = this.props;
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
proposalBody,
|
proposalBody,
|
||||||
|
@ -173,10 +173,9 @@ class CreateProposal extends React.Component<Props, State> {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const backendData = { content: proposalBody, title, category };
|
const backendData = { content: proposalBody, title, category };
|
||||||
|
const targetInWei = toWei(amountToRaise, 'ether');
|
||||||
const targetInWei = web3.utils.toWei(String(amountToRaise), 'ether');
|
|
||||||
const milestoneAmounts = milestones.map(milestone =>
|
const milestoneAmounts = milestones.map(milestone =>
|
||||||
milestoneToMilestoneAmount(milestone, targetInWei),
|
Wei(milestoneToMilestoneAmount(milestone, targetInWei)),
|
||||||
);
|
);
|
||||||
const immediateFirstMilestonePayout = milestones[0].immediatePayout;
|
const immediateFirstMilestonePayout = milestones[0].immediatePayout;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import { CrowdFund } from 'modules/proposals/reducers';
|
||||||
|
import UserRow from 'components/UserRow';
|
||||||
|
import * as ProposalStyled from '../styled';
|
||||||
|
import Placeholder from 'components/Placeholder';
|
||||||
|
import UnitDisplay from 'components/UnitDisplay';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
crowdFund: CrowdFund;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContributorsBlock = ({ crowdFund }: Props) => {
|
||||||
|
let content;
|
||||||
|
if (crowdFund) {
|
||||||
|
if (crowdFund.contributors.length) {
|
||||||
|
content = crowdFund.contributors.map(contributor => (
|
||||||
|
<UserRow
|
||||||
|
key={contributor.address}
|
||||||
|
address={contributor.address}
|
||||||
|
secondary={<UnitDisplay value={contributor.contributionAmount} symbol="ETH" />}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<Placeholder
|
||||||
|
style={{ minHeight: '220px' }}
|
||||||
|
title="No contributors found"
|
||||||
|
subtitle={`
|
||||||
|
It appears that your campaign hasn't yet been funded.
|
||||||
|
Check back later once you've received at least one contribution!
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProposalStyled.SideBlock>
|
||||||
|
{crowdFund.contributors.length ? (
|
||||||
|
<>
|
||||||
|
<ProposalStyled.BlockTitle>Contributors</ProposalStyled.BlockTitle>
|
||||||
|
<ProposalStyled.Block>{content}</ProposalStyled.Block>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)}
|
||||||
|
</ProposalStyled.SideBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContributorsBlock;
|
|
@ -15,6 +15,7 @@ import Milestones from './Milestones';
|
||||||
import CommentsTab from './Comments';
|
import CommentsTab from './Comments';
|
||||||
import UpdatesTab from './Updates';
|
import UpdatesTab from './Updates';
|
||||||
import GovernanceTab from './Governance';
|
import GovernanceTab from './Governance';
|
||||||
|
import ContributorsTab from './Contributors';
|
||||||
// import CommunityTab from './Community';
|
// import CommunityTab from './Community';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
import { withRouter } from 'next/router';
|
import { withRouter } from 'next/router';
|
||||||
|
@ -113,9 +114,12 @@ class ProposalDetail extends React.Component<Props, State> {
|
||||||
<div style={{ marginTop: '1.5rem' }} />
|
<div style={{ marginTop: '1.5rem' }} />
|
||||||
<UpdatesTab proposalId={proposal.proposalId} />
|
<UpdatesTab proposalId={proposal.proposalId} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="Governance" key="governanc">
|
<Tabs.TabPane tab="Governance" key="governance">
|
||||||
<GovernanceTab proposal={proposal} />
|
<GovernanceTab proposal={proposal} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Contributors" key="contributors">
|
||||||
|
<ContributorsTab crowdFund={proposal.crowdFund} />
|
||||||
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)}
|
)}
|
||||||
</Styled.Container>
|
</Styled.Container>
|
||||||
|
|
|
@ -5,11 +5,10 @@ import * as Styled from './styled';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
address: string;
|
address: string;
|
||||||
|
secondary?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - don't hardcode monero image
|
const UserRow = ({ address, secondary }: Props) => (
|
||||||
|
|
||||||
const UserRow = ({ address }: Props) => (
|
|
||||||
<Styled.Container>
|
<Styled.Container>
|
||||||
<Styled.Avatar>
|
<Styled.Avatar>
|
||||||
<Identicon address={address} />
|
<Identicon address={address} />
|
||||||
|
@ -18,7 +17,7 @@ const UserRow = ({ address }: Props) => (
|
||||||
<Styled.InfoMain>
|
<Styled.InfoMain>
|
||||||
<ShortAddress address={address} />
|
<ShortAddress address={address} />
|
||||||
</Styled.InfoMain>
|
</Styled.InfoMain>
|
||||||
<Styled.InfoSecondary>{/* user.title */}</Styled.InfoSecondary>
|
{secondary && <Styled.InfoSecondary>{secondary}</Styled.InfoSecondary>}
|
||||||
</Styled.Info>
|
</Styled.Info>
|
||||||
</Styled.Container>
|
</Styled.Container>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,7 @@ export interface User {
|
||||||
|
|
||||||
export interface Contributor {
|
export interface Contributor {
|
||||||
address: string;
|
address: string;
|
||||||
contributionAmount: string;
|
contributionAmount: Wei;
|
||||||
refundVote: boolean;
|
refundVote: boolean;
|
||||||
refunded: boolean;
|
refunded: boolean;
|
||||||
proportionalContribution: string;
|
proportionalContribution: string;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { sleep } from 'utils/helpers';
|
||||||
import { fetchProposal, fetchProposals } from 'modules/proposals/actions';
|
import { fetchProposal, fetchProposals } from 'modules/proposals/actions';
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Wei } from 'utils/units';
|
||||||
|
|
||||||
type GetState = () => AppState;
|
type GetState = () => AppState;
|
||||||
|
|
||||||
|
@ -97,10 +98,10 @@ interface MilestoneData {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProposalContractData {
|
interface ProposalContractData {
|
||||||
ethAmount: number | string; // TODO: BigNumber
|
ethAmount: Wei;
|
||||||
payOutAddress: string;
|
payOutAddress: string;
|
||||||
trusteesAddresses: string[];
|
trusteesAddresses: string[];
|
||||||
milestoneAmounts: number[] | string[]; // TODO: BigNumber
|
milestoneAmounts: Wei[];
|
||||||
milestones: MilestoneData[];
|
milestones: MilestoneData[];
|
||||||
durationInMinutes: number;
|
durationInMinutes: number;
|
||||||
milestoneVotingPeriodInMinutes: number;
|
milestoneVotingPeriodInMinutes: number;
|
||||||
|
|
|
@ -5,7 +5,3 @@ export function isNumeric(n: any) {
|
||||||
export async function sleep(ms: number) {
|
export async function sleep(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computePercentage(num: number, percent: number) {
|
|
||||||
return (num / 100) * percent;
|
|
||||||
}
|
|
||||||
|
|
|
@ -93,7 +93,13 @@ const fromWei = (wei: Wei, unit: UnitKey) => {
|
||||||
return baseToConvertedUnit(wei.toString(), decimal);
|
return baseToConvertedUnit(wei.toString(), decimal);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toWei = (value: string, decimal: number): Wei => {
|
const toWei = (value: string, unitType: number | UnitKey): Wei => {
|
||||||
|
let decimal;
|
||||||
|
if (typeof unitType === 'number') {
|
||||||
|
decimal = unitType;
|
||||||
|
} else if (typeof unitType === 'string') {
|
||||||
|
decimal = getDecimalFromEtherUnit(unitType);
|
||||||
|
}
|
||||||
const wei = convertedToBaseUnit(value, decimal);
|
const wei = convertedToBaseUnit(value, decimal);
|
||||||
return Wei(wei);
|
return Wei(wei);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Web3 from 'web3';
|
||||||
import { CrowdFund, Milestone, MILESTONE_STATE } from 'modules/proposals/reducers';
|
import { CrowdFund, Milestone, MILESTONE_STATE } from 'modules/proposals/reducers';
|
||||||
import { collectArrayElements } from 'utils/web3Utils';
|
import { collectArrayElements } from 'utils/web3Utils';
|
||||||
import { Wei } from 'utils/units';
|
import { Wei } from 'utils/units';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
|
||||||
export async function getCrowdFundState(
|
export async function getCrowdFundState(
|
||||||
crowdFundContract: any,
|
crowdFundContract: any,
|
||||||
|
@ -26,7 +27,10 @@ export async function getCrowdFundState(
|
||||||
? 100
|
? 100
|
||||||
: balance.divn(100).isZero()
|
: balance.divn(100).isZero()
|
||||||
? 0
|
? 0
|
||||||
: target.div(balance.divn(100)).toNumber();
|
: balance
|
||||||
|
.mul(new BN(100))
|
||||||
|
.div(target)
|
||||||
|
.toNumber();
|
||||||
const amountVotingForRefund = isRaiseGoalReached
|
const amountVotingForRefund = isRaiseGoalReached
|
||||||
? Wei(await crowdFundContract.methods.amountVotingForRefund().call({ from: account }))
|
? Wei(await crowdFundContract.methods.amountVotingForRefund().call({ from: account }))
|
||||||
: Wei('0');
|
: Wei('0');
|
||||||
|
@ -103,6 +107,11 @@ export async function getCrowdFundState(
|
||||||
.call({ form: account }),
|
.call({ form: account }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
contributor.contributionAmount = Wei(
|
||||||
|
await crowdFundContract.methods
|
||||||
|
.getContributorContributionAmount(addr)
|
||||||
|
.call({ from: account }),
|
||||||
|
);
|
||||||
return contributor;
|
return contributor;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue