diff options
Diffstat (limited to 'packages/website/ts/components/wallet/wallet.tsx')
-rw-r--r-- | packages/website/ts/components/wallet/wallet.tsx | 527 |
1 files changed, 0 insertions, 527 deletions
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx deleted file mode 100644 index d9da0b9d5..000000000 --- a/packages/website/ts/components/wallet/wallet.tsx +++ /dev/null @@ -1,527 +0,0 @@ -import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0x/react-shared'; -import { BigNumber, errorUtils } from '@0x/utils'; -import * as _ from 'lodash'; - -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; -import * as React from 'react'; -import firstBy from 'thenby'; - -import { Blockchain } from 'ts/blockchain'; -import { AccountConnection } from 'ts/components/ui/account_connection'; -import { Balance } from 'ts/components/ui/balance'; -import { Container } from 'ts/components/ui/container'; -import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down'; -import { IconButton } from 'ts/components/ui/icon_button'; -import { Identicon } from 'ts/components/ui/identicon'; -import { Island } from 'ts/components/ui/island'; -import { PointerDirection } from 'ts/components/ui/pointer'; -import { - CopyAddressSimpleMenuItem, - DifferentWalletSimpleMenuItem, - GoToAccountManagementSimpleMenuItem, - SimpleMenu, - SimpleMenuItem, -} from 'ts/components/ui/simple_menu'; -import { Text } from 'ts/components/ui/text'; -import { TokenIcon } from 'ts/components/ui/token_icon'; -import { BodyOverlay } from 'ts/components/wallet/body_overlay'; -import { NullTokenRow } from 'ts/components/wallet/null_token_row'; -import { PlaceHolder } from 'ts/components/wallet/placeholder'; -import { StandardIconRow } from 'ts/components/wallet/standard_icon_row'; -import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item'; -import { AllowanceStateToggle } from 'ts/containers/inputs/allowance_state_toggle'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { colors } from 'ts/style/colors'; -import { - AccountState, - BlockchainErrs, - ProviderType, - ScreenWidths, - Side, - Token, - TokenByAddress, - TokenState, - TokenStateByAddress, -} from 'ts/types'; -import { analytics } from 'ts/utils/analytics'; -import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; - -export interface WalletProps { - userAddress: string; - networkId: number; - blockchain: Blockchain; - blockchainIsLoaded: boolean; - blockchainErr: BlockchainErrs; - dispatcher: Dispatcher; - tokenByAddress: TokenByAddress; - trackedTokens: Token[]; - userEtherBalanceInWei?: BigNumber; - lastForceTokenStateRefetch: number; - injectedProviderName: string; - providerType: ProviderType; - screenWidth: ScreenWidths; - location: Location; - trackedTokenStateByAddress: TokenStateByAddress; - onToggleLedgerDialog: () => void; - onAddToken: () => void; - onRemoveToken: () => void; - refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; - style: React.CSSProperties; - toggleTooltipDirection?: PointerDirection; -} - -interface WalletState { - wrappedEtherDirection?: Side; - isHoveringSidebar: boolean; -} - -interface AllowanceStateToggleConfig { - token: Token; - tokenState: TokenState; -} - -interface AccessoryItemConfig { - wrappedEtherDirection?: Side; - allowanceStateToggleConfig?: AllowanceStateToggleConfig; -} - -const ETHER_ICON_PATH = '/images/ether.png'; -const ICON_DIMENSION = 28; -const BODY_ITEM_KEY = 'BODY'; -const HEADER_ITEM_KEY = 'HEADER'; -const ETHER_ITEM_KEY = 'ETHER'; -const WRAP_ROW_ALLOWANCE_TOGGLE_WIDTH = 67; -const ALLOWANCE_TOGGLE_WIDTH = 56; -const PLACEHOLDER_COLOR = colors.grey300; -const LOADING_ROWS_COUNT = 6; - -export class Wallet extends React.Component<WalletProps, WalletState> { - public static defaultProps = { - style: {}, - }; - constructor(props: WalletProps) { - super(props); - this.state = { - wrappedEtherDirection: undefined, - isHoveringSidebar: false, - }; - } - public componentDidUpdate(prevProps: WalletProps): void { - const currentTrackedTokens = this.props.trackedTokens; - const differentTrackedTokens = _.difference(currentTrackedTokens, prevProps.trackedTokens); - const firstDifferentTrackedToken = _.head(differentTrackedTokens); - // check if there is only one different token, and if that token is a member of the current tracked tokens - // this means that the token was added, not removed - if ( - !_.isUndefined(firstDifferentTrackedToken) && - _.size(differentTrackedTokens) === 1 && - _.includes(currentTrackedTokens, firstDifferentTrackedToken) - ) { - document.getElementById(firstDifferentTrackedToken.address).scrollIntoView(); - } - } - public render(): React.ReactNode { - return ( - <Island className="flex flex-column wallet" style={this.props.style}> - {this._isBlockchainReady() ? this._renderLoadedRows() : this._renderLoadingRows()} - </Island> - ); - } - private _renderLoadingRows(): React.ReactNode { - return _.concat(this._renderLoadingHeaderRows(), this._renderLoadingBodyRows()); - } - private _renderLoadingHeaderRows(): React.ReactElement<{}> { - return this._renderPlainHeaderRow('Loading...'); - } - private _renderLoadingBodyRows(): React.ReactElement<{}> { - const bodyStyle = this._getBodyStyle(); - const loadingRowsRange = _.range(LOADING_ROWS_COUNT); - return ( - <div key={BODY_ITEM_KEY} className="flex flex-column" style={bodyStyle}> - {_.map(loadingRowsRange, index => { - return <NullTokenRow key={index} iconDimension={ICON_DIMENSION} fillColor={PLACEHOLDER_COLOR} />; - })} - <Container - className="flex items-center" - position="absolute" - width="100%" - height="100%" - maxHeight={bodyStyle.maxHeight} - > - <div className="mx-auto"> - <BodyOverlay - dispatcher={this.props.dispatcher} - userAddress={this.props.userAddress} - injectedProviderName={this.props.injectedProviderName} - providerType={this.props.providerType} - onToggleLedgerDialog={this.props.onToggleLedgerDialog} - blockchain={this.props.blockchain} - blockchainIsLoaded={this.props.blockchainIsLoaded} - /> - </div> - </Container> - </div> - ); - } - private _renderLoadedRows(): React.ReactNode { - const isAddressAvailable = !_.isEmpty(this.props.userAddress); - return isAddressAvailable - ? _.concat(this._renderConnectedHeaderRows(), this._renderBody()) - : _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows()); - } - private _renderDisconnectedHeaderRows(): React.ReactElement<{}> { - const isExternallyInjectedProvider = utils.isExternallyInjected( - this.props.providerType, - this.props.injectedProviderName, - ); - const text = isExternallyInjectedProvider ? 'Please unlock MetaMask...' : 'Please connect a wallet...'; - return this._renderPlainHeaderRow(text); - } - private _renderPlainHeaderRow(text: string): React.ReactElement<{}> { - return ( - <StandardIconRow - key={HEADER_ITEM_KEY} - icon={<ActionAccountBalanceWallet color={colors.grey} />} - main={ - <Text fontSize="16px" fontColor={colors.grey}> - {text} - </Text> - // https://github.com/palantir/tslint-react/issues/140 - // tslint:disable-next-line:jsx-curly-spacing - } - minHeight="60px" - backgroundColor={colors.white} - /> - ); - } - private _renderConnectedHeaderRows(): React.ReactElement<{}> { - const isMobile = this.props.screenWidth === ScreenWidths.Sm; - const userAddress = this.props.userAddress; - const accountState = this._getAccountState(); - const main = ( - <div className="flex flex-column"> - <Text fontSize="16px" lineHeight="19px" fontWeight={500}> - {utils.getAddressBeginAndEnd(userAddress)} - </Text> - <AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} /> - </div> - ); - const onClick = _.noop.bind(_); - const accessory = ( - <DropDown - activeNode={ - // this container gives the menu button more of a hover target for the drop down - // it prevents accidentally closing the menu by moving off of the button - <Container paddingLeft="100px" paddingRight="15px"> - <Text - className="zmdi zmdi-more-horiz" - Tag="i" - fontSize="32px" - fontFamily="Material-Design-Iconic-Font" - fontColor={colors.darkGrey} - onClick={onClick} - hoverColor={colors.mediumBlue} - /> - </Container> - } - popoverContent={ - <SimpleMenu minWidth="150px"> - <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> - {!isMobile && <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />} - <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} /> - <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} /> - {!isMobile && <GoToAccountManagementSimpleMenuItem />} - </SimpleMenu> - } - anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} - targetOrigin={{ horizontal: 'right', vertical: 'top' }} - zDepth={1} - activateEvent={DropdownMouseEvent.Click} - closeEvent={DropdownMouseEvent.Click} - /> - ); - return ( - <StandardIconRow - key={HEADER_ITEM_KEY} - icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} - main={main} - accessory={accessory} - minHeight="60px" - backgroundColor={colors.white} - /> - ); - } - private _renderBody(): React.ReactElement<{}> { - const bodyStyle = this._getBodyStyle(); - return ( - <div - style={bodyStyle} - key={BODY_ITEM_KEY} - onMouseEnter={this._onSidebarHover.bind(this)} - onMouseLeave={this._onSidebarHoverOff.bind(this)} - > - {this._renderEthRows()} - {this._renderTokenRows()} - </div> - ); - } - private _getBodyStyle(): React.CSSProperties { - return { - overflow: 'auto', - WebkitOverflowScrolling: 'touch', - position: 'relative', - overflowY: this.state.isHoveringSidebar ? 'scroll' : 'hidden', - marginRight: this.state.isHoveringSidebar ? 0 : 4, - minHeight: '250px', - maxHeight: !utils.isMobileWidth(this.props.screenWidth) ? 'calc(90vh - 300px)' : undefined, - }; - } - private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void { - this.setState({ - isHoveringSidebar: true, - }); - } - private _onSidebarHoverOff(): void { - this.setState({ - isHoveringSidebar: false, - }); - } - private _renderEthRows(): React.ReactNode { - const icon = <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />; - const primaryText = this._renderAmount( - this.props.userEtherBalanceInWei || new BigNumber(0), - constants.DECIMAL_PLACES_ETH, - constants.ETHER_SYMBOL, - _.isUndefined(this.props.userEtherBalanceInWei), - ); - const etherToken = this._getEthToken(); - const etherTokenState = this.props.trackedTokenStateByAddress[etherToken.address]; - const etherPrice = etherTokenState.price; - const secondaryText = this._renderValue( - this.props.userEtherBalanceInWei || new BigNumber(0), - constants.DECIMAL_PLACES_ETH, - etherPrice, - _.isUndefined(this.props.userEtherBalanceInWei) || !etherTokenState.isLoaded, - ); - const accessoryItemConfig = { - wrappedEtherDirection: Side.Deposit, - }; - const key = ETHER_ITEM_KEY; - return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig); - } - private _renderTokenRows(): React.ReactNode { - const trackedTokens = this.props.trackedTokens; - const trackedTokensStartingWithEtherToken = trackedTokens.sort( - firstBy((t: Token) => t.symbol !== constants.ETHER_TOKEN_SYMBOL) - .thenBy((t: Token) => t.symbol !== constants.ZRX_TOKEN_SYMBOL) - .thenBy('trackedTimestamp'), - ); - return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); - } - private _renderTokenRow(token: Token): React.ReactNode { - const tokenState = this.props.trackedTokenStateByAddress[token.address]; - if (_.isUndefined(tokenState)) { - return null; - } - const tokenLink = sharedUtils.getEtherScanLinkIfExists( - token.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const icon = <TokenIcon token={token} diameter={ICON_DIMENSION} link={tokenLink} />; - const isWeth = token.symbol === constants.ETHER_TOKEN_SYMBOL; - const wrappedEtherDirection = isWeth ? Side.Receive : undefined; - const primaryText = this._renderAmount(tokenState.balance, token.decimals, token.symbol, !tokenState.isLoaded); - const secondaryText = this._renderValue( - tokenState.balance, - token.decimals, - tokenState.price, - !tokenState.isLoaded, - ); - const accessoryItemConfig: AccessoryItemConfig = { - wrappedEtherDirection, - allowanceStateToggleConfig: { - token, - tokenState, - }, - }; - const key = token.address; - return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig); - } - private _renderBalanceRow( - key: string, - icon: React.ReactNode, - primaryText: React.ReactNode, - secondaryText: React.ReactNode, - accessoryItemConfig: AccessoryItemConfig, - className?: string, - ): React.ReactNode { - const shouldShowWrapEtherItem = - !_.isUndefined(this.state.wrappedEtherDirection) && - this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection && - !_.isUndefined(this.props.userEtherBalanceInWei); - const etherToken = this._getEthToken(); - const wrapEtherItem = shouldShowWrapEtherItem ? ( - <WrapEtherItem - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - userEtherBalanceInWei={this.props.userEtherBalanceInWei} - direction={accessoryItemConfig.wrappedEtherDirection} - etherToken={etherToken} - lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} - onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} - // tslint:disable:jsx-no-lambda - refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} - /> - ) : null; - return ( - <div id={key} key={key} className={`flex flex-column ${className || ''}`}> - {this.state.wrappedEtherDirection === Side.Receive && wrapEtherItem} - <StandardIconRow - icon={icon} - main={ - <div className="flex flex-column"> - {primaryText} - <Container marginTop="3px">{secondaryText}</Container> - </div> - } - accessory={this._renderAccessoryItems(accessoryItemConfig)} - /> - {this.state.wrappedEtherDirection === Side.Deposit && wrapEtherItem} - </div> - ); - } - private _renderAccessoryItems(config: AccessoryItemConfig): React.ReactElement<{}> { - const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherDirection); - const shouldShowToggle = !_.isUndefined(config.allowanceStateToggleConfig); - // if we don't have a toggle, we still want some space to the right of the "wrap" button so that it aligns with - // the "unwrap" button in the row below - const isWrapEtherRow = shouldShowWrappedEtherAction && config.wrappedEtherDirection === Side.Deposit; - const width = isWrapEtherRow ? WRAP_ROW_ALLOWANCE_TOGGLE_WIDTH : ALLOWANCE_TOGGLE_WIDTH; - const toggle = ( - <Container className="flex justify-center" width={width}> - {shouldShowToggle && this._renderAllowanceToggle(config.allowanceStateToggleConfig)} - </Container> - ); - return ( - <div className="flex items-center"> - <div className="flex-auto"> - {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherDirection)} - </div> - <div className="flex-last pl2">{toggle}</div> - </div> - ); - } - private _renderAllowanceToggle(config: AllowanceStateToggleConfig): React.ReactNode { - // TODO: Error handling - return ( - <AllowanceStateToggle - blockchain={this.props.blockchain} - token={config.token} - tokenState={config.tokenState} - tooltipDirection={this.props.toggleTooltipDirection} - refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)} - /> - ); - } - private _renderAmount( - amount: BigNumber, - decimals: number, - symbol: string, - isLoading: boolean = false, - ): React.ReactNode { - if (isLoading) { - return ( - <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}> - <Text fontSize="16px" fontWeight="bold" lineHeight="1em"> - 0.00 XXX - </Text> - </PlaceHolder> - ); - } else { - return <Balance amount={amount} decimals={decimals} symbol={symbol} />; - } - } - private _renderValue( - amount: BigNumber, - decimals: number, - price?: BigNumber, - isLoading: boolean = false, - ): React.ReactNode { - const result = !isLoading - ? _.isUndefined(price) - ? '--' - : utils.getUsdValueFormattedAmount(amount, decimals, price) - : '$0.00'; - return ( - <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}> - <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em"> - {result} - </Text> - </PlaceHolder> - ); - } - private _renderWrappedEtherButton(wrappedEtherDirection: Side): React.ReactNode { - const isWrappedEtherDirectionOpen = this.state.wrappedEtherDirection === wrappedEtherDirection; - let buttonLabel; - let buttonIconName; - if (isWrappedEtherDirectionOpen) { - buttonLabel = 'cancel'; - buttonIconName = 'zmdi-close'; - } else { - switch (wrappedEtherDirection) { - case Side.Deposit: - buttonLabel = 'wrap'; - buttonIconName = 'zmdi-long-arrow-down'; - break; - case Side.Receive: - buttonLabel = 'unwrap'; - buttonIconName = 'zmdi-long-arrow-up'; - break; - default: - throw errorUtils.spawnSwitchErr('wrappedEtherDirection', wrappedEtherDirection); - } - } - const onClick = isWrappedEtherDirectionOpen - ? this._closeWrappedEtherActionRow.bind(this, wrappedEtherDirection) - : this._openWrappedEtherActionRow.bind(this, wrappedEtherDirection); - return ( - <IconButton iconName={buttonIconName} labelText={buttonLabel} onClick={onClick} color={colors.mediumBlue} /> - ); - } - private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void { - const action = - wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened'; - analytics.track(action); - this.setState({ - wrappedEtherDirection, - }); - } - private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void { - const action = - wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed'; - analytics.track(action); - this.setState({ - wrappedEtherDirection: undefined, - }); - } - private _getEthToken(): Token { - return utils.getEthToken(this.props.tokenByAddress); - } - private _isBlockchainReady(): boolean { - return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain); - } - private _getAccountState(): AccountState { - return utils.getAccountState( - this._isBlockchainReady(), - this.props.providerType, - this.props.injectedProviderName, - this.props.userAddress, - ); - } -} - -// tslint:disable:max-file-line-count |