Files
protocol/packages/website/ts/pages/governance/modal_vote.tsx
2019-07-17 15:04:32 +10:00

333 lines
10 KiB
TypeScript

import { ContractWrappers } from '@0x/contract-wrappers';
import { LedgerSubprovider } from '@0x/subproviders';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { DialogContent, DialogOverlay } from '@reach/dialog';
import '@reach/dialog/styles.css';
import { ZeroExProvider } from 'ethereum-types';
import * as React from 'react';
import styled from 'styled-components';
import { Button } from 'ts/components/button';
import { Icon } from 'ts/components/icon';
import { Heading, Paragraph } from 'ts/components/text';
import { GlobalStyle } from 'ts/constants/globalStyle';
import { ConnectForm, WalletConnectedProps } from 'ts/pages/governance/connect_form';
import { ErrorModal } from 'ts/pages/governance/error_modal';
import { VoteForm, VoteInfo } from 'ts/pages/governance/vote_form';
import { colors } from 'ts/style/colors';
import { constants } from 'ts/utils/constants';
interface Props {
theme?: GlobalStyle;
isOpen?: boolean;
onDismiss?: () => void;
onWalletConnected?: (providerName: string) => void;
onVoted?: (voteInfo: VoteInfo) => void;
zeipId: number;
}
interface State {
currentBalance: BigNumber;
isWalletConnected: boolean;
providerName: string;
isSubmitting: boolean;
isLedger: boolean;
ledgerSubproviderIfExists?: LedgerSubprovider;
isSuccessful: boolean;
isErrorModalOpen?: boolean;
isU2fSupported: boolean;
isVoted: boolean;
votePreference: string | null;
voteHash?: string;
signedVote?: any;
errorMessage?: string;
errors: ErrorProps;
web3Wrapper?: Web3Wrapper;
contractWrappers?: ContractWrappers;
providerEngine?: ZeroExProvider;
web3?: any;
selectedAddress?: string;
}
interface FormProps {
isSuccessful?: boolean;
isSubmitting?: boolean;
}
interface ErrorProps {
[key: string]: string;
}
// This is a copy of the generic form and includes a number of extra fields
// TODO remove the extraneous fields
export class ModalVote extends React.Component<Props> {
public networkId: number;
public state: State = {
currentBalance: new BigNumber(0),
isWalletConnected: false,
isU2fSupported: false,
providerName: 'Metamask',
selectedAddress: null,
isSubmitting: false,
isLedger: false,
ledgerSubproviderIfExists: null,
providerEngine: null,
isSuccessful: false,
isVoted: false,
votePreference: null,
errors: {},
};
// shared fields
public constructor(props: Props) {
super(props);
}
public render(): React.ReactNode {
const { isOpen, onDismiss, zeipId } = this.props;
const { isSuccessful, selectedAddress, currentBalance, isErrorModalOpen, errorMessage } = this.state;
const bigNumberFormat = {
decimalSeparator: '.',
groupSeparator: ',',
groupSize: 3,
secondaryGroupSize: 0,
fractionGroupSeparator: ' ',
fractionGroupSize: 0,
};
const formattedBalance = Web3Wrapper.toUnitAmount(currentBalance, constants.DECIMAL_PLACES_ETH).toFormat(
0,
BigNumber.ROUND_FLOOR,
bigNumberFormat,
);
return (
<>
<DialogOverlay
style={{ background: 'rgba(0, 0, 0, 0.75)', zIndex: 30 }}
isOpen={isOpen}
onDismiss={onDismiss}
>
<StyledDialogContent>
{this._renderFormContent()}
<Confirmation isSuccessful={isSuccessful}>
<Icon name={`zeip-${zeipId}`} size="large" margin={[0, 0, 'default', 0]} />
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
Vote Received!
</Heading>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
Your vote will help to decide the future of the protocol. You will be receiving a custom
I voted NFT as a token of our appreciation.
</Paragraph>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
You voted from {selectedAddress} with {formattedBalance} ZRX
</Paragraph>
<ButtonWrap>
<Button type="button" onClick={this._shareViaTwitterAsync.bind(this)}>
Tweet
</Button>
<Button type="button" onClick={this._onDone.bind(this)}>
Done
</Button>
</ButtonWrap>
</Confirmation>
<ButtonClose type="button" onClick={this.props.onDismiss}>
<span>Close</span>
<Icon name="close-modal" size={27} margin={[0, 0, 0, 0]} />
</ButtonClose>
<ErrorModal
isOpen={isErrorModalOpen}
text={errorMessage}
onClose={this._onCloseError.bind(this)}
/>
</StyledDialogContent>
</DialogOverlay>
</>
);
}
public _renderFormContent(): React.ReactNode {
switch (this.state.isWalletConnected) {
case true:
return this._renderVoteFormContent();
case false:
default:
return this._renderConnectWalletFormContent();
}
}
private _shareViaTwitterAsync(): void {
const { zeipId } = this.props;
const tweetText = encodeURIComponent(`I voted on ZEIP-${zeipId}! 🗳️#VoteWithZRX https://0x.org/vote`);
window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your vote', 'width=500,height=400');
}
private _renderConnectWalletFormContent(): React.ReactNode {
const { web3Wrapper } = this.state;
return (
<>
<ConnectForm
web3Wrapper={web3Wrapper}
onDismiss={this.props.onDismiss}
onWalletConnected={this._onWalletConnected.bind(this)}
onError={this._onError.bind(this)}
/>
</>
);
}
private _renderVoteFormContent(): React.ReactNode {
const {
currentBalance,
selectedAddress,
web3Wrapper,
isLedger,
web3,
ledgerSubproviderIfExists,
providerEngine,
} = this.state;
return (
<>
<VoteForm
currentBalance={currentBalance}
selectedAddress={selectedAddress}
web3Wrapper={web3Wrapper}
injectedProvider={web3}
onDismiss={this.props.onDismiss}
isLedger={isLedger}
ledgerSubproviderIfExists={ledgerSubproviderIfExists}
provider={providerEngine}
zeipId={this.props.zeipId}
onVoted={this._onVoted.bind(this)}
onError={this._onError.bind(this)}
/>
</>
);
}
private _onWalletConnected(props: WalletConnectedProps): void {
const {
contractWrappers,
selectedAddress,
currentBalance,
providerName,
injectedProviderIfExists,
web3Wrapper,
isLedger,
ledgerSubproviderIfExists,
providerEngine,
} = props;
this.setState({
...this.state,
web3Wrapper,
contractWrappers,
web3: injectedProviderIfExists,
isWalletConnected: true,
providerName,
currentBalance,
selectedAddress,
isLedger,
ledgerSubproviderIfExists,
providerEngine,
});
this.props.onWalletConnected(providerName);
}
private _onVoted(voteInfo: VoteInfo): void {
this.setState({
isSuccessful: true,
});
if (this.props.onVoted) {
this.props.onVoted(voteInfo);
}
}
private _onDone(): void {
this.setState({
isErrorModalOpen: false,
isSuccessful: false,
});
this.props.onDismiss();
}
private _onError(errorMessage: string): void {
this.setState({
errorMessage,
isErrorModalOpen: true,
});
}
private _onCloseError(): void {
this.setState({
errorMessage: '',
isErrorModalOpen: false,
});
}
}
const ButtonClose = styled.button.attrs({})`
cursor: pointer;
position: absolute;
right: 0;
top: 0;
overflow: hidden;
width: 27px;
height: 27px;
border: 0;
background-color: transparent;
padding: 0;
transform: translateY(-47px);
span {
opacity: 0;
visibility: hidden;
position: absolute;
}
`;
const StyledDialogContent = styled(DialogContent)`
position: relative;
max-width: 800px;
background-color: #f6f6f6 !important;
padding: 60px 60px !important;
@media (max-width: 768px) {
width: calc(100vw - 40px) !important;
margin: 40px auto !important;
padding: 30px 30px !important;
}
`;
const Confirmation = styled.div<FormProps>`
position: absolute;
top: 50%;
text-align: center;
width: 100%;
left: 0;
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
transition-delay: 0.4s;
padding: 60px 60px;
transform: translateY(-50%);
opacity: ${props => (props.isSuccessful ? `1` : `0`)};
visibility: ${props => (props.isSuccessful ? 'visible' : `hidden`)};
p {
max-width: 492px;
margin-left: auto;
margin-right: auto;
}
`;
const ButtonWrap = styled.div`
display: inline-block;
@media (min-width: 768px) {
* + * {
margin-left: 15px;
}
}
@media (max-width: 768px) {
a,
button {
display: block;
width: 220px;
}
* + * {
margin-top: 15px;
}
}
`;