333 lines
10 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
`;
|