import { colors, Link } from '@0x/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import DocumentTitle from 'react-document-title'; import { Footer } from 'ts/components/footer'; import { SubscribeForm } from 'ts/components/forms/subscribe_form'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { CallToAction } from 'ts/components/ui/button'; import { Container } from 'ts/components/ui/container'; import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; import { TypedText } from 'ts/components/ui/typed_text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; interface BoxContent { title: string; description: string; imageUrl: string; classNames: string; maxWidth: number; } interface UseCase { imageUrl: string; type: string; description: string; classNames: string; style?: React.CSSProperties; } interface Project { logoFileName: string; projectUrl: string; } const THROTTLE_TIMEOUT = 100; const WHATS_NEW_TITLE = 'Introducing the 0x Launch Kit'; const WHATS_NEW_URL = 'https://blog.0xproject.com/introducing-the-0x-launch-kit-4acdc3453585'; const TITLE_STYLE: React.CSSProperties = { fontFamily: 'Roboto Mono', color: colors.grey, fontWeight: 300, letterSpacing: 3, }; const ROTATING_LIST = [ 'tokens', 'game items', 'digital art', 'futures', 'stocks', 'derivatives', 'loans', 'cats', 'everything', ]; const relayerProjects: Project[] = [ { logoFileName: 'ethfinex.png', projectUrl: constants.PROJECT_URL_ETHFINEX, }, { logoFileName: 'radar_relay.png', projectUrl: constants.PROJECT_URL_RADAR_RELAY, }, { logoFileName: 'paradex.png', projectUrl: constants.PROJECT_URL_PARADEX, }, { logoFileName: 'the_ocean.png', projectUrl: constants.PROJECT_URL_0CEAN, }, { logoFileName: 'amadeus.png', projectUrl: constants.PROJECT_URL_AMADEUS, }, { logoFileName: 'ddex.png', projectUrl: constants.PROJECT_URL_DDEX, }, { logoFileName: 'decent_ex.png', projectUrl: constants.PROJECT_URL_DECENT_EX, }, { logoFileName: 'dextroid.png', projectUrl: constants.PROJECT_URL_DEXTROID, }, { logoFileName: 'ercdex.png', projectUrl: constants.PROJECT_URL_ERC_DEX, }, { logoFileName: 'open_relay.png', projectUrl: constants.PROJECT_URL_OPEN_RELAY, }, { logoFileName: 'idt.png', projectUrl: constants.PROJECT_URL_IDT, }, { logoFileName: 'imtoken.png', projectUrl: constants.PROJECT_URL_IMTOKEN, }, ]; export interface LandingProps { location: Location; translate: Translate; dispatcher: Dispatcher; } interface LandingState { screenWidth: ScreenWidths; } export class Landing extends React.Component { private readonly _throttledScreenWidthUpdate: () => void; constructor(props: LandingProps) { super(props); this.state = { screenWidth: utils.getScreenWidth(), }; this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); } public componentDidMount(): void { window.addEventListener('resize', this._throttledScreenWidthUpdate); window.scrollTo(0, 0); } public componentWillUnmount(): void { window.removeEventListener('resize', this._throttledScreenWidthUpdate); } public render(): React.ReactNode { return (
{this._renderHero()} {this._renderProjects( relayerProjects, this.props.translate.get(Key.RelayersHeader, Deco.Upper), colors.projectsGrey, true, )} {this._renderInfoBoxes()} {this._renderTokenizationSection()} {this._renderUseCases()} {this._renderCallToAction()}
); } private _renderHero(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const left = 'col lg-col-6 md-col-6 col-12 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; const flexClassName = isSmallScreen ? 'flex items-center flex-column justify-center' : 'flex items-center justify-center'; return (
{this._renderWhatsNew()}
{this.props.translate.get(Key.TopHeader, Deco.Cap)} {this.props.translate.getLanguage() === Language.English && ( {' '} for{' '} )} {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)}
{this.props.translate.get(Key.TradeCallToAction, Deco.Cap)}
{this.props.translate.getLanguage() === Language.English && }
); } private _renderWhatsNew(): React.ReactNode { return (
New {WHATS_NEW_TITLE}
); } private _renderProjects( projects: Project[], title: string, backgroundColor: string, isTitleCenter: boolean, ): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const projectList = _.map(projects, (project: Project, i: number) => { const isRelayersOnly = projects.length === 12; let colWidth: number; switch (this.state.screenWidth) { case ScreenWidths.Sm: colWidth = 4; break; case ScreenWidths.Md: colWidth = 3; break; case ScreenWidths.Lg: colWidth = isRelayersOnly ? 2 : 2 - i % 2; break; default: throw new Error(`Encountered unknown ScreenWidths value: ${this.state.screenWidth}`); } return (
); }); return (
{title}
{projectList}
{this.props.translate.get(Key.FullListPrompt)}{' '} {this.props.translate.get(Key.FullListLink)}
); } private _renderTokenizationSection(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return (
{isSmallScreen && this._renderTokenCloud()}
{this.props.translate.get(Key.TokenizedSectionHeader, Deco.Cap)}
{this.props.translate.get(Key.TokenizedSectionDescription, Deco.Cap)}
{this._renderMissionAndValuesButton()}
{!isSmallScreen && this._renderTokenCloud()}
); } private _renderTokenCloud(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return (
); } private _renderMissionAndValuesButton(): React.ReactNode { return ( {this.props.translate.get(Key.OurMissionAndValues, Deco.CapWords)} ); } private _renderInfoBoxes(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const boxStyle: React.CSSProperties = { maxWidth: 253, height: 402, backgroundColor: colors.grey50, borderRadius: 5, padding: '10px 24px 24px', }; const boxContents: BoxContent[] = [ { title: this.props.translate.get(Key.BenefitOneTitle, Deco.Cap), description: this.props.translate.get(Key.BenefitOneDescription, Deco.Cap), imageUrl: '/images/landing/distributed_network.png', classNames: '', maxWidth: 160, }, { title: this.props.translate.get(Key.BenefitTwoTitle, Deco.Cap), description: this.props.translate.get(Key.BenefitTwoDescription, Deco.Cap), imageUrl: '/images/landing/liquidity.png', classNames: 'mx-auto', maxWidth: 160, }, { title: this.props.translate.get(Key.BenefitThreeTitle, Deco.Cap), description: this.props.translate.get(Key.BenefitThreeDescription, Deco.Cap), imageUrl: '/images/landing/exchange_everywhere.png', classNames: 'right', maxWidth: 130, }, ]; const boxes = _.map(boxContents, (boxContent: BoxContent) => { return (
{boxContent.title}
{boxContent.description}
); }); return (
{this.props.translate.get(Key.BenefitsHeader, Deco.Upper)}
{boxes}
); } private _getUseCases(): UseCase[] { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const isEnglish = this.props.translate.getLanguage() === Language.English; if (isEnglish) { return [ { imageUrl: '/images/landing/governance_icon.png', type: this.props.translate.get(Key.GamingAndCollectables, Deco.Upper), description: this.props.translate.get(Key.GamingAndCollectablesDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/prediction_market_icon.png', type: this.props.translate.get(Key.PredictionMarkets, Deco.Upper), description: this.props.translate.get(Key.PredictionMarketsDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/fund_management_icon.png', type: this.props.translate.get(Key.OrderBooks, Deco.Upper), description: this.props.translate.get(Key.OrderBooksDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/loans_icon.png', type: this.props.translate.get(Key.DecentralizedLoans, Deco.Upper), description: this.props.translate.get(Key.DecentralizedLoansDescription, Deco.Cap), classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', style: { width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0, }, }, { imageUrl: '/images/landing/stable_tokens_icon.png', type: this.props.translate.get(Key.StableTokens, Deco.Upper), description: this.props.translate.get(Key.StableTokensDescription, Deco.Cap), classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, }, ]; } else { return [ { imageUrl: '/images/landing/governance_icon.png', type: this.props.translate.get(Key.DecentralizedGovernance, Deco.Upper), description: this.props.translate.get(Key.DecentralizedGovernanceDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/prediction_market_icon.png', type: this.props.translate.get(Key.PredictionMarkets, Deco.Upper), description: this.props.translate.get(Key.PredictionMarketsDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/stable_tokens_icon.png', type: this.props.translate.get(Key.StableTokens, Deco.Upper), description: this.props.translate.get(Key.StableTokensDescription, Deco.Cap), classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/loans_icon.png', type: this.props.translate.get(Key.DecentralizedLoans, Deco.Upper), description: this.props.translate.get(Key.DecentralizedLoansDescription, Deco.Cap), classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', style: { width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0, }, }, { imageUrl: '/images/landing/fund_management_icon.png', type: this.props.translate.get(Key.FundManagement, Deco.Upper), description: this.props.translate.get(Key.FundManagementDescription, Deco.Cap), classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, }, ]; } } private _renderUseCases(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const useCases = this._getUseCases(); const cases = _.map(useCases, (useCase: UseCase) => { const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; const useCaseBoxStyle = { color: colors.grey, border: `1px solid ${colors.grey750}`, borderRadius: 4, maxWidth: isSmallScreen ? 375 : 'none', ...style, }; const typeStyle: React.CSSProperties = { color: colors.lightGrey, fontSize: 13, textTransform: 'uppercase', fontFamily: 'Roboto Mono', fontWeight: 300, }; return (
{useCase.type}
{useCase.description}
); }); return (
{this.props.translate.get(Key.UseCasesHeader, Deco.Upper)}
{cases}
); } private _renderCallToAction(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const callToActionClassNames = 'lg-pr3 md-pr3 lg-right-align md-right-align sm-center sm-px3 h4 lg-table-cell md-table-cell'; return (
{this.props.translate.get(Key.FinalCallToAction, Deco.Cap)}
{this.props.translate.get(Key.BuildCallToAction, Deco.Cap)}
); } private _updateScreenWidth(): void { const newScreenWidth = utils.getScreenWidth(); if (newScreenWidth !== this.state.screenWidth) { this.setState({ screenWidth: newScreenWidth, }); } } } // tslint:disable:max-file-line-count