Implement the vote index page with routing to voting pages

This commit is contained in:
fragosti 2019-06-28 16:16:55 -07:00 committed by Jacob Evans
parent dd20d8d6de
commit f388751a97
No known key found for this signature in database
GPG Key ID: 2036DA2ADDFB0842
13 changed files with 541 additions and 129 deletions

View File

@ -23,6 +23,7 @@ export interface ButtonInterface {
hasIcon?: boolean | string;
isInline?: boolean;
padding?: string;
fontSize?: string;
href?: string;
type?: string;
target?: string;
@ -78,7 +79,7 @@ const ButtonBase = styled.button<ButtonInterface>`
!props.isNoPadding && !props.isWithArrow && ((!!props.padding && props.padding) || '18px 30px')};
white-space: ${props => props.isWithArrow && 'nowrap'};
text-align: center;
font-size: ${props => (props.isWithArrow ? '20px' : '18px')};
font-size: ${props => (props.fontSize ? props.fontSize : props.isWithArrow ? '20px' : '18px')};
text-decoration: none;
cursor: pointer;
outline: none;

View File

@ -1,6 +1,8 @@
import * as React from 'react';
import styled from 'styled-components';
import { opacify } from 'polished';
export interface WrapProps {
bgColor?: string;
id?: string;
@ -27,7 +29,10 @@ export interface SectionProps extends WrapProps {
isFullWidth?: boolean;
isFlex?: boolean;
padding?: string;
margin?: string;
paddingMobile?: string;
hasBorder?: boolean;
hasHover?: boolean;
flexBreakpoint?: string;
maxWidth?: string;
bgColor?: 'dark' | 'light' | string;
@ -91,12 +96,18 @@ export const WrapSticky = styled.div<WrapProps>`
const SectionBase = styled.section<SectionProps>`
width: ${props => !props.isFullWidth && 'calc(100% - 60px)'};
max-width: 1500px;
margin: 0 auto;
cursor: ${props => props.hasHover && 'pointer'};
border: ${props => props.hasBorder && `1px solid ${props.theme.lightBgColor}`};
margin: ${props => (props.margin ? props.margin : '0 auto')};
padding: ${props => props.isPadded && (props.padding || '120px 0')};
background-color: ${props => props.theme[`${props.bgColor}BgColor`] || props.bgColor};
position: relative;
overflow: ${props => !props.isFullWidth && 'hidden'};
&:hover {
background-color: ${props => props.hasHover && opacify(0.2, props.theme[`lightBgColor`])};
}
@media (max-width: 768px) {
padding: ${props => props.isPadded && (props.paddingMobile || '40px 0')};
}

View File

@ -16,6 +16,7 @@ interface HeadingProps extends BaseTextInterface {
isFlex?: boolean;
isNoMargin?: boolean;
isMuted?: boolean | number;
isInline?: boolean;
marginBottom?: string;
color?: string;
}
@ -30,7 +31,7 @@ interface ParagraphProps extends BaseTextInterface {
const StyledHeading = styled.h1<HeadingProps>`
max-width: ${props => props.maxWidth};
color: ${props => props.color || props.theme.textColor};
display: ${props => props.isFlex && `inline-flex`};
display: ${props => (props.isFlex ? `inline-flex` : props.isInline ? 'inline' : undefined)};
align-items: center;
justify-content: ${props => props.isFlex && `space-between`};
font-size: ${props =>

View File

@ -26,6 +26,7 @@ import { Explore } from 'ts/pages/explore';
import { NextEcosystem } from 'ts/pages/ecosystem';
import { Extensions } from 'ts/pages/extensions';
import { Governance } from 'ts/pages/governance/governance';
import { VoteIndex } from 'ts/pages/governance/vote_index';
import { Next0xInstant } from 'ts/pages/instant';
import { NextLanding } from 'ts/pages/landing';
import { NextLaunchKit } from 'ts/pages/launch_kit';
@ -120,7 +121,8 @@ render(
<Route exact={true} path={WebsitePaths.Instant} component={Next0xInstant as any} />
<Route exact={true} path={WebsitePaths.LaunchKit} component={NextLaunchKit as any} />
<Route exact={true} path={WebsitePaths.Ecosystem} component={NextEcosystem as any} />
<Route exact={true} path={WebsitePaths.Vote} component={Governance as any} />
<Route exact={true} path={`${WebsitePaths.Vote}/:zeip`} component={Governance as any} />
<Route exact={true} path={WebsitePaths.Vote} component={VoteIndex as any} />
<Route exact={true} path={WebsitePaths.Extensions} component={Extensions as any} />
<Route
exact={true}

View File

@ -5,7 +5,7 @@ import * as React from 'react';
import { Paragraph } from 'ts/components/text';
interface Props {
deadline: number;
deadline: moment.Moment;
}
interface TimeStructure {
@ -23,7 +23,7 @@ const now = moment();
export const Countdown: React.StatelessComponent<Props> = ({ deadline }) => {
const pstOffset = '-0800';
const time = moment(deadline, 'X').utcOffset(pstOffset);
const time = deadline.utcOffset(pstOffset);
const isPassed = time.isBefore(now);
const voteTextPrefix = isPassed ? `Voting ended: ` : `Vote ends: `;
const timeText = !isPassed ? `${getRelativeTime(time)}` : '';

View File

@ -0,0 +1,181 @@
import { BigNumber } from '@0x/utils';
import * as moment from 'moment';
import { TallyInterface, VoteOutcome, ZeipId } from 'ts/types';
export interface ProposalLink {
text: string;
url: string;
}
export interface ProposalProperty {
title: string;
summary: string;
rating: number;
links: ProposalLink[];
}
export interface Proposal {
zeipId: ZeipId;
title: string;
summary: string;
url: string;
voteStartDate: moment.Moment;
voteEndDate: moment.Moment;
outcome?: VoteOutcome;
benefit: ProposalProperty;
risks: ProposalProperty;
}
export interface Proposals {
[id: number]: Proposal;
}
export const proposals: Proposals = {
23: {
zeipId: 23,
title: 'Trade Bundles of Assets',
summary: `This ZEIP introduces the MultiAssetProxy, which adds support for trading arbitrary bundles of assets to 0x protocol. Historically, only a single asset could be traded per each side of a trade. With the introduction of the MultiAssetProxy, users will be able to trade multiple ERC721 assets or even mix ERC721 and ERC20 assets within a single order.`,
url: 'https://blog.0xproject.com/zeip-23-trade-bundles-of-assets-fe69eb3ed960',
voteStartDate: moment(1551042800, 'X'),
voteEndDate: moment(1551142800, 'X'),
outcome: 'accepted',
benefit: {
title: 'Benefit',
summary: `Supporting trades for bundles of assets has been one of the most commonly requested features since the launch of 0x v2. The idea for this feature originated from discussions with gaming and NFT related projects. However, this upgrade also provides utility to relayers for prediction markets or baskets of tokens. The MultiAssetProxy will enable brand new ways of trading.`,
rating: 3,
links: [
{
text: 'Technical detail',
url: 'https://github.com/0xProject/ZEIPs/issues/23',
},
],
},
risks: {
title: 'Risk',
summary: `While the MultiAssetProxys code is relatively straightforward and has successfully undergone a full third-party audit, a bug within the code could result in the loss of user funds. Deploying the MultiAssetProxy is a hot upgrade that requires modifying the state of existing contracts within 0x protocol. The contracts being modified contain allowances to many users tokens. We encourage the community to verify the code, as well as the state changes.`,
rating: 2,
links: [
{
text: 'View Code',
url:
'https://github.com/0xProject/0x-monorepo/blob/development/contracts/asset-proxy/contracts/src/MultiAssetProxy.sol#L25',
},
{
text: 'View Audit',
url: 'https://github.com/ConsenSys/0x-audit-report-2018-12',
},
],
},
},
39: {
zeipId: 39,
title: 'StaticCallAssetProxy',
summary: `This ZEIP introduces the StaticCallAssetProxy, which adds support for trading arbitrary bundles of assets to 0x protocol. Historically, only a single asset could be traded per each side of a trade. With the introduction of the MultiAssetProxy, users will be able to trade multiple ERC721 assets or even mix ERC721 and ERC20 assets within a single order.`,
url: 'https://blog.0xproject.com/zeip-23-trade-bundles-of-assets-fe69eb3ed960',
voteStartDate: moment().add(2, 'days'),
voteEndDate: moment().add(3, 'days'),
benefit: {
title: 'Benefit',
summary: `Supporting trades for bundles of assets has been one of the most commonly requested features since the launch of 0x v2. The idea for this feature originated from discussions with gaming and NFT related projects. However, this upgrade also provides utility to relayers for prediction markets or baskets of tokens. The MultiAssetProxy will enable brand new ways of trading.`,
rating: 3,
links: [
{
text: 'Technical detail',
url: 'https://github.com/0xProject/ZEIPs/issues/23',
},
],
},
risks: {
title: 'Risk',
summary: `While the MultiAssetProxys code is relatively straightforward and has successfully undergone a full third-party audit, a bug within the code could result in the loss of user funds. Deploying the MultiAssetProxy is a hot upgrade that requires modifying the state of existing contracts within 0x protocol. The contracts being modified contain allowances to many users tokens. We encourage the community to verify the code, as well as the state changes.`,
rating: 2,
links: [
{
text: 'View Code',
url:
'https://github.com/0xProject/0x-monorepo/blob/development/contracts/asset-proxy/contracts/src/MultiAssetProxy.sol#L25',
},
{
text: 'View Audit',
url: 'https://github.com/ConsenSys/0x-audit-report-2018-12',
},
],
},
},
24: {
zeipId: 24,
title: 'Support ERC-1155 MultiToken Standard',
summary: `This ZEIP introduces the StaticCallAssetProxy, which adds support for trading arbitrary bundles of assets to 0x protocol. Historically, only a single asset could be traded per each side of a trade. With the introduction of the MultiAssetProxy, users will be able to trade multiple ERC721 assets or even mix ERC721 and ERC20 assets within a single order.`,
url: 'https://blog.0xproject.com/zeip-23-trade-bundles-of-assets-fe69eb3ed960',
voteStartDate: moment().subtract(2, 'days'),
voteEndDate: moment().add(3, 'days'),
benefit: {
title: 'Benefit',
summary: `Supporting trades for bundles of assets has been one of the most commonly requested features since the launch of 0x v2. The idea for this feature originated from discussions with gaming and NFT related projects. However, this upgrade also provides utility to relayers for prediction markets or baskets of tokens. The MultiAssetProxy will enable brand new ways of trading.`,
rating: 3,
links: [
{
text: 'Technical detail',
url: 'https://github.com/0xProject/ZEIPs/issues/23',
},
],
},
risks: {
title: 'Risk',
summary: `While the MultiAssetProxys code is relatively straightforward and has successfully undergone a full third-party audit, a bug within the code could result in the loss of user funds. Deploying the MultiAssetProxy is a hot upgrade that requires modifying the state of existing contracts within 0x protocol. The contracts being modified contain allowances to many users tokens. We encourage the community to verify the code, as well as the state changes.`,
rating: 2,
links: [
{
text: 'View Code',
url:
'https://github.com/0xProject/0x-monorepo/blob/development/contracts/asset-proxy/contracts/src/MultiAssetProxy.sol#L25',
},
{
text: 'View Audit',
url: 'https://github.com/ConsenSys/0x-audit-report-2018-12',
},
],
},
},
25: {
zeipId: 25,
title: 'Support ERC-1155 MultiToken Standard',
summary: `This ZEIP introduces the StaticCallAssetProxy, which adds support for trading arbitrary bundles of assets to 0x protocol. Historically, only a single asset could be traded per each side of a trade. With the introduction of the MultiAssetProxy, users will be able to trade multiple ERC721 assets or even mix ERC721 and ERC20 assets within a single order.`,
url: 'https://blog.0xproject.com/zeip-23-trade-bundles-of-assets-fe69eb3ed960',
voteStartDate: moment().subtract(2, 'days'),
voteEndDate: moment().add(3, 'days'),
benefit: {
title: 'Benefit',
summary: `Supporting trades for bundles of assets has been one of the most commonly requested features since the launch of 0x v2. The idea for this feature originated from discussions with gaming and NFT related projects. However, this upgrade also provides utility to relayers for prediction markets or baskets of tokens. The MultiAssetProxy will enable brand new ways of trading.`,
rating: 3,
links: [
{
text: 'Technical detail',
url: 'https://github.com/0xProject/ZEIPs/issues/23',
},
],
},
risks: {
title: 'Risk',
summary: `While the MultiAssetProxys code is relatively straightforward and has successfully undergone a full third-party audit, a bug within the code could result in the loss of user funds. Deploying the MultiAssetProxy is a hot upgrade that requires modifying the state of existing contracts within 0x protocol. The contracts being modified contain allowances to many users tokens. We encourage the community to verify the code, as well as the state changes.`,
rating: 2,
links: [
{
text: 'View Code',
url:
'https://github.com/0xProject/0x-monorepo/blob/development/contracts/asset-proxy/contracts/src/MultiAssetProxy.sol#L25',
},
{
text: 'View Audit',
url: 'https://github.com/ConsenSys/0x-audit-report-2018-12',
},
],
},
},
};
export const ZERO_TALLY: TallyInterface = {
yes: new BigNumber(0),
no: new BigNumber(0),
};

View File

@ -1,6 +1,7 @@
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import styled from 'styled-components';
import { Banner } from 'ts/components/banner';
@ -11,11 +12,13 @@ import { Column, FlexWrap, Section } from 'ts/components/newLayout';
import { SiteWrap } from 'ts/components/siteWrap';
import { Heading, Paragraph } from 'ts/components/text';
import { Countdown } from 'ts/pages/governance/countdown';
import { Proposal, proposals } from 'ts/pages/governance/data';
import { ModalVote } from 'ts/pages/governance/modal_vote';
import { RatingBar } from 'ts/pages/governance/rating_bar';
import { VoteInfo, VoteValue } from 'ts/pages/governance/vote_form';
import { VoteStats } from 'ts/pages/governance/vote_stats';
import { colors } from 'ts/style/colors';
import { TallyInterface } from 'ts/types';
import { configs } from 'ts/utils/configs';
import { documentConstants } from 'ts/utils/document_meta_constants';
import { utils } from 'ts/utils/utils';
@ -24,15 +27,6 @@ interface LabelInterface {
[key: number]: string;
}
export interface TallyInterface {
zeip?: string;
yes?: BigNumber;
no?: BigNumber;
blockNumber?: string;
totalVotes?: string;
totalBalance?: BigNumber;
}
interface State {
isContactModalOpen: boolean;
isVoteModalOpen: boolean;
@ -54,54 +48,20 @@ const riskLabels: LabelInterface = {
3: 'High Risk',
};
const proposalData = {
zeipId: 23,
title: 'ZEIP-23: Trade Bundles of Assets',
summary: `This ZEIP introduces the MultiAssetProxy, which adds support for trading arbitrary bundles of assets to 0x protocol. Historically, only a single asset could be traded per each side of a trade. With the introduction of the MultiAssetProxy, users will be able to trade multiple ERC721 assets or even mix ERC721 and ERC20 assets within a single order.`,
url: 'https://blog.0xproject.com/zeip-23-trade-bundles-of-assets-fe69eb3ed960',
votingDeadline: 1551142800,
benefit: {
title: 'Benefit',
summary: `Supporting trades for bundles of assets has been one of the most commonly requested features since the launch of 0x v2. The idea for this feature originated from discussions with gaming and NFT related projects. However, this upgrade also provides utility to relayers for prediction markets or baskets of tokens. The MultiAssetProxy will enable brand new ways of trading.`,
rating: 3,
links: [
{
text: 'Technical detail',
url: 'https://github.com/0xProject/ZEIPs/issues/23',
},
],
},
risks: {
title: 'Risk',
summary: `While the MultiAssetProxys code is relatively straightforward and has successfully undergone a full third-party audit, a bug within the code could result in the loss of user funds. Deploying the MultiAssetProxy is a hot upgrade that requires modifying the state of existing contracts within 0x protocol. The contracts being modified contain allowances to many users tokens. We encourage the community to verify the code, as well as the state changes.`,
rating: 2,
links: [
{
text: 'View Code',
url:
'https://github.com/0xProject/0x-monorepo/blob/development/contracts/asset-proxy/contracts/src/MultiAssetProxy.sol#L25',
},
{
text: 'View Audit',
url: 'https://github.com/ConsenSys/0x-audit-report-2018-12',
},
],
},
};
export class Governance extends React.Component {
export class Governance extends React.Component<RouteComponentProps<any>> {
public state: State = {
isContactModalOpen: false,
isVoteModalOpen: false,
isWalletConnected: false,
isVoteReceived: false,
providerName: 'Metamask',
tally: {
totalBalance: new BigNumber(0),
yes: new BigNumber(0),
no: new BigNumber(0),
},
};
private readonly _proposalData: Proposal;
constructor(props: RouteComponentProps<any>) {
super(props);
const zeipId = parseInt(props.match.params.zeip.split('-')[1], 10);
this._proposalData = proposals[zeipId];
}
public componentDidMount(): void {
// tslint:disable:no-floating-promises
this._fetchVoteStatusAsync();
@ -113,12 +73,12 @@ export class Governance extends React.Component {
<DocumentTitle {...documentConstants.VOTE} />
<Section maxWidth="1170px" isFlex={true}>
<Column width="55%" maxWidth="560px">
<Countdown deadline={proposalData.votingDeadline} />
<Heading size="medium">{proposalData.title}</Heading>
<Paragraph>{proposalData.summary}</Paragraph>
<Countdown deadline={this._proposalData.voteEndDate} />
<Heading size="medium">{this._proposalData.title}</Heading>
<Paragraph>{this._proposalData.summary}</Paragraph>
<Button
href={proposalData.url}
target={proposalData.url !== undefined ? '_blank' : undefined}
href={this._proposalData.url}
target={this._proposalData.url !== undefined ? '_blank' : undefined}
isWithArrow={true}
isAccentColor={true}
>
@ -135,11 +95,11 @@ export class Governance extends React.Component {
<Section bgColor="dark" maxWidth="1170px">
<SectionWrap>
<Heading>{proposalData.benefit.title}</Heading>
<Heading>{this._proposalData.benefit.title}</Heading>
<FlexWrap>
<Column width="55%" maxWidth="560px">
<Paragraph>{proposalData.benefit.summary}</Paragraph>
{_.map(proposalData.benefit.links, (link, index) => (
<Paragraph>{this._proposalData.benefit.summary}</Paragraph>
{_.map(this._proposalData.benefit.links, (link, index) => (
<MoreLink
href={link.url}
target={link.url !== undefined ? '_blank' : undefined}
@ -155,17 +115,17 @@ export class Governance extends React.Component {
<RatingBar
color={colors.brandLight}
labels={benefitLabels}
rating={proposalData.benefit.rating}
rating={this._proposalData.benefit.rating}
/>
</Column>
</FlexWrap>
</SectionWrap>
<SectionWrap>
<Heading>{proposalData.risks.title}</Heading>
<Heading>{this._proposalData.risks.title}</Heading>
<FlexWrap>
<Column width="55%" maxWidth="560px">
<Paragraph>{proposalData.risks.summary}</Paragraph>
{_.map(proposalData.risks.links, (link, index) => (
<Paragraph>{this._proposalData.risks.summary}</Paragraph>
{_.map(this._proposalData.risks.links, (link, index) => (
<MoreLink
href={link.url}
target={link.url !== undefined ? '_blank' : undefined}
@ -178,7 +138,11 @@ export class Governance extends React.Component {
))}
</Column>
<Column width="30%" maxWidth="360px">
<RatingBar color="#AE5400" labels={riskLabels} rating={proposalData.risks.rating} />
<RatingBar
color="#AE5400"
labels={riskLabels}
rating={this._proposalData.risks.rating}
/>
</Column>
</FlexWrap>
</SectionWrap>
@ -238,14 +202,12 @@ export class Governance extends React.Component {
tally.no = tally.no.plus(userBalance);
}
tally.totalBalance = tally.yes.plus(tally.no);
this.setState({ ...this.state, isVoteReceived: true, tally });
};
private async _fetchVoteStatusAsync(): Promise<void> {
try {
const voteDomain = utils.isProduction() ? `https://${configs.DOMAIN_VOTE}` : 'http://localhost:3000';
const voteEndpoint = `${voteDomain}/v1/tally/${proposalData.zeipId}`;
const voteEndpoint = `${voteDomain}/v1/tally/${this._proposalData.zeipId}`;
const response = await fetch(voteEndpoint, {
method: 'get',
mode: 'cors',

View File

@ -0,0 +1,88 @@
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import { DocumentTitle } from 'ts/components/document_title';
import { Column, Section } from 'ts/components/newLayout';
import { SiteWrap } from 'ts/components/siteWrap';
import { Heading, Paragraph } from 'ts/components/text';
import { Proposal, proposals } from 'ts/pages/governance/data';
import { VoteIndexCard } from 'ts/pages/governance/vote_index_card';
import { TallyInterface } from 'ts/types';
import { documentConstants } from 'ts/utils/document_meta_constants';
const ZEIP_IDS = [23, 39, 24, 25];
const ZEIP_PROPOSALS: Proposal[] = ZEIP_IDS.map(id => proposals[id]).sort(
(a, b) => b.voteStartDate.unix() - a.voteStartDate.unix(),
);
export interface VoteIndexProps {}
interface ZeipTallyMap {
[id: number]: TallyInterface;
}
export interface VoteIndexState {
tallys?: ZeipTallyMap;
}
export class VoteIndex extends React.Component<VoteIndexProps, VoteIndexState> {
public state: VoteIndexState = {
tallys: undefined,
};
public componentDidMount(): void {
// tslint:disable:no-floating-promises
this._fetchTallysAsync();
}
public render(): React.ReactNode {
return (
<SiteWrap>
<DocumentTitle {...documentConstants.VOTE} />
<Section isTextCentered={true} isPadded={true} padding="150px 0px 110px">
<Column>
<Heading size="medium" isCentered={true}>
0x Protocol Governance
</Heading>
<Paragraph size="medium" isCentered={true} isMuted={true} marginBottom="0">
Level up your favorite trading infrastructure to support new markets and industries
</Paragraph>
</Column>
</Section>
<VoteIndexCardWrapper>
{ZEIP_PROPOSALS.map(proposal => {
const tally = this.state.tallys && this.state.tallys[proposal.zeipId];
return <VoteIndexCard key={proposal.zeipId} tally={tally} {...proposal} />;
})}
</VoteIndexCardWrapper>
</SiteWrap>
);
}
private async _fetchTallysAsync(): Promise<void> {
// TODO: Real implementation
const getRandomInt = (max: string): BigNumber => {
return new BigNumber(max).times(Math.random());
};
const bigNumber = '100000000000000000000000000';
const generateRandomTally = (): TallyInterface => ({
yes: getRandomInt(bigNumber),
no: getRandomInt(bigNumber),
});
setTimeout(() => {
const tallys = {
23: generateRandomTally(),
39: generateRandomTally(),
24: generateRandomTally(),
25: generateRandomTally(),
};
this.setState({ tallys });
}, 1000);
}
}
const VoteIndexCardWrapper = styled.div`
margin-bottom: 150px;
`;

View File

@ -0,0 +1,103 @@
import * as _ from 'lodash';
import * as moment from 'moment';
import * as React from 'react';
import { Link as ReactRouterLink } from 'react-router-dom';
import styled from 'styled-components';
import { Column, FlexWrap, Section } from 'ts/components/newLayout';
import { Heading, Paragraph } from 'ts/components/text';
import { getTotalBalancesString } from 'ts/pages/governance/vote_stats';
import { VoteStatusText } from 'ts/pages/governance/vote_status_text';
import { TallyInterface, VoteOutcome, VoteTime, ZeipId } from 'ts/types';
export interface VoteIndexCardProps {
title: string;
zeipId: ZeipId;
summary: string;
voteStartDate: moment.Moment;
voteEndDate: moment.Moment;
// Non-static properties
tally?: TallyInterface;
}
const getVoteTime = (voteStartDate: moment.Moment, voteEndDate: moment.Moment): VoteTime | undefined => {
const now = moment();
if (now.isBefore(voteEndDate) && now.isAfter(voteStartDate)) {
return 'happening';
}
if (now.isBefore(voteStartDate)) {
return 'upcoming';
}
return undefined;
};
const getVoteOutcome = (tally?: TallyInterface): VoteOutcome | undefined => {
if (!tally) {
return undefined;
}
if (tally.no.isGreaterThanOrEqualTo(tally.yes)) {
return 'rejected';
} else if (tally.yes.isGreaterThan(tally.no)) {
return 'accepted';
}
return undefined;
};
const getDateString = (voteStartDate: moment.Moment, voteEndDate: moment.Moment): string => {
const voteTime = getVoteTime(voteStartDate, voteEndDate);
if (voteTime === 'happening') {
return `Ends ${voteEndDate.format('MMMM Do YYYY, h:mm:ss a')}`;
}
if (voteTime === 'upcoming') {
return `Starting ${voteStartDate.format('MMMM Do YYYY, h:mm:ss a')}`;
}
return `Ended ${voteEndDate.format('MMMM Do YYYY')}`;
};
export const VoteIndexCard: React.StatelessComponent<VoteIndexCardProps> = ({
title,
zeipId,
summary,
voteStartDate,
voteEndDate,
tally,
}) => {
const voteTime = getVoteTime(voteStartDate, voteEndDate);
const voteStatus = voteTime || getVoteOutcome(tally);
const totalBalances = getTotalBalancesString(tally);
const isPastProposal = voteTime === undefined;
return (
<ReactRouterLink to={`vote/zeip-${zeipId}`}>
<Section
hasBorder={isPastProposal}
bgColor={!isPastProposal ? 'dark' : 'none'}
padding="60px 30px 40px"
hasHover={true}
margin="30px auto"
maxWidth="100%"
>
<FlexWrap>
<Column width="60%" padding="0px 20px 0px 0px">
<Heading>
{`${title} `}
<Muted>{`(ZEIP-${zeipId})`}</Muted>
</Heading>
<Paragraph>{summary}</Paragraph>
</Column>
<Column>
<div className="flex flex-column items-end">
<VoteStatusText status={voteStatus} />
<Paragraph marginBottom="12px" isMuted={1}>{`${totalBalances} ZRX Total Vote`}</Paragraph>
<Paragraph marginBottom="12px">{getDateString(voteStartDate, voteEndDate)}</Paragraph>
</div>
</Column>
</FlexWrap>
</Section>
</ReactRouterLink>
);
};
const Muted = styled.span`
opacity: 0.6;
`;

View File

@ -3,16 +3,17 @@ import { Web3Wrapper } from '@0x/web3-wrapper';
import * as React from 'react';
import { Heading, Paragraph } from 'ts/components/text';
import { TallyInterface } from 'ts/pages/governance/governance';
import { ZERO_TALLY } from 'ts/pages/governance/data';
import { VoteBar } from 'ts/pages/governance/vote_bar';
import { colors } from 'ts/style/colors';
import { TallyInterface } from 'ts/types';
import { constants } from 'ts/utils/constants';
interface VoteStatsProps {
export interface VoteStatsProps {
tally?: TallyInterface;
}
export const VoteStats: React.StatelessComponent<VoteStatsProps> = ({ tally }) => {
export const getTotalBalancesString = (tally: TallyInterface = ZERO_TALLY): string => {
const bigNumberFormat = {
decimalSeparator: '.',
groupSeparator: ',',
@ -21,15 +22,23 @@ export const VoteStats: React.StatelessComponent<VoteStatsProps> = ({ tally }) =
fractionGroupSeparator: ' ',
fractionGroupSize: 0,
};
const { yes, totalBalance } = tally;
const HUNDRED = new BigNumber(100);
const { yes, no } = tally;
const totalBalance = yes.plus(no);
const totalBalanceString = Web3Wrapper.toUnitAmount(totalBalance, constants.DECIMAL_PLACES_ETH).toFormat(
0,
BigNumber.ROUND_FLOOR,
bigNumberFormat,
);
let yesPercentage = HUNDRED.times(yes.dividedBy(totalBalance));
let noPercentage = HUNDRED.minus(yesPercentage);
return totalBalanceString;
};
export const VoteStats: React.StatelessComponent<VoteStatsProps> = ({ tally }) => {
const { yes, no } = tally;
const totalBalance = yes.plus(no);
const oneHundred = new BigNumber(100);
const totalBalanceString = getTotalBalancesString(tally);
let yesPercentage = oneHundred.times(yes.dividedBy(totalBalance));
let noPercentage = oneHundred.minus(yesPercentage);
if (isNaN(yesPercentage.toNumber())) {
yesPercentage = new BigNumber(0);
@ -50,3 +59,7 @@ export const VoteStats: React.StatelessComponent<VoteStatsProps> = ({ tally }) =
</>
);
};
VoteStats.defaultProps = {
tally: ZERO_TALLY,
};

View File

@ -0,0 +1,88 @@
import * as React from 'react';
import styled from 'styled-components';
import { Button } from 'ts/components/button';
import { VoteStatus } from 'ts/types';
const checkColor = '#00AE99';
const renderCheck = (width: number = 18) => (
<svg width={width} viewBox="0 0 18 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 1L6 12L1 7" stroke={checkColor} strokeWidth="1.4" />
</svg>
);
const clockColor = '#EECE29';
const renderClock = (width: number = 18) => (
<svg width={width} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="9" cy="9" r="8" stroke={clockColor} strokeWidth="0.75" />
<line x1="8.08838" y1="4.76465" x2="8.08838" y2="10.4117" stroke={clockColor} />
<line x1="13.2354" y1="9.9707" x2="7.58829" y2="9.9707" stroke={clockColor} />
</svg>
);
const crossColor = '#D34F4F';
const renderCross = (width: number = 14) => (
<svg width={width} viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.50551 6.99961L0 12.5051L0.989949 13.4951L6.49546 7.98956L12.001 13.4951L12.9909 12.5051L7.48541 6.99961L12.99 1.49508L12 0.505127L6.49546 6.00966L0.990926 0.505127L0.0009767 1.49508L5.50551 6.99961Z"
fill={crossColor}
/>
</svg>
);
export interface VoteStatusTextProps {
status: VoteStatus;
}
export const VoteStatusText: React.StatelessComponent<VoteStatusTextProps> = ({ status }) => {
switch (status) {
case 'upcoming':
return (
<VoteStatusTextBase color={clockColor}>
<span>{renderClock()}</span>
Upcoming
</VoteStatusTextBase>
);
case 'accepted':
return (
<VoteStatusTextBase color={checkColor}>
<span>{renderCheck()}</span>
Accepted
</VoteStatusTextBase>
);
case 'rejected':
return (
<VoteStatusTextBase color={crossColor}>
<span>{renderCross()}</span>
Rejected
</VoteStatusTextBase>
);
case 'happening':
return (
<VoteStatusTextBase>
<Button isWithArrow={true} isAccentColor={true} fontSize="22px">
Vote Now
</Button>
</VoteStatusTextBase>
);
default:
return <VoteStatusTextBase>Loading...</VoteStatusTextBase>;
}
};
interface VoteStatusTextBaseProps {
color?: string;
}
const VoteStatusTextBase = styled.div<VoteStatusTextBaseProps>`
font-size: 22px;
color: ${props => props.color};
margin-bottom: 12px;
span {
position: relative;
margin-right: 8px;
top: 1px;
}
`;

View File

@ -1,50 +0,0 @@
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import { Button } from 'ts/components/button';
import { DocumentTitle } from 'ts/components/document_title';
import { Column, Section } from 'ts/components/newLayout';
import { SiteWrap } from 'ts/components/siteWrap';
import { Heading, Paragraph } from 'ts/components/text';
import { constants } from 'ts/utils/constants';
import { documentConstants } from 'ts/utils/document_meta_constants';
export const VotePlaceholder = () => (
<SiteWrap>
<DocumentTitle {...documentConstants.VOTE} />
<Section isTextCentered={true} isPadded={true} padding="150px 0px">
<Column>
<Heading size="medium" isCentered={true}>
Come back on February 18th to vote
</Heading>
<Paragraph size="medium" isCentered={true} isMuted={true} marginBottom="0">
0x is conducting a vote on ZEIP-23, which adds the ability to trade bundles of ERC-20 and ERC-721
tokens via the 0x protocol. Integrating ZEIP-23 requires a modification to the protocols smart
contract pipeline, which has access to live digital assets. All ZRX token holders have the right to
vote on this improvement proposal.
</Paragraph>
<LinkWrap>
<Button
href={constants.URL_VOTE_BLOG_POST}
isWithArrow={true}
isAccentColor={true}
shouldUseAnchorTag={true}
target="_blank"
>
Learn More
</Button>
</LinkWrap>
</Column>
</Section>
</SiteWrap>
);
const LinkWrap = styled.div`
display: inline-flex;
margin-top: 60px;
a + a {
margin-left: 60px;
}
`;

View File

@ -765,4 +765,16 @@ export interface Package {
description: string;
link: ALink;
}
export type VoteOutcome = 'accepted' | 'rejected';
export type VoteTime = 'upcoming' | 'happening';
export type VoteStatus = VoteOutcome | VoteTime;
export type ZeipId = 39 | 24 | 23 | 25;
export interface TallyInterface {
zeipId?: ZeipId;
yes?: BigNumber;
no?: BigNumber;
blockNumber?: string;
}
// tslint:disable:max-file-line-count