import { getValidatorAddressFromDelegatorAddress } from "@anthem/utils" ; import { Classes , Drawer , Icon , IconName , Position , Tooltip , } from "@blueprintjs/core" ; import { IconNames } from "@blueprintjs/icons" ; import { ChorusLogo } from "assets/icons" ; import { NetworkLogoIcon } from "assets/images" ; import { COLORS } from "constants/colors" ; import { ValidatorsProps , withGraphQLVariables } from "graphql/queries" ; import Modules , { ReduxStoreState } from "modules/root" ; import { i18nSelector } from "modules/settings/selectors" ; import React from "react" ; import CopyToClipboard from "react-copy-to-clipboard" ; import { connect } from "react-redux" ; import { Link , RouteComponentProps , withRouter } from "react-router-dom" ; import styled from "styled-components" ; import Swipy from "swipyjs" ; import { abbreviateAddress , copyTextToClipboard , onActiveRoute , } from "tools/client-utils" ; import { composeWithProps } from "tools/context-utils" ; import { IThemeProps } from "ui/containers/ThemeContainer" ; import { Centered , View } from "ui/SharedComponents" ; import BetaBanner from "./BetaBanner" ; import { KeyboardShortcutsPopover } from "./KeyboardShortcutsPopover" ; import Toast from "./Toast" ; /** ===========================================================================
 * State
 * ============================================================================
 */ interface IState { mobileMenuOpen : boolean ; } /** ===========================================================================
 * React Component
 * ============================================================================
 */ class SideMenuComponent extends React . Component < IProps , IState > { constructor ( props ) { super ( props ) ; this . state = { mobileMenuOpen : false , } ; } componentDidMount ( ) { this . setupMobileSwipeHandler ( ) ; } render ( ) { const { settings , location , i18n , ledger } = this . props ; const { address , network } = ledger ; const { t , tString } = i18n ; const { pathname } = location ; const { isDesktop } = settings ; const { name } = network ; const { mobileMenuOpen } = this . state ; const open = ( ) => this . setMobileMenuState ( true ) ; const close = ( ) => this . setMobileMenuState ( false ) ; const { activeChartTab } = this . props . app ; const ledgerConnected = this . props . ledger . connected ; const validator = this . getValidatorFromDelegatorAddressIfExists ( ) ; const HAS_ADDRESS = ! ! address ; const TOP_ROUTE_LINKS = [ < NavItem path = { pathname } closeHandler = { close } key = "Networks" title = "Networks" icon = { IconNames . GLOBE_NETWORK } route = { `/networks` } />, ] ; const IS_CELO = network . name === "CELO" ; if ( HAS_ADDRESS ) { TOP_ROUTE_LINKS . push ( < NavItem path = { pathname } closeHandler = { close } key = "Dashboard" title = { tString ( "Dashboard" ) } icon = { IconNames . TIMELINE_BAR_CHART } route = { `/${ name . toLowerCase ( ) }/${ activeChartTab . toLowerCase ( ) }` } />, <NavItem key={IS_CELO ? "Voting" : "Staking"} title={IS_CELO ? "Voting" : "Staking"} path={pathname} closeHandler={close} icon={IconNames.BANK_ACCOUNT} route={`/$ { name . toLowerCase ( ) } /delegate`}/ > , < NavItem key = "Governance" path = { pathname } closeHandler = { close } title = { tString ( "Governance" ) } icon = { IconNames . TAKE_ACTION } route = { `/${ name . toLowerCase ( ) }/governance` } />); } const BOTTOM_ROUTE_LINKS = [ address && ( < NavItem path = { pathname } closeHandler = { close } key = "Settings" route = "/settings" title = { tString ( "Settings" ) } icon = { IconNames . COG } / > ) , < NavItem path = { pathname } closeHandler = { close } key = "Help" route = "/help" title = { tString ( "Help" ) } icon = { IconNames . LIGHTBULB } / > , address && ( < NavItem path = { pathname } closeHandler = { ( e ) => { /**
                     * Prevent default link behavior since this does not need to
                     * navigate to any route currently.
                     */ e . preventDefault ( ) ; close ( ) ; this . props . openLogoutMenu ( ) ; } } key = "Logout" route = "/logout" title = { tString ( "Logout" ) } icon = { IconNames . LOG_OUT } / > ) , ] . filter ( Boolean ) as ReadonlyArray < JSX . Element > ; const AddressSelectComponent = ( < AddressContainer style = { { marginTop : isDesktop ? 0 : 4 , paddingLeft : isDesktop ? 20 : 12 , } } > < AddressConnectedTextBar > { isDesktop && < KeyboardShortcutsPopover />} { ledgerConnected ? ( < LedgerConnectedText > Ledger Connected < /LedgerConnectedText>) : HAS_ADDRESS ? (<AddressTitle>{t("Address Connected")}</AddressTitle > ) : ( < AddressTitle > { t ( "Connect Your Address" ) } < /AddressTitle>)} < /AddressConnectedTextBar> < NetworkAddressContainer > < NetworkAddressBox id = "link-address-button" onClick = { ( ) => { close ( ) ; this . props . openLedgerDialog ( { signinType : "INITIAL_SETUP" , ledgerAccessType : "SIGNIN" , } ) ; } } > { isDesktop && HAS_ADDRESS && ( < NetworkLogoIcon network = { network . name } styles = { { marginRight : 8 } } />)} < Address data - cy = "user-selected-address-bar" > { address ? `${ abbreviateAddress ( address , 6 ) }` : "Sign In" } < /Address> < /NetworkAddressBox> { HAS_ADDRESS && ( < CopyToClipboard onCopy = { this . onCopySuccess } text = { address } > { isDesktop ? ( < Centered style = { { marginTop : ! isDesktop ? 16 : "auto" } } > < Icon icon = "duplicate" color = { COLORS . LIGHT_GRAY } data - cy = "address-copy-to-clipboard-icon" className = "address-copy-to-clipboard-icon" style = { { marginLeft : 8 , marginRight : 8 } } /> < /Centered>) : (<MobileCopyAddressBox> < Icon icon = "duplicate" color = { COLORS . LIGHT_GRAY } data - cy = "address-copy-to-clipboard-icon" className = "address-copy-to-clipboard-icon" style = { { marginRight : 8 } } /> < p style = { { margin : 0 } } > { t ( "Copy Address" ) } < /p> < /MobileCopyAddressBox>)} < /CopyToClipboard>)} < /NetworkAddressContainer> { ! HAS_ADDRESS && ( < Link to = "/landing" > < ExitText > ← Exit to Landing Page < /ExitText> < /Link>)} { validator && ( < Tooltip usePortal = { false } disabled = { ! isDesktop } position = { Position . RIGHT } content = { validator . operator_address } > < ValidatorOperatorLabel onClick = { ( ) => copyTextToClipboard ( validator . operator_address ) } > { renderValidatorName ( validator . description . moniker ) } < /ValidatorOperatorLabel> < /Tooltip>)} { HAS_ADDRESS && ( < NetworkInfoText > < b style = { { color : COLORS . LIGHT_GRAY } } > { t ( "NETWORK:" ) } < /b>{" "} { network . name } < /NetworkInfoText>)} < /AddressContainer>); if ( isDesktop ) { return ( < DesktopNavigationContainer className = { Classes . DARK } > < View > < Link to = { `/${ this . props . app . activeChartTab }` } > < BetaBanner mobile = { false } /> < ChorusTitleImage src = { ChorusLogo } alt = "Chorus One Logo" / > < /Link> { TOP_ROUTE_LINKS } < /View> < View > { BOTTOM_ROUTE_LINKS } { AddressSelectComponent } < /View> < /DesktopNavigationContainer>); } else { return ( < React . Fragment > < MobileNavContainer > < Icon onClick = { open } iconSize = { 25 } color = { COLORS . WHITE } icon = { IconNames . MENU } data - cy = "hamburger-menu-button" / > < BetaBanner mobile = { true } /> < ChorusTitleImage src = { ChorusLogo } alt = "Chorus One Logo" / > < /MobileNavContainer> < Drawer position = "left" onClose = { close } isOpen = { mobileMenuOpen } title = "Chorus One" className = { Classes . DARK } style = { { paddingTop : 25 , backgroundColor : "rgb(26, 26, 39)" , } } > { TOP_ROUTE_LINKS } { BOTTOM_ROUTE_LINKS } { AddressSelectComponent } < /Drawer> < /React.Fragment>); } } setMobileMenuState = ( state ) => { this . setState ( { mobileMenuOpen : state , } ) ; } ; onCopySuccess = ( ) => { const { i18n } = this . props ; const { address } = this . props . ledger ; Toast . success ( i18n . tString ( "Address {{address}} copied to clipboard" , { address } ) ) ; } ; getValidatorFromDelegatorAddressIfExists = ( ) => { if ( ! this . props . cosmosValidators ) { return ; } const { cosmosValidators } = this . props . cosmosValidators ; if ( ! cosmosValidators ) { return ; } const { network , address } = this . props . ledger ; if ( ! address ) { return null ; } const validatorAddress = getValidatorAddressFromDelegatorAddress ( address , network . name ) ; const validator = cosmosValidators . find ( v => v . operator_address === validatorAddress ) ; return validator ; } ; setupMobileSwipeHandler = ( ) => { if ( this . props . settings . isDesktop ) { return ; } const swipeHandler = new Swipy ( document . documentElement ) ; swipeHandler . on ( "swiperight" , ( ) => { if ( ! this . state . mobileMenuOpen ) { this . setMobileMenuState ( true ) ; } } ) ; swipeHandler . on ( "swipeleft" , ( ) => { if ( this . state . mobileMenuOpen ) { this . setMobileMenuState ( false ) ; } } ) ; } ; } /** ===========================================================================
 * Styles and Helpers
 * ============================================================================
 */ const DesktopNavigationContainer = styled . div `
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 25;
  width: 250px;
  display: flex;
  position: fixed;
  flex-direction: column;
  justify-content: space-between;
  background-color: ${ COLORS . NAVIGATION_BACKGROUND };
` ; const MobileNavContainer = styled . div `
  z-index: 25;
  position: fixed;
  padding-left: 16px;
  padding-right: 16px;
  top: 0;
  height: 62px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: ${ COLORS . NAVIGATION_BACKGROUND };
` ; interface INavItemProps { title : string ; icon : IconName ; path : string ; route : string ; closeHandler ? : ( e : React . MouseEvent < HTMLAnchorElement , MouseEvent > ) => void ; } const NavItem = ( { route , title , icon , path , closeHandler } ) => { const active = onActiveRoute ( path , route ) ; const cypressLabel = `${ title . toLowerCase ( ) }-navigation-link` ; return ( < Link onClick = { closeHandler } data - cy = { cypressLabel } to = { route } > < NavLinkContainer activeRoute = { active } > < Icon icon = { icon } iconSize = { 20 } color = { active ? COLORS . LIGHT_WHITE : COLORS . LIGHT_GRAY } className = "nav-icon" style = { { marginRight : 14 } } /> < NavTitle activeRoute = { active } > { title } < /NavTitle> < /NavLinkContainer> < /Link>); } ; const NavTitle = styled . h4 `
  margin: 0;
  color: ${ ( props ) => props . activeRoute ? COLORS . LIGHT_WHITE : COLORS . LIGHT_GRAY };
` ; const LedgerConnectedText = styled . p `
  margin-bottom: 0;
  font-weight: bold;
  color: ${ COLORS . CHORUS_MINT };
` ; const NetworkInfoText = styled . p `
  margin-top: 5px;
  margin-bottom: 0;
  font-size: 12px;
  color: ${ COLORS . WHITE };
  margin-left: ${ ( props ) => props . theme . isDesktop ? "42px" : "auto" };
` ; const ValidatorOperatorLabel = styled . p `
  margin-top: 5px;
  margin-bottom: 0;
  font-size: 12px;
  color: ${ COLORS . FOCUS };
  margin-left: ${ ( props ) => props . theme . isDesktop ? "42px" : "auto" };

  &:hover {
    cursor: pointer;
  }
` ; const NavLinkContainer = styled . div `
  height: 50px;
  padding-left: 22px;
  display: flex;
  align-items: center;

  background: ${ ( props ) => props . activeRoute ? `linear-gradient(
    to right,
    ${ COLORS . CHORUS_BACKGROUND_INTERMEDIATE },
    ${ COLORS . NAVIGATION_BACKGROUND_ACTIVE_GRADIENT_END }
  )` : "" };

  &:hover {
    cursor: pointer;
    background-color: ${ COLORS . NAVIGATION_BACKGROUND_HOVER };
  }

  &:hover ${ NavTitle } {
    color: white;
  }
` ; const AddressContainer = styled . div `
  padding-top: 10px;
  padding-left: 20px;
  padding-right: 10px;
  display: block;
  height: 125px;
  border-top: 1px solid ${ COLORS . ADDRESS_LINE };
  background: ${ ( props ) => props . theme . isDesktop ? COLORS . ADDRESS_BACKGROUND : undefined };
` ; const AddressTitle = styled . p `
  font-weight: bold;
  margin: 0;
` ; const ChorusTitleImage = styled . img `
  height: 35px;
  width: 150px;
  margin-top: 20px;
  margin-left: 20px;
  margin-bottom: 20px;
` ; const NetworkAddressContainer = styled . div `
  height: 32px;
  margin-top: 12px;
  display: flex;
  flex-direction: ${ ( props ) => props . theme . isDesktop ? "row" : "column" };
` ; const NetworkAddressBox = styled . div `
  display: flex;
  flex-direction: row;

  &:hover {
    cursor: pointer;
  }
` ; const Address = styled . div `
  width: 145px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid ${ COLORS . ADDRESS_LINE };

  &:hover {
    cursor: pointer;
    background-color: ${ COLORS . NAVIGATION_BACKGROUND_HOVER };
  }
` ; export const AddressConnectedTextBar = styled . div `
  margin: 0;
  display: flex;
  flex-direction: row;
` ; const renderValidatorName = ( moniker ) => { return moniker . length > 16 ? `${ moniker . slice ( 0 , 16 ) }... Validator` : `${ moniker } Validator` ; } ; const MobileCopyAddressBox = styled . div `
  margin-top: 75px;
  display: flex;
  flex-direction: row;
` ; const ExitText = styled . p `
  margin-top: 12px;
  font-size: 12px;
` ; /** ===========================================================================
 * Props
 * ============================================================================
 */ const mapStateToProps = ( state ) => ( { i18n : i18nSelector ( state ) , settings : Modules . selectors . settings ( state ) , app : Modules . selectors . app . appSelector ( state ) , ledger : Modules . selectors . ledger . ledgerSelector ( state ) , network : Modules . selectors . ledger . networkSelector ( state ) , } ) ; const dispatchProps = { openLogoutMenu : Modules . actions . ledger . openLogoutMenu , openLedgerDialog : Modules . actions . ledger . openLedgerDialog , } ; type ConnectProps = ReturnType < typeof mapStateToProps > & typeof dispatchProps ; const withProps = connect ( mapStateToProps , dispatchProps ) ; interface ComponentProps { } interface IProps extends ComponentProps , ValidatorsProps , RouteComponentProps , ConnectProps { } /** ===========================================================================
 * Export
 * ============================================================================
 */ export default composeWithProps < ComponentProps > ( withProps , withGraphQLVariables , withRouter ) ( SideMenuComponent ) ;