Merge pull request #49 from grant-project/show-contribution-amounts-frontend

Show Proposal Contribution Amounts
This commit is contained in:
William O'Beirne 2018-09-17 11:59:27 -04:00 committed by GitHub
commit 210b656939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 90 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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