aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts')
-rw-r--r--packages/website/ts/components/documentation/docs_content_top_bar.tsx155
-rw-r--r--packages/website/ts/components/documentation/docs_logo.tsx22
-rw-r--r--packages/website/ts/components/documentation/tutorial_button.tsx74
-rw-r--r--packages/website/ts/components/dropdowns/developers_drop_down.tsx177
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx127
-rw-r--r--packages/website/ts/components/ui/container.tsx1
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx3
-rw-r--r--packages/website/ts/components/ui/link.tsx42
-rw-r--r--packages/website/ts/containers/docs_home.ts28
-rw-r--r--packages/website/ts/index.tsx2
-rw-r--r--packages/website/ts/pages/documentation/home.tsx430
-rw-r--r--packages/website/ts/types.ts21
-rw-r--r--packages/website/ts/utils/constants.ts2
-rw-r--r--packages/website/ts/utils/translate.ts7
14 files changed, 968 insertions, 123 deletions
diff --git a/packages/website/ts/components/documentation/docs_content_top_bar.tsx b/packages/website/ts/components/documentation/docs_content_top_bar.tsx
new file mode 100644
index 000000000..dede6f636
--- /dev/null
+++ b/packages/website/ts/components/documentation/docs_content_top_bar.tsx
@@ -0,0 +1,155 @@
+import { colors } from '@0xproject/react-shared';
+import * as _ from 'lodash';
+import Drawer from 'material-ui/Drawer';
+import * as React from 'react';
+import { DocsLogo } from 'ts/components/documentation/docs_logo';
+import { Container } from 'ts/components/ui/container';
+import { Link } from 'ts/components/ui/link';
+import { Deco, Key, WebsitePaths } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { Translate } from 'ts/utils/translate';
+
+export interface DocsContentTopBarProps {
+ location: Location;
+ translate: Translate;
+}
+
+interface DocsContentTopBarState {
+ isDrawerOpen: boolean;
+}
+
+interface MenuItemInfo {
+ title: string;
+ url: string;
+ iconUrl: string;
+ textStyle: React.CSSProperties;
+}
+
+export class DocsContentTopBar extends React.Component<DocsContentTopBarProps, DocsContentTopBarState> {
+ constructor(props: DocsContentTopBarProps) {
+ super(props);
+ this.state = {
+ isDrawerOpen: false,
+ };
+ }
+ public componentWillReceiveProps(nextProps: DocsContentTopBarProps): void {
+ if (nextProps.location.pathname !== this.props.location.pathname) {
+ this.setState({
+ isDrawerOpen: false,
+ });
+ }
+ }
+ public render(): React.ReactNode {
+ const menuItemInfos: MenuItemInfo[] = [
+ {
+ title: this.props.translate.get(Key.Github, Deco.Cap),
+ url: constants.URL_GITHUB_ORG,
+ iconUrl: '/images/developers/github_icon.svg',
+ textStyle: { color: colors.linkSectionGrey },
+ },
+ {
+ title: this.props.translate.get(Key.Forum, Deco.Cap),
+ url: constants.URL_FORUM,
+ iconUrl: '/images/developers/forum_icon.svg',
+ textStyle: { color: colors.linkSectionGrey },
+ },
+ {
+ title: this.props.translate.get(Key.LiveChat, Deco.Cap),
+ url: constants.URL_ZEROEX_CHAT,
+ iconUrl: '/images/developers/chat_icon.svg',
+ textStyle: { color: colors.lightLinkBlue, fontWeight: 'bold' },
+ },
+ ];
+ return (
+ <Container height={75} className="pb2 mb3">
+ <Container className="flex items-center lg-pt3 md-pt3 sm-pt1 relative" width="100%">
+ <div className="col col-2 sm-hide xs-hide">
+ <Link
+ to={WebsitePaths.Home}
+ style={{ color: colors.linkSectionGrey }}
+ className="flex items-center text-decoration-none"
+ >
+ <i className="zmdi zmdi-chevron-left bold" style={{ fontSize: 16 }} />
+ <div className="pl1" style={{ fontSize: 16 }}>
+ 0xproject.com
+ </div>
+ </Link>
+ </div>
+ <div className="col col-4 md-hide sm-hide xs-hide" />
+ <div className="col col-6 md-pl4 md-ml4 sm-hide xs-hide">
+ <div className="flex items-center justify-between right" style={{ width: 300 }}>
+ {this._renderMenuItems(menuItemInfos)}
+ </div>
+ </div>
+ <div className="lg-hide md-hide">
+ <DocsLogo height={30} containerStyle={{ paddingTop: 6, paddingLeft: 18 }} />
+ </div>
+ <div className="md-hide lg-hide absolute" style={{ right: 18, top: 12 }}>
+ <div
+ style={{
+ fontSize: 30,
+ color: 'black',
+ cursor: 'pointer',
+ }}
+ >
+ <i
+ className="zmdi zmdi-menu"
+ style={{ color: colors.grey700 }}
+ onClick={this._onMenuButtonClick.bind(this)}
+ />
+ </div>
+ </div>
+ </Container>
+ <div
+ style={{
+ width: '100%',
+ height: 1,
+ backgroundColor: colors.grey300,
+ marginTop: 11,
+ }}
+ />
+ {this._renderDrawer()}
+ </Container>
+ );
+ }
+ private _renderMenuItems(menuItemInfos: MenuItemInfo[]): React.ReactNode {
+ const menuItems = _.map(menuItemInfos, menuItemInfo => {
+ return (
+ <a
+ key={`menu-item-${menuItemInfo.title}`}
+ href={menuItemInfo.url}
+ target="_blank"
+ className="text-decoration-none"
+ style={{
+ fontSize: 16,
+ }}
+ >
+ <div className="flex">
+ <img src={menuItemInfo.iconUrl} width="18" />
+ <div className="flex items-center" style={{ ...menuItemInfo.textStyle, paddingLeft: 4 }}>
+ {menuItemInfo.title}
+ </div>
+ </div>
+ </a>
+ );
+ });
+ return menuItems;
+ }
+ private _renderDrawer(): React.ReactNode {
+ return (
+ <Drawer
+ open={this.state.isDrawerOpen}
+ docked={false}
+ openSecondary={true}
+ onRequestChange={this._onMenuButtonClick.bind(this)}
+ >
+ <div className="clearfix">TODO</div>
+ </Drawer>
+ );
+ }
+ private _onMenuButtonClick(): void {
+ this.setState({
+ isDrawerOpen: !this.state.isDrawerOpen,
+ });
+ }
+}
diff --git a/packages/website/ts/components/documentation/docs_logo.tsx b/packages/website/ts/components/documentation/docs_logo.tsx
new file mode 100644
index 000000000..570a81bca
--- /dev/null
+++ b/packages/website/ts/components/documentation/docs_logo.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+import { WebsitePaths } from 'ts/types';
+
+export interface DocsLogoProps {
+ height: number;
+ containerStyle?: React.CSSProperties;
+}
+
+export const DocsLogo: React.StatelessComponent<DocsLogoProps> = props => {
+ return (
+ <div style={props.containerStyle}>
+ <Link to={`${WebsitePaths.Docs}`} className="text-decoration-none">
+ <img src="/images/docs_logo.svg" height={props.height} />
+ </Link>
+ </div>
+ );
+};
+
+DocsLogo.defaultProps = {
+ containerStyle: {},
+};
diff --git a/packages/website/ts/components/documentation/tutorial_button.tsx b/packages/website/ts/components/documentation/tutorial_button.tsx
new file mode 100644
index 000000000..0cb7d9de5
--- /dev/null
+++ b/packages/website/ts/components/documentation/tutorial_button.tsx
@@ -0,0 +1,74 @@
+import { colors } from '@0xproject/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+import { Text } from 'ts/components/ui/text';
+import { Deco, Key, TutorialInfo } from 'ts/types';
+import { Translate } from 'ts/utils/translate';
+
+export interface TutorialButtonProps {
+ translate: Translate;
+ tutorialInfo: TutorialInfo;
+}
+
+export interface TutorialButtonState {
+ isHovering: boolean;
+}
+
+export class TutorialButton extends React.Component<TutorialButtonProps, TutorialButtonState> {
+ constructor(props: TutorialButtonProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render(): React.ReactNode {
+ return (
+ <Link
+ to={this.props.tutorialInfo.location}
+ style={{ textDecoration: 'none' }}
+ onMouseEnter={this._onHover.bind(this)}
+ onMouseLeave={this._onHoverOff.bind(this)}
+ >
+ <div
+ className="flex relative"
+ style={{
+ borderRadius: 4,
+ border: `1px solid ${this.state.isHovering ? colors.lightLinkBlue : '#dfdfdf'}`,
+ padding: 20,
+ marginBottom: 15,
+ backgroundColor: this.state.isHovering ? '#e7f1fd' : colors.white,
+ }}
+ >
+ <div>
+ <img src={this.props.tutorialInfo.iconUrl} height={40} />
+ </div>
+ <div className="pl2">
+ <Text Tag="div" fontSize="18" fontColor={colors.lightLinkBlue} fontWeight="bold">
+ {this.props.translate.get(this.props.tutorialInfo.title as Key, Deco.Cap)}
+ </Text>
+ <Text Tag="div" fontColor="#555555" fontSize="16">
+ {this.props.translate.get(this.props.tutorialInfo.description as Key, Deco.Cap)}
+ </Text>
+ </div>
+ <div className="absolute" style={{ top: 31, right: 31 }}>
+ <i
+ className="zmdi zmdi-chevron-right bold"
+ style={{ fontSize: 26, color: colors.lightLinkBlue }}
+ />
+ </div>
+ </div>
+ </Link>
+ );
+ }
+ private _onHover(_event: React.FormEvent<HTMLInputElement>): void {
+ this.setState({
+ isHovering: true,
+ });
+ }
+ private _onHoverOff(): void {
+ this.setState({
+ isHovering: false,
+ });
+ }
+}
diff --git a/packages/website/ts/components/dropdowns/developers_drop_down.tsx b/packages/website/ts/components/dropdowns/developers_drop_down.tsx
new file mode 100644
index 000000000..0af211bc1
--- /dev/null
+++ b/packages/website/ts/components/dropdowns/developers_drop_down.tsx
@@ -0,0 +1,177 @@
+import { colors } from '@0xproject/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { DropDown } from 'ts/components/ui/drop_down';
+import { Link } from 'ts/components/ui/link';
+import { Deco, Key, ObjectMap, WebsitePaths } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { Translate } from 'ts/utils/translate';
+
+interface LinkInfo {
+ link: string;
+ shouldOpenInNewTab?: boolean;
+}
+
+const gettingStartedKeyToLinkInfo1: ObjectMap<LinkInfo> = {
+ [Key.BuildARelayer]: {
+ link: `${WebsitePaths.Wiki}#Build-A-Relayer`,
+ },
+ [Key.OrderBasics]: {
+ link: `${WebsitePaths.Wiki}#Create,-Validate,-Fill-Order`,
+ },
+};
+const gettingStartedKeyToLinkInfo2: ObjectMap<LinkInfo> = {
+ [Key.DevelopOnEthereum]: {
+ link: `${WebsitePaths.Wiki}#Ethereum-Development`,
+ },
+ [Key.UseSharedLiquidity]: {
+ link: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`,
+ },
+};
+const popularDocsToLinkInfos: ObjectMap<LinkInfo> = {
+ [Key.ZeroExJs]: {
+ link: WebsitePaths.ZeroExJs,
+ },
+ [Key.Connect]: {
+ link: WebsitePaths.Connect,
+ },
+ [Key.SmartContract]: {
+ link: WebsitePaths.SmartContracts,
+ },
+};
+const usefulLinksToLinkInfo: ObjectMap<LinkInfo> = {
+ [Key.Github]: {
+ link: constants.URL_GITHUB_ORG,
+ shouldOpenInNewTab: true,
+ },
+ [Key.Whitepaper]: {
+ link: WebsitePaths.Whitepaper,
+ shouldOpenInNewTab: true,
+ },
+ [Key.Sandbox]: {
+ link: constants.URL_SANDBOX,
+ shouldOpenInNewTab: true,
+ },
+};
+
+interface DevelopersDropDownProps {
+ translate: Translate;
+ menuItemStyles: React.CSSProperties;
+ menuIconStyle: React.CSSProperties;
+}
+
+interface DevelopersDropDownState {}
+
+export class DevelopersDropDown extends React.Component<DevelopersDropDownProps, DevelopersDropDownState> {
+ public render(): React.ReactNode {
+ const activeNode = (
+ <div className="flex relative" style={{ color: this.props.menuIconStyle.color }}>
+ <div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div>
+ </div>
+ );
+ return (
+ <DropDown
+ activeNode={activeNode}
+ popoverContent={this._renderDropdownMenu()}
+ anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'left', vertical: 'top' }}
+ style={this.props.menuItemStyles}
+ popoverStyle={{ borderRadius: 4, width: 427, height: 373, marginTop: 10 }}
+ />
+ );
+ }
+ private _renderDropdownMenu(): React.ReactNode {
+ const dropdownMenu = (
+ <div>
+ <div style={{ padding: '1.75rem' }}>
+ {this._renderTitle('Getting started')}
+ <div className="flex">
+ <div className="pr4 mr2">{this._renderLinkSection(gettingStartedKeyToLinkInfo1)}</div>
+ <div>{this._renderLinkSection(gettingStartedKeyToLinkInfo2)}</div>
+ </div>
+ </div>
+ <div
+ style={{
+ width: '100%',
+ height: 1,
+ backgroundColor: colors.grey300,
+ }}
+ />
+ <div className="flex" style={{ padding: '1.75rem' }}>
+ <div className="pr4 mr2">
+ <div>{this._renderTitle('Popular docs')}</div>
+ <div>{this._renderLinkSection(popularDocsToLinkInfos)}</div>
+ </div>
+ <div>
+ <div>{this._renderTitle('Useful links')}</div>
+ <div>{this._renderLinkSection(usefulLinksToLinkInfo)}</div>
+ </div>
+ </div>
+ <div
+ style={{
+ padding: '1.125rem',
+ textAlign: 'center',
+ backgroundColor: colors.lightBgGrey,
+ borderBottomLeftRadius: 4,
+ borderBottomRightRadius: 4,
+ }}
+ >
+ <Link
+ to={WebsitePaths.Docs}
+ className="text-decoration-none"
+ style={{
+ color: colors.lightBlueA700,
+ fontWeight: 'bold',
+ fontSize: 14,
+ }}
+ >
+ {this.props.translate.get(Key.ViewAllDocumentation, Deco.Upper)}
+ </Link>
+ </div>
+ </div>
+ );
+ return dropdownMenu;
+ }
+ private _renderTitle(title: string): React.ReactNode {
+ return (
+ <div
+ style={{
+ color: colors.linkSectionGrey,
+ fontSize: 14,
+ paddingBottom: 12,
+ fontWeight: 600,
+ letterSpacing: 1,
+ }}
+ >
+ {title.toUpperCase()}
+ </div>
+ );
+ }
+ private _renderLinkSection(keyToLinkInfo: ObjectMap<LinkInfo>): React.ReactNode {
+ const linkStyle: React.CSSProperties = {
+ color: colors.lightBlueA700,
+ fontFamily: 'Roboto, Roboto Mono',
+ };
+ const numLinks = _.size(keyToLinkInfo);
+ let i = 0;
+ const links = _.map(keyToLinkInfo, (linkInfo: LinkInfo, key: string) => {
+ i++;
+ const isLast = i === numLinks;
+ const linkText = this.props.translate.get(key as Key, Deco.Cap);
+ return (
+ <div className={`pr1 pt1 ${!isLast && 'pb1'}`} key={`dev-dropdown-link-${key}`}>
+ <Link
+ to={linkInfo.link}
+ isExternal={!!linkInfo.shouldOpenInNewTab}
+ shouldOpenInNewTab={linkInfo.shouldOpenInNewTab}
+ className="text-decoration-none"
+ style={linkStyle}
+ >
+ {linkText}
+ </Link>
+ </div>
+ );
+ });
+ return <div>{links}</div>;
+ }
+}
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index bb61e4fb9..c2d753e31 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -8,16 +8,15 @@ import {
} from '@0xproject/react-shared';
import * as _ from 'lodash';
import Drawer from 'material-ui/Drawer';
-import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
import * as React from 'react';
import { Link } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
+import { DevelopersDropDown } from 'ts/components/dropdowns/developers_drop_down';
import { DrawerMenu } from 'ts/components/portal/drawer_menu';
import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
import { Container } from 'ts/components/ui/container';
-import { DropDown } from 'ts/components/ui/drop_down';
import { Dispatcher } from 'ts/redux/dispatcher';
import { Deco, Key, ProviderType, WebsitePaths } from 'ts/types';
import { constants } from 'ts/utils/constants';
@@ -129,111 +128,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
? 'flex mx-auto items-center max-width-4'
: 'flex mx-auto items-center';
const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
- const developerSectionMenuItems = [
- <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none">
- <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" />
- </Link>,
- <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.SmartContract, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.Connect, Deco.CapWords)}
- />
- </Link>,
- <a
- key="subMenuItem-standard-relayer-api"
- target="_blank"
- className="text-decoration-none"
- href={constants.URL_STANDARD_RELAYER_API_GITHUB}
- >
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.StandardRelayerApi, Deco.CapWords)}
- />
- </a>,
- <Link key="subMenuItem-jsonSchema" to={WebsitePaths.JSONSchemas} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.JsonSchemas, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-subproviders" to={WebsitePaths.Subproviders} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.Subproviders, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-web3Wrapper" to={WebsitePaths.Web3Wrapper} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.Web3Wrapper, Deco.CapWords)}
- />
- </Link>,
- <Link
- key="subMenuItem-contractWrappers"
- to={WebsitePaths.ContractWrappers}
- className="text-decoration-none"
- >
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.ContractWrappers, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-orderUtils" to={WebsitePaths.OrderUtils} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.OrderUtils, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-orderWatcher" to={WebsitePaths.OrderWatcher} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.OrderWatcher, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-sol-compiler" to={WebsitePaths.SolCompiler} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.SolCompiler, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-sol-cov" to={WebsitePaths.SolCov} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.SolCov, Deco.CapWords)}
- />
- </Link>,
- <Link key="subMenuItem-ethereum-types" to={WebsitePaths.EthereumTypes} className="text-decoration-none">
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.EthereumTypes, Deco.CapWords)}
- />
- </Link>,
- <a
- key="subMenuItem-whitePaper"
- target="_blank"
- className="text-decoration-none"
- href={`${WebsitePaths.Whitepaper}`}
- >
- <MenuItem
- style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.Whitepaper, Deco.CapWords)}
- />
- </a>,
- <a
- key="subMenuItem-github"
- target="_blank"
- className="text-decoration-none"
- href={constants.URL_GITHUB_ORG}
- >
- <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" />
- </a>,
- ];
const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
const fullWidthClasses = isExpandedDisplayType ? 'pr4' : '';
const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
@@ -245,15 +139,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
color: isNightVersion ? 'white' : 'black',
cursor: 'pointer',
};
- const activeNode = (
- <div className="flex relative" style={{ color: menuIconStyle.color }}>
- <div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div>
- <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
- <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
- </div>
- </div>
- );
- const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
return (
<div
style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }}
@@ -273,12 +158,10 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
{!this._isViewingPortal() && (
<div className={menuClasses}>
<div className="flex items-center justify-between">
- <DropDown
- activeNode={activeNode}
- popoverContent={popoverContent}
- anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
- targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
- style={styles.menuItem}
+ <DevelopersDropDown
+ menuItemStyles={styles.menuItem}
+ translate={this.props.translate}
+ menuIconStyle={menuIconStyle}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.Wiki, Deco.Cap)}
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index f2ae68b70..1e0bfd959 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -15,6 +15,7 @@ export interface ContainerProps {
paddingRight?: StringOrNum;
paddingLeft?: StringOrNum;
backgroundColor?: string;
+ background?: string;
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
maxHeight?: StringOrNum;
diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx
index 4d5caef08..1daaeb1c3 100644
--- a/packages/website/ts/components/ui/drop_down.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -21,6 +21,7 @@ export interface DropDownProps {
zDepth?: number;
activateEvent?: DropdownMouseEvent;
closeEvent?: DropdownMouseEvent;
+ popoverStyle?: React.CSSProperties;
}
interface DropDownState {
@@ -34,6 +35,7 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
zDepth: 1,
activateEvent: DropdownMouseEvent.Hover,
closeEvent: DropdownMouseEvent.Hover,
+ popoverStyle: {},
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
@@ -77,6 +79,7 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click}
animated={false}
zDepth={this.props.zDepth}
+ style={this.props.popoverStyle}
>
<div
onMouseEnter={this._onHover.bind(this)}
diff --git a/packages/website/ts/components/ui/link.tsx b/packages/website/ts/components/ui/link.tsx
new file mode 100644
index 000000000..252199457
--- /dev/null
+++ b/packages/website/ts/components/ui/link.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+import { Link as ReactRounterLink } from 'react-router-dom';
+
+export interface LinkProps {
+ to: string;
+ isExternal?: boolean;
+ shouldOpenInNewTab?: boolean;
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+export const Link: React.StatelessComponent<LinkProps> = ({
+ style,
+ className,
+ isExternal,
+ to,
+ shouldOpenInNewTab,
+ children,
+}) => {
+ if (isExternal) {
+ return (
+ <a target={shouldOpenInNewTab && '_blank'} className={className} style={style} href={to}>
+ {children}
+ </a>
+ );
+ } else {
+ return (
+ <ReactRounterLink to={to} className={className} style={style}>
+ {children}
+ </ReactRounterLink>
+ );
+ }
+};
+
+Link.defaultProps = {
+ isExternal: false,
+ shouldOpenInNewTab: false,
+ style: {},
+ className: '',
+};
+
+Link.displayName = 'Link';
diff --git a/packages/website/ts/containers/docs_home.ts b/packages/website/ts/containers/docs_home.ts
new file mode 100644
index 000000000..9c7b70a6f
--- /dev/null
+++ b/packages/website/ts/containers/docs_home.ts
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import { Home as HomeComponent, HomeProps } from 'ts/pages/documentation/home';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { State } from 'ts/redux/reducer';
+import { ScreenWidths } from 'ts/types';
+import { Translate } from 'ts/utils/translate';
+
+interface ConnectedState {
+ translate: Translate;
+ screenWidth: ScreenWidths;
+}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, _ownProps: HomeProps): ConnectedState => ({
+ translate: state.translate,
+ screenWidth: state.screenWidth,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const DocsHome: React.ComponentClass<HomeProps> = connect(mapStateToProps, mapDispatchToProps)(HomeComponent);
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index 9e59b00ac..c476f2358 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import { MetaTags } from 'ts/components/meta_tags';
import { About } from 'ts/containers/about';
+import { DocsHome } from 'ts/containers/docs_home';
import { FAQ } from 'ts/containers/faq';
import { Jobs } from 'ts/containers/jobs';
import { Landing } from 'ts/containers/landing';
@@ -133,6 +134,7 @@ render(
path={`${WebsitePaths.EthereumTypes}/:version?`}
component={LazyEthereumTypesDocumentation}
/>
+ <Route path={WebsitePaths.Docs} component={DocsHome as any} />
{/* Legacy endpoints */}
<Route
diff --git a/packages/website/ts/pages/documentation/home.tsx b/packages/website/ts/pages/documentation/home.tsx
new file mode 100644
index 000000000..cf2ba0eec
--- /dev/null
+++ b/packages/website/ts/pages/documentation/home.tsx
@@ -0,0 +1,430 @@
+import { colors, NestedSidebarMenu } from '@0xproject/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import { DocsContentTopBar } from 'ts/components/documentation/docs_content_top_bar';
+import { DocsLogo } from 'ts/components/documentation/docs_logo';
+import { TutorialButton } from 'ts/components/documentation/tutorial_button';
+import { Container } from 'ts/components/ui/container';
+import { Link } from 'ts/components/ui/link';
+import { Text } from 'ts/components/ui/text';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { Deco, Key, ScreenWidths, TutorialInfo, WebsitePaths } from 'ts/types';
+import { Translate } from 'ts/utils/translate';
+import { utils } from 'ts/utils/utils';
+
+interface Package {
+ name: string;
+ description: string;
+ to: string;
+ isExternal?: boolean;
+ shouldOpenInNewTab?: boolean;
+}
+
+const THROTTLE_TIMEOUT = 100;
+const TUTORIALS: TutorialInfo[] = [
+ {
+ title: Key.DevelopOnEthereum,
+ iconUrl: '/images/developers/tutorials/develop_on_ethereum.svg',
+ description: Key.DevelopOnEthereumDescription,
+ location: `${WebsitePaths.Wiki}#Ethereum-Development`,
+ },
+ {
+ title: Key.BuildARelayer,
+ iconUrl: '/images/developers/tutorials/build_a_relayer.svg',
+ description: Key.BuildARelayerDescription,
+ location: `${WebsitePaths.Wiki}#Build-A-Relayer`,
+ },
+ {
+ title: Key.OrderBasics,
+ iconUrl: '/images/developers/tutorials/0x_order_basics.svg',
+ description: Key.OrderBasicsDescription,
+ location: `${WebsitePaths.Wiki}#Create,-Validate,-Fill-Order`,
+ },
+ {
+ title: Key.UseSharedLiquidity,
+ iconUrl: '/images/developers/tutorials/use_shared_liquidity.svg',
+ description: Key.UseSharedLiquidityDescription,
+ location: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`,
+ },
+];
+const CATEGORY_TO_PACKAGES: { [category: string]: Package[] } = {
+ '0x Protocol': [
+ {
+ name: '0x.js',
+ description:
+ 'A library for interacting with the 0x protocol. It is a high level package which combines a number of smaller specific-purpose packages such as `order-utils` and `contract-wrappers`.',
+ to: WebsitePaths.ZeroExJs,
+ },
+ {
+ name: '0x starter project',
+ description:
+ 'A Typescript starter project that will walk you through the basics of how to interact with 0x Protocol and trade of an SRA relayer',
+ to: 'https://github.com/0xProject/0x-starter-project',
+ isExternal: true,
+ shouldOpenInNewTab: true,
+ },
+ {
+ name: '@0xproject/connect',
+ description:
+ 'An http & websocket client for interacting with relayers that have implemented the Standard Relayer API',
+ to: WebsitePaths.Connect,
+ },
+ {
+ name: '@0xproject/contract-wrappers',
+ description:
+ 'Typescript/Javascript wrappers of the 0x protocol Ethereum smart contracts. Use this library to call methods on the 0x smart contracts, subscribe to contract events and to fetch information stored in the contracts.',
+ to: WebsitePaths.ContractWrappers,
+ },
+ {
+ name: '@0xproject/json-schemas',
+ description:
+ 'A collection of 0x-related JSON-schemas (incl. SRA request/response schemas, 0x order message format schema, etc...)',
+ to: WebsitePaths.JSONSchemas,
+ },
+ {
+ name: '@0xproject/order-utils',
+ description:
+ 'A set of utils for working with 0x orders. It includes utilities for creating, signing, validating 0x orders, encoding/decoding assetData and much more.',
+ to: WebsitePaths.OrderUtils,
+ },
+ {
+ name: '@0xproject/order-watcher',
+ description:
+ "A daemon that watches a set of 0x orders and emits events when an order's fillability has changed. Can be used by a relayer to prune their orderbook or by a trader to keep their view of the market up-to-date.",
+ to: WebsitePaths.OrderWatcher,
+ },
+ {
+ name: '@0xproject/sra-spec',
+ description:
+ 'Contains the Standard Relayer API OpenAPI Spec. The package distributes both a javascript object version and a json version.',
+ to: 'https://github.com/0xProject/0x-monorepo/tree/development/packages/sra-spec',
+ isExternal: true,
+ shouldOpenInNewTab: true,
+ },
+ ],
+ Ethereum: [
+ {
+ name: 'abi-gen',
+ description:
+ "This package allows you to generate TypeScript contract wrappers from ABI files. It's heavily inspired by Geth abigen but takes a different approach. You can write your custom handlebars templates which will allow you to seamlessly integrate the generated code into your existing codebase with existing conventions.",
+ to: 'https://github.com/0xProject/0x-monorepo/tree/development/packages/abi-gen',
+ isExternal: true,
+ shouldOpenInNewTab: true,
+ },
+ {
+ name: 'ethereum-types',
+ description:
+ 'A collection of Typescript types that are useful when working on an Ethereum-based project (e.g RawLog, Transaction, TxData, SolidityTypes, etc...).',
+ to: WebsitePaths.EthereumTypes,
+ },
+ {
+ name: '@0xproject/sol-compiler',
+ description:
+ 'A wrapper around `solc-js` that adds smart re-compilation, ability to compile an entire project, Solidity version specific compilation, standard input description support and much more.',
+ to: WebsitePaths.SolCompiler,
+ },
+ {
+ name: '@0xproject/sol-cov',
+ description:
+ 'A Solidity code coverage tool. Sol-cov uses transaction traces to figure out which lines of your code has been covered by your tests.',
+ to: WebsitePaths.SolCov,
+ },
+ {
+ name: '@0xproject/subproviders',
+ description:
+ 'A collection of subproviders to use with `web3-provider-engine` (e.g subproviders for interfacing with Ledger hardware wallet, Mnemonic wallet, private key wallet, etc...)',
+ to: WebsitePaths.Subproviders,
+ },
+ {
+ name: '@0xproject/web3-wrapper',
+ description:
+ 'A raw Ethereum JSON RPC client to simplify interfacing with Ethereum nodes. Also includes some convenience functions for awaiting transactions to be mined, converting between token units, etc...',
+ to: WebsitePaths.Web3Wrapper,
+ },
+ ],
+ 'Community Maintained': [
+ {
+ name: '0x Event Extractor',
+ description:
+ 'NodeJS worker originally built for 0x Tracker which extracts 0x fill events from the Ethereum blockchain and persists them to MongoDB. Support for both V1 and V2 of the 0x protocol is included with events tagged against the protocol version they belong to.',
+ to: 'https://github.com/0xTracker/0x-event-extractor',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: '0x Tracker Worker',
+ description:
+ 'NodeJS worker built for 0x Tracker which performs various ETL tasks related to the 0x protocol trading data and other information used on 0x Tracker.',
+ to: 'https://github.com/0xTracker/0x-tracker-worker',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'Aquaduct',
+ description:
+ "ERCdex's Javascript SDK for trading on their relayer, as well as other Aquaduct partner relayers",
+ to: 'https://www.npmjs.com/package/aqueduct',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'Aquaduct Server SDK',
+ description:
+ 'SDKs for automation using Aqueduct & ERC dEX. Aqueduct Server is a lightweight, portable and secure server that runs locally on any workstation. The server exposes a small number of foundational endpoints that enable working with the decentralized Aqueduct liquidity pool from any context or programming language.',
+ to: 'https://github.com/ERCdEX/aqueduct-server-sdk',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'DDEX Node.js SDK',
+ description: 'A node.js SDK for trading on the DDEX relayer',
+ to: 'https://www.npmjs.com/package/ddex-api',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'ERCdex Widget',
+ description: "The ERC dEX Trade Widget let's any website provide token liquidity to it's users",
+ to: 'https://github.com/ERCdEX/widget',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'ERCdex Java SDK',
+ description: "ERCdex's Java SDK for trading on their relayer, as well as other Aquaduct partner relayers",
+ to: 'https://github.com/ERCdEX/java',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'ERCdex Python SDK',
+ description: "ERCdex's Python SDK for trading on their relayer, as well as other Aquaduct partner relayers",
+ to: 'https://github.com/ERCdEX/python',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'Massive',
+ description:
+ 'A set of command-line tools for creating command-line scripts for interacting with the Ethereum blockchain in general, and 0x in particular',
+ to: 'https://github.com/NoteGio/massive',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'OpenRelay',
+ description: 'An open-source API-only Relayer written in Go',
+ to: 'https://github.com/NoteGio/openrelay',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'OpenRelay.js',
+ description:
+ 'A JavaScript Library for Interacting with OpenRelay.xyz and other 0x Standard Relayer API Implementations',
+ to: 'https://github.com/NoteGio/openrelay.js',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'Radar SDK',
+ description:
+ 'The Radar Relay SDK is a software development kit that simplifies the interactions with Radar Relay’s APIs',
+ to: 'https://github.com/RadarRelay/sdk',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'The Ocean Javascript SDK',
+ description:
+ 'The Ocean provides a simple REST API, WebSockets API, and JavaScript library to help you integrate decentralized trading into your existing trading strategy.',
+ to: 'https://github.com/TheOceanTrade/theoceanx-javascript',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'Tokenlon Javascript SDK',
+ description: "Tokenlon SDK provides APIs for developers to trade of imToken's relayer",
+ to: 'https://www.npmjs.com/package/tokenlon-sdk',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ {
+ name: 'AssetData decoder library in Java',
+ description: 'A small library that implements the 0x order assetData encoding/decoding in Java',
+ to: 'https://github.com/wildnothing/asset-data-decoder',
+ shouldOpenInNewTab: true,
+ isExternal: true,
+ },
+ ],
+};
+
+export interface HomeProps {
+ location: Location;
+ translate: Translate;
+ screenWidth: ScreenWidths;
+ dispatcher: Dispatcher;
+}
+
+export interface HomeState {}
+
+export class Home extends React.Component<HomeProps, HomeState> {
+ private readonly _throttledScreenWidthUpdate: () => void;
+ constructor(props: HomeProps) {
+ super(props);
+ 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 {
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
+ const mainContentPadding = isSmallScreen ? 0 : 50;
+ return (
+ <Container
+ className="flex items-center"
+ width="100%"
+ background={`linear-gradient(to right, ${colors.grey100} 0%, ${colors.grey100} 50%, ${
+ colors.white
+ } 50%, ${colors.white} 100%)`}
+ >
+ <DocumentTitle title="0x Docs Home" />
+ <div className="flex mx-auto">
+ <Container
+ className="sm-hide xs-hide"
+ width={234}
+ paddingLeft={22}
+ paddingRight={22}
+ paddingTop={2}
+ backgroundColor={colors.grey100}
+ >
+ <DocsLogo height={36} containerStyle={{ paddingTop: 28 }} />
+ </Container>
+ <Container
+ width={isSmallScreen ? '100vw' : 716}
+ paddingBottom="100px"
+ paddingLeft={mainContentPadding}
+ paddingRight={mainContentPadding}
+ backgroundColor={colors.white}
+ >
+ <DocsContentTopBar location={this.props.location} translate={this.props.translate} />
+ <div>
+ {this._renderSectionTitle('Start building on 0x')}
+ <Container paddingTop="12px">
+ {this._renderSectionDescription(
+ 'Follow one of our "Getting started" guides to learn more about building ontop of 0x.',
+ )}
+ <Container marginTop="36px">
+ {_.map(TUTORIALS, tutorialInfo => (
+ <TutorialButton
+ translate={this.props.translate}
+ tutorialInfo={tutorialInfo}
+ key={`tutorial-${tutorialInfo.title}`}
+ />
+ ))}
+ </Container>
+ </Container>
+ </div>
+ <div className="mt4">
+ {this._renderSectionTitle(this.props.translate.get(Key.LibrariesAndTools, Deco.CapWords))}
+ <Container paddingTop="12px">
+ {this._renderSectionDescription(
+ 'A list of available tools maintained by the 0x core developers and wider community for building on top of 0x Protocol and Ethereum',
+ )}
+ <Container marginTop="36px">
+ {_.map(CATEGORY_TO_PACKAGES, (pkgs, category) =>
+ this._renderPackageCategory(category, pkgs),
+ )}
+ </Container>
+ </Container>
+ </div>
+ </Container>
+ </div>
+ </Container>
+ );
+ }
+ private _renderPackageCategory(category: string, pkgs: Package[]): React.ReactNode {
+ return (
+ <div key={`category-${category}`}>
+ <Text fontSize="18px">{category}</Text>
+ <div>{_.map(pkgs, pkg => this._renderPackage(pkg))}</div>
+ </div>
+ );
+ }
+ private _renderPackage(pkg: Package): React.ReactNode {
+ return (
+ <div className="pb2" key={`package-${pkg.name}`}>
+ <div
+ style={{
+ width: '100%',
+ height: 1,
+ backgroundColor: colors.grey300,
+ marginTop: 11,
+ }}
+ />
+ <div className="clearfix mt2 pt1">
+ <div className="col col-4">
+ <Link
+ to={pkg.to}
+ className="text-decoration-none"
+ style={{ color: colors.lightLinkBlue }}
+ isExternal={!!pkg.isExternal}
+ shouldOpenInNewTab={!!pkg.shouldOpenInNewTab}
+ >
+ <Text Tag="div" fontColor={colors.lightLinkBlue} fontWeight="bold">
+ {pkg.name}
+ </Text>
+ </Link>
+ </div>
+ <div className="col col-6">
+ <Text Tag="div" fontColor={colors.grey700}>
+ {pkg.description}
+ </Text>
+ </div>
+ <div className="col col-2 relative">
+ <Link
+ to={pkg.to}
+ className="text-decoration-none absolute"
+ style={{ right: 0, color: colors.lightLinkBlue }}
+ isExternal={!!pkg.isExternal}
+ shouldOpenInNewTab={!!pkg.shouldOpenInNewTab}
+ >
+ <div className="flex">
+ <div>{this.props.translate.get(Key.More, Deco.Cap)}</div>
+ <Container paddingTop="1px" paddingLeft="6px">
+ <i
+ className="zmdi zmdi-chevron-right bold"
+ style={{ fontSize: 18, color: colors.lightLinkBlue }}
+ />
+ </Container>
+ </div>
+ </Link>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private _renderSectionTitle(text: string): React.ReactNode {
+ return (
+ <Text fontColor="#333333" fontSize="30px" fontWeight="bold">
+ {text}
+ </Text>
+ );
+ }
+ private _renderSectionDescription(text: string): React.ReactNode {
+ return (
+ <Text fontColor="#999999" fontSize="18px" fontFamily="Roboto Mono">
+ {text}
+ </Text>
+ );
+ }
+ private _updateScreenWidth(): void {
+ const newScreenWidth = utils.getScreenWidth();
+ this.props.dispatcher.updateScreenWidth(newScreenWidth);
+ }
+}
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 4ec45c46e..eeb0efaea 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -457,6 +457,20 @@ export enum Key {
Home = 'HOME',
RocketChat = 'ROCKETCHAT',
TradeCallToAction = 'TRADE_CALL_TO_ACTION',
+ BuildARelayer = 'BUILD_A_RELAYER',
+ BuildARelayerDescription = 'BUILD_A_RELAYER_DESCRIPTION',
+ DevelopOnEthereum = 'DEVELOP_ON_ETHEREUM',
+ DevelopOnEthereumDescription = 'DEVELOP_ON_ETHEREUM_DESCRIPTION',
+ OrderBasics = 'ORDER_BASICS',
+ OrderBasicsDescription = 'ORDER_BASICS_DESCRIPTION',
+ UseSharedLiquidity = 'USE_SHARED_LIQUIDITY',
+ UseSharedLiquidityDescription = 'USE_SHARED_LIQUIDITY_DESCRIPTION',
+ ViewAllDocumentation = 'VIEW_ALL_DOCUMENTATION',
+ Sandbox = 'SANDBOX',
+ Github = 'GITHUB',
+ LiveChat = 'LIVE_CHAT',
+ LibrariesAndTools = 'LIBRARIES_AND_TOOLS',
+ More = 'MORE',
OurMissionAndValues = 'OUR_MISSION_AND_VALUES',
}
@@ -600,4 +614,11 @@ export interface InjectedWeb3 {
getNetwork(cd: (err: Error, networkId: string) => void): void;
};
}
+
+export interface TutorialInfo {
+ title: string;
+ iconUrl: string;
+ description: string;
+ location: string;
+}
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index 18a4d8100..0809fb438 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -74,6 +74,7 @@ export const constants = {
URL_TESTNET_FAUCET: 'https://faucet.0xproject.com',
URL_GITHUB_ORG: 'https://github.com/0xProject',
URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki',
+ URL_FORUM: 'https://forum.0xproject.com',
URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
URL_METAMASK_FIREFOX_STORE: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask/',
URL_COINBASE_WALLET_IOS_APP_STORE: 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8',
@@ -84,6 +85,7 @@ export const constants = {
URL_PARITY_CHROME_STORE:
'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig',
URL_REDDIT: 'https://reddit.com/r/0xproject',
+ URL_SANDBOX: 'https://codesandbox.io/s/1qmjyp7p5j',
URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md',
URL_TWITTER: 'https://twitter.com/0xproject',
URL_WETH_IO: 'https://weth.io/',
diff --git a/packages/website/ts/utils/translate.ts b/packages/website/ts/utils/translate.ts
index 1ee1a59c5..5595e6e0f 100644
--- a/packages/website/ts/utils/translate.ts
+++ b/packages/website/ts/utils/translate.ts
@@ -80,7 +80,12 @@ export class Translate {
case Deco.CapWords:
const words = text.split(' ');
- const capitalizedWords = _.map(words, w => this._capitalize(w));
+ const capitalizedWords = _.map(words, (w: string, i: number) => {
+ if (w.length === 1) {
+ return w;
+ }
+ return this._capitalize(w);
+ });
text = capitalizedWords.join(' ');
break;