aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx28
-rw-r--r--packages/instant/src/types.ts18
-rw-r--r--packages/website/package.json4
-rw-r--r--packages/website/public/index.html217
-rw-r--r--packages/website/ts/components/ui/check_mark.tsx31
-rw-r--r--packages/website/ts/components/ui/container.tsx39
-rw-r--r--packages/website/ts/components/ui/input.tsx6
-rw-r--r--packages/website/ts/components/ui/multi_select.tsx66
-rw-r--r--packages/website/ts/components/ui/select.tsx170
-rw-r--r--packages/website/ts/pages/documentation/developers_page.tsx4
-rw-r--r--packages/website/ts/pages/instant/action_link.tsx46
-rw-r--r--packages/website/ts/pages/instant/code_demo.tsx148
-rw-r--r--packages/website/ts/pages/instant/config_generator.tsx291
-rw-r--r--packages/website/ts/pages/instant/config_generator_address_input.tsx58
-rw-r--r--packages/website/ts/pages/instant/configurator.tsx93
-rw-r--r--packages/website/ts/pages/instant/features.tsx44
-rw-r--r--yarn.lock87
17 files changed, 1196 insertions, 154 deletions
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index dae9124c6..bdc531617 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -11,7 +11,14 @@ import { asyncData } from '../redux/async_data';
import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
-import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchOrigin } from '../types';
+import {
+ AccountState,
+ AffiliateInfo,
+ AssetMetaData,
+ Network,
+ QuoteFetchOrigin,
+ ZeroExInstantBaseConfig,
+} from '../types';
import { analytics, disableAnalytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
@@ -21,24 +28,7 @@ import { Heartbeater } from '../util/heartbeater';
import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory';
import { providerStateFactory } from '../util/provider_state_factory';
-export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
- Partial<ZeroExInstantProviderOptionalProps>;
-
-export interface ZeroExInstantProviderRequiredProps {
- orderSource: OrderSource;
-}
-
-export interface ZeroExInstantProviderOptionalProps {
- provider: Provider;
- walletDisplayName: string;
- availableAssetDatas: string[];
- defaultAssetBuyAmount: number;
- defaultSelectedAssetData: string;
- additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
- networkId: Network;
- affiliateInfo: AffiliateInfo;
- shouldDisableAnalyticsTracking: boolean;
-}
+export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig;
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
private readonly _store: Store;
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index 2d73ba29e..e65961e95 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -177,3 +177,21 @@ export enum ProviderType {
Cipher = 'CIPHER',
Fallback = 'FALLBACK',
}
+
+export interface ZeroExInstantRequiredBaseConfig {
+ orderSource: OrderSource;
+}
+
+export interface ZeroExInstantOptionalBaseConfig {
+ provider: Provider;
+ walletDisplayName: string;
+ availableAssetDatas: string[];
+ defaultAssetBuyAmount: number;
+ defaultSelectedAssetData: string;
+ additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
+ networkId: Network;
+ affiliateInfo: AffiliateInfo;
+ shouldDisableAnalyticsTracking: boolean;
+}
+
+export type ZeroExInstantBaseConfig = ZeroExInstantRequiredBaseConfig & Partial<ZeroExInstantOptionalBaseConfig>;
diff --git a/packages/website/package.json b/packages/website/package.json
index dc10c7b1c..c02e1eee2 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -20,6 +20,8 @@
"author": "Fabio Berger",
"license": "Apache-2.0",
"dependencies": {
+ "@0x/asset-buyer": "^3.0.2",
+ "@0x/contract-addresses": "^2.0.0",
"@0x/contract-wrappers": "^4.1.1",
"@0x/json-schemas": "^2.1.2",
"@0x/order-utils": "^3.0.4",
@@ -54,6 +56,7 @@
"react-popper": "^1.0.0-beta.6",
"react-redux": "^5.0.3",
"react-scroll": "0xproject/react-scroll#pr-330-and-replace-state",
+ "react-syntax-highlighter": "^10.1.1",
"react-tooltip": "^3.2.7",
"react-typist": "^2.0.4",
"redux": "^3.6.0",
@@ -83,6 +86,7 @@
"@types/react-helmet": "^5.0.6",
"@types/react-redux": "^4.4.37",
"@types/react-scroll": "1.5.3",
+ "@types/react-syntax-highlighter": "^0.0.8",
"@types/react-tap-event-plugin": "0.0.30",
"@types/redux": "^3.6.0",
"@types/web3-provider-engine": "^14.0.0",
diff --git a/packages/website/public/index.html b/packages/website/public/index.html
index a8a61f8ad..538eca6d9 100644
--- a/packages/website/public/index.html
+++ b/packages/website/public/index.html
@@ -1,95 +1,132 @@
<!DOCTYPE html>
<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" />
+ <meta property="og:type" content="website" />
+ <meta property="og:title" content="0x" />
+ <meta
+ property="og:description"
+ content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain"
+ />
+ <meta property="og:image" content="/images/og_image.png" />
+ <title>0x: The Protocol for Trading Tokens</title>
+ <link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" />
+ <link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" />
+ <link rel="stylesheet" href="/css/material-design-iconic-font.min.css" />
+ <link rel="stylesheet" href="/css/roboto.css" />
+ <link rel="stylesheet" href="/css/roboto_mono.css" />
+ <link rel="stylesheet" href="/css/basscss_responsive_custom.css" />
+ <link rel="stylesheet" href="/css/basscss_responsive_padding.css" />
+ <link rel="stylesheet" href="/css/basscss_responsive_margin.css" />
+ <link rel="stylesheet" href="/css/basscss_responsive_type_scale.css" />
+ </head>
-<head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" />
- <meta property="og:type" content="website" />
- <meta property="og:title" content="0x" />
- <meta property="og:description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" />
- <meta property="og:image" content="/images/og_image.png" />
- <title>0x: The Protocol for Trading Tokens</title>
- <link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" />
- <link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" />
- <link rel="stylesheet" href="/css/github-gist.css">
- <link rel="stylesheet" href="/css/material-design-iconic-font.min.css">
- <link rel="stylesheet" href="/css/roboto.css">
- <link rel="stylesheet" href="/css/roboto_mono.css">
- <link rel="stylesheet" href="/css/basscss_responsive_custom.css">
- <link rel="stylesheet" href="/css/basscss_responsive_padding.css">
- <link rel="stylesheet" href="/css/basscss_responsive_margin.css">
- <link rel="stylesheet" href="/css/basscss_responsive_type_scale.css">
-</head>
+ <body style="margin: 0px; min-width: 355px;">
+ <!-- Heap SDK -->
+ <script type="text/javascript">
+ (window.heap = window.heap || []),
+ (heap.load = function(e, t) {
+ (window.heap.appid = e), (window.heap.config = t = t || {});
+ var r = t.forceSSL || 'https:' === document.location.protocol,
+ a = document.createElement('script');
+ (a.type = 'text/javascript'),
+ (a.async = !0),
+ (a.src = (r ? 'https:' : 'http:') + '//cdn.heapanalytics.com/js/heap-' + e + '.js');
+ var n = document.getElementsByTagName('script')[0];
+ n.parentNode.insertBefore(a, n);
+ for (
+ var o = function(e) {
+ return function() {
+ heap.push([e].concat(Array.prototype.slice.call(arguments, 0)));
+ };
+ },
+ p = [
+ 'addEventProperties',
+ 'addUserProperties',
+ 'clearEventProperties',
+ 'identify',
+ 'resetIdentity',
+ 'removeEventProperty',
+ 'setEventProperties',
+ 'track',
+ 'unsetEventProperty',
+ ],
+ c = 0;
+ c < p.length;
+ c++
+ )
+ heap[p[c]] = o(p[c]);
+ });
+ heap.load('410099666');
+ </script>
+ <!-- End Heap SDK -->
+ <!-- Global site tag (gtag.js) - Google Analytics -->
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script>
+ <script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag() {
+ dataLayer.push(arguments);
+ }
+ gtag('js', new Date());
-<body style="margin: 0px; min-width: 355px;">
- <!-- Heap SDK -->
- <script type="text/javascript">
- window.heap = window.heap || [], heap.load = function (e, t) { window.heap.appid = e, window.heap.config = t = t || {}; var r = t.forceSSL || "https:" === document.location.protocol, a = document.createElement("script"); a.type = "text/javascript", a.async = !0, a.src = (r ? "https:" : "http:") + "//cdn.heapanalytics.com/js/heap-" + e + ".js"; var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(a, n); for (var o = function (e) { return function () { heap.push([e].concat(Array.prototype.slice.call(arguments, 0))) } }, p = ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"], c = 0; c < p.length; c++)heap[p[c]] = o(p[c]) };
- heap.load("410099666");
- </script>
- <!-- End Heap SDK -->
- <!-- Global site tag (gtag.js) - Google Analytics -->
- <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script>
- <script>
- window.dataLayer = window.dataLayer || [];
- function gtag() {
- dataLayer.push(arguments);
- }
- gtag('js', new Date());
+ gtag('config', 'UA-98720122-1');
+ </script>
+ <!-- End Google Analytics -->
+ <!-- Facebook SDK -->
+ <div id="fb-root"></div>
+ <script>
+ (function(d, s, id) {
+ var js,
+ fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) return;
+ js = d.createElement(s);
+ js.id = id;
+ js.src = '//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1687545238205192';
+ fjs.parentNode.insertBefore(js, fjs);
+ })(document, 'script', 'facebook-jssdk');
+ </script>
+ <div id="app"></div>
+ <!-- End Facebook SDK -->
+ <!-- Twitter SDK -->
+ <script>
+ window.twttr = (function(d, s, id) {
+ var js,
+ fjs = d.getElementsByTagName(s)[0],
+ t = window.twttr || {};
+ if (d.getElementById(id)) return t;
+ js = d.createElement(s);
+ js.id = id;
+ js.src = 'https://platform.twitter.com/widgets.js';
+ fjs.parentNode.insertBefore(js, fjs);
- gtag('config', 'UA-98720122-1');
- </script>
- <!-- End Google Analytics -->
- <!-- Facebook SDK -->
- <div id="fb-root"></div>
- <script>
- (function (d, s, id) {
- var js,
- fjs = d.getElementsByTagName(s)[0];
- if (d.getElementById(id)) return;
- js = d.createElement(s);
- js.id = id;
- js.src = '//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1687545238205192';
- fjs.parentNode.insertBefore(js, fjs);
- })(document, 'script', 'facebook-jssdk');
- </script>
- <div id="app"></div>
- <!-- End Facebook SDK -->
- <!-- Twitter SDK -->
- <script>
- window.twttr = (function (d, s, id) {
- var js,
- fjs = d.getElementsByTagName(s)[0],
- t = window.twttr || {};
- if (d.getElementById(id)) return t;
- js = d.createElement(s);
- js.id = id;
- js.src = 'https://platform.twitter.com/widgets.js';
- fjs.parentNode.insertBefore(js, fjs);
-
- t._e = [];
- t.ready = function (f) {
- t._e.push(f);
- };
- return t;
- })(document, 'script', 'twitter-wjs');
- </script>
- <!-- End Twitter SDK -->
- <!-- Hotjar Tracking Code for https://0xproject.com/ -->
- <script>
- (function (h, o, t, j, a, r) {
- h.hj = h.hj || function () { (h.hj.q = h.hj.q || []).push(arguments) };
- h._hjSettings = { hjid: 935597, hjsv: 6 };
- a = o.getElementsByTagName('head')[0];
- r = o.createElement('script'); r.async = 1;
- r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
- a.appendChild(r);
- })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
- </script>
- <!-- End Hotjar Tracking Code -->
- <!-- Main -->
- <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
-</body>
-
-</html> \ No newline at end of file
+ t._e = [];
+ t.ready = function(f) {
+ t._e.push(f);
+ };
+ return t;
+ })(document, 'script', 'twitter-wjs');
+ </script>
+ <!-- End Twitter SDK -->
+ <!-- Hotjar Tracking Code for https://0xproject.com/ -->
+ <script>
+ (function(h, o, t, j, a, r) {
+ h.hj =
+ h.hj ||
+ function() {
+ (h.hj.q = h.hj.q || []).push(arguments);
+ };
+ h._hjSettings = { hjid: 935597, hjsv: 6 };
+ a = o.getElementsByTagName('head')[0];
+ r = o.createElement('script');
+ r.async = 1;
+ r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
+ a.appendChild(r);
+ })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
+ </script>
+ <!-- End Hotjar Tracking Code -->
+ <!-- Main -->
+ <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
+ </body>
+</html>
diff --git a/packages/website/ts/components/ui/check_mark.tsx b/packages/website/ts/components/ui/check_mark.tsx
new file mode 100644
index 000000000..86e427c8b
--- /dev/null
+++ b/packages/website/ts/components/ui/check_mark.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+
+import { colors } from '@0x/react-shared';
+
+export interface CheckMarkProps {
+ color?: string;
+ isChecked?: boolean;
+}
+
+export const CheckMark: React.StatelessComponent<CheckMarkProps> = ({ color, isChecked }) => (
+ <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <circle
+ cx="8.5"
+ cy="8.5"
+ r="8.5"
+ fill={isChecked ? color : 'white'}
+ stroke={isChecked ? undefined : '#CCCCCC'}
+ />
+ <path
+ d="M2.5 4.5L1.79289 5.20711L2.5 5.91421L3.20711 5.20711L2.5 4.5ZM-0.707107 2.70711L1.79289 5.20711L3.20711 3.79289L0.707107 1.29289L-0.707107 2.70711ZM3.20711 5.20711L7.70711 0.707107L6.29289 -0.707107L1.79289 3.79289L3.20711 5.20711Z"
+ transform="translate(5 6.5)"
+ fill="white"
+ />
+ </svg>
+);
+
+CheckMark.displayName = 'Check';
+
+CheckMark.defaultProps = {
+ color: colors.mediumBlue,
+};
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index 7eab2a50f..4b76ce8be 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -1,11 +1,15 @@
import { TextAlignProperty } from 'csstype';
+import { darken } from 'polished';
import * as React from 'react';
+import { styled } from 'ts/style/theme';
+
type StringOrNum = string | number;
export type ContainerTag = 'div' | 'span';
export interface ContainerProps {
+ margin?: string;
marginTop?: StringOrNum;
marginBottom?: StringOrNum;
marginRight?: StringOrNum;
@@ -17,10 +21,13 @@ export interface ContainerProps {
paddingLeft?: StringOrNum;
backgroundColor?: string;
background?: string;
+ border?: string;
+ borderTop?: string;
borderRadius?: StringOrNum;
borderBottomLeftRadius?: StringOrNum;
borderBottomRightRadius?: StringOrNum;
borderBottom?: StringOrNum;
+ borderColor?: string;
maxWidth?: StringOrNum;
maxHeight?: StringOrNum;
width?: StringOrNum;
@@ -42,10 +49,26 @@ export interface ContainerProps {
id?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible';
+ overflowY?: 'scroll' | 'hidden' | 'auto' | 'visible';
+ shouldDarkenOnHover?: boolean;
+ hasBoxShadow?: boolean;
+ shouldAddBoxShadowOnHover?: boolean;
}
-export const Container: React.StatelessComponent<ContainerProps> = props => {
- const { children, className, Tag, isHidden, id, onClick, ...style } = props;
+export const PlainContainer: React.StatelessComponent<ContainerProps> = props => {
+ const {
+ children,
+ className,
+ Tag,
+ isHidden,
+ id,
+ onClick,
+ shouldDarkenOnHover,
+ shouldAddBoxShadowOnHover,
+ hasBoxShadow,
+ // tslint:disable-next-line:trailing-comma
+ ...style
+ } = props;
const visibility = isHidden ? 'hidden' : undefined;
return (
<Tag id={id} style={{ ...style, visibility }} className={className} onClick={onClick}>
@@ -54,6 +77,18 @@ export const Container: React.StatelessComponent<ContainerProps> = props => {
);
};
+export const Container = styled(PlainContainer)`
+ box-sizing: border-box;
+ ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
+ &:hover {
+ ${props =>
+ props.shouldDarkenOnHover
+ ? `background-color: ${props.backgroundColor ? darken(0.05, props.backgroundColor) : 'none'} !important`
+ : ''};
+ ${props => (props.shouldAddBoxShadowOnHover ? 'box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)' : '')};
+ }
+`;
+
Container.defaultProps = {
Tag: 'div',
};
diff --git a/packages/website/ts/components/ui/input.tsx b/packages/website/ts/components/ui/input.tsx
index e5f4f6c70..1f56c814f 100644
--- a/packages/website/ts/components/ui/input.tsx
+++ b/packages/website/ts/components/ui/input.tsx
@@ -8,6 +8,7 @@ export interface InputProps {
width?: string;
fontSize?: string;
fontColor?: string;
+ border?: string;
placeholderColor?: string;
placeholder?: string;
backgroundColor?: string;
@@ -23,9 +24,11 @@ export const Input = styled(PlainInput)`
width: ${props => props.width};
padding: 0.8em 1.2em;
border-radius: 3px;
+ box-sizing: border-box;
font-family: 'Roboto Mono';
color: ${props => props.fontColor};
- border: none;
+ border: ${props => props.border};
+ outline: none;
background-color: ${props => props.backgroundColor};
&::placeholder {
color: ${props => props.placeholderColor};
@@ -38,6 +41,7 @@ Input.defaultProps = {
fontColor: colors.darkestGrey,
placeholderColor: colors.darkGrey,
fontSize: '12px',
+ border: 'none',
};
Input.displayName = 'Input';
diff --git a/packages/website/ts/components/ui/multi_select.tsx b/packages/website/ts/components/ui/multi_select.tsx
new file mode 100644
index 000000000..2cf44cae1
--- /dev/null
+++ b/packages/website/ts/components/ui/multi_select.tsx
@@ -0,0 +1,66 @@
+import { colors } from '@0x/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Container } from './container';
+
+export interface MultiSelectItemConfig {
+ value: string;
+ renderItemContent: (isSelected: boolean) => React.ReactNode;
+ onClick?: () => void;
+}
+
+export interface MultiSelectProps {
+ selectedValues?: string[];
+ items: MultiSelectItemConfig[];
+ backgroundColor?: string;
+ height?: string;
+}
+
+export class MultiSelect extends React.Component<MultiSelectProps> {
+ public static defaultProps = {
+ backgroundColor: colors.white,
+ };
+ public render(): React.ReactNode {
+ const { items, backgroundColor, selectedValues, height } = this.props;
+ return (
+ <Container
+ backgroundColor={backgroundColor}
+ borderRadius="4px"
+ width="100%"
+ height={height}
+ overflowY="scroll"
+ >
+ {_.map(items, item => (
+ <MultiSelectItem
+ key={item.value}
+ renderItemContent={item.renderItemContent}
+ backgroundColor={backgroundColor}
+ onClick={item.onClick}
+ isSelected={_.isUndefined(selectedValues) || _.includes(selectedValues, item.value)}
+ />
+ ))}
+ </Container>
+ );
+ }
+}
+
+export interface MultiSelectItemProps {
+ renderItemContent: (isSelected: boolean) => React.ReactNode;
+ isSelected?: boolean;
+ onClick?: () => void;
+ backgroundColor?: string;
+}
+
+export const MultiSelectItem: React.StatelessComponent<MultiSelectItemProps> = ({
+ renderItemContent,
+ isSelected,
+ onClick,
+ backgroundColor,
+}) => (
+ <Container cursor="pointer" shouldDarkenOnHover={true} onClick={onClick} backgroundColor={backgroundColor}>
+ <Container borderBottom={`1px solid ${colors.lightestGrey}`} margin="0px 15px" padding="10px 0px">
+ {renderItemContent(isSelected)}
+ </Container>
+ </Container>
+);
diff --git a/packages/website/ts/components/ui/select.tsx b/packages/website/ts/components/ui/select.tsx
new file mode 100644
index 000000000..743b082b0
--- /dev/null
+++ b/packages/website/ts/components/ui/select.tsx
@@ -0,0 +1,170 @@
+import { colors } from '@0x/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { zIndex } from 'ts/style/z_index';
+
+import { Container } from './container';
+import { Overlay } from './overlay';
+import { Text } from './text';
+
+export interface SelectItemConfig {
+ text: string;
+ onClick?: () => void;
+}
+
+export interface SelectProps {
+ value: string;
+ label?: string;
+ items: SelectItemConfig[];
+ onOpen?: () => void;
+ border?: string;
+ fontSize?: string;
+ iconSize?: number;
+ textColor?: string;
+ labelColor?: string;
+ backgroundColor?: string;
+}
+
+export interface SelectState {
+ isOpen: boolean;
+}
+
+export class Select extends React.Component<SelectProps, SelectState> {
+ public static defaultProps = {
+ items: [] as SelectItemConfig[],
+ textColor: colors.black,
+ backgroundColor: colors.white,
+ fontSize: '16px',
+ iconSize: 25,
+ };
+ public state: SelectState = {
+ isOpen: false,
+ };
+ public render(): React.ReactNode {
+ const { value, label, items, border, textColor, labelColor, backgroundColor, fontSize, iconSize } = this.props;
+ const { isOpen } = this.state;
+ const hasItems = !_.isEmpty(items);
+ const borderRadius = isOpen ? '4px 4px 0px 0px' : '4px';
+ return (
+ <React.Fragment>
+ {isOpen && (
+ <Overlay
+ style={{
+ zIndex: zIndex.overlay,
+ backgroundColor: 'rgba(255, 255, 255, 0)',
+ }}
+ onClick={this._closeDropdown}
+ />
+ )}
+ <Container position="relative">
+ <Container
+ cursor={hasItems ? 'pointer' : undefined}
+ onClick={this._handleDropdownClick}
+ borderRadius={borderRadius}
+ hasBoxShadow={isOpen}
+ border={border}
+ backgroundColor={backgroundColor}
+ padding="0.8em"
+ width="100%"
+ >
+ <Container className="flex justify-between">
+ <Text fontSize={fontSize} fontColor={textColor}>
+ {value}
+ </Text>
+ <Container>
+ {label && (
+ <Text fontSize={fontSize} fontColor={labelColor}>
+ {label}
+ </Text>
+ )}
+ {hasItems && (
+ <Container marginLeft="5px" display="inline-block" position="relative" bottom="2px">
+ <i
+ className="zmdi zmdi-chevron-down"
+ style={{ fontSize: iconSize, color: colors.darkGrey }}
+ />
+ </Container>
+ )}
+ </Container>
+ </Container>
+ </Container>
+ {isOpen && (
+ <Container
+ width="100%"
+ position="absolute"
+ onClick={this._closeDropdown}
+ zIndex={zIndex.aboveOverlay}
+ hasBoxShadow={true}
+ >
+ {_.map(items, (item, index) => (
+ <SelectItem
+ key={item.text}
+ {...item}
+ isLast={index === items.length - 1}
+ backgroundColor={backgroundColor}
+ textColor={textColor}
+ border={border}
+ />
+ ))}
+ </Container>
+ )}
+ </Container>
+ </React.Fragment>
+ );
+ }
+ private readonly _handleDropdownClick = (): void => {
+ if (_.isEmpty(this.props.items)) {
+ return;
+ }
+ const isOpen = !this.state.isOpen;
+ this.setState({
+ isOpen,
+ });
+
+ if (isOpen && this.props.onOpen) {
+ this.props.onOpen();
+ }
+ };
+ private readonly _closeDropdown = (): void => {
+ this.setState({
+ isOpen: false,
+ });
+ };
+}
+
+export interface SelectItemProps extends SelectItemConfig {
+ text: string;
+ onClick?: () => void;
+ isLast: boolean;
+ backgroundColor?: string;
+ border?: string;
+ textColor?: string;
+ fontSize?: string;
+}
+
+export const SelectItem: React.StatelessComponent<SelectItemProps> = ({
+ text,
+ onClick,
+ isLast,
+ border,
+ backgroundColor,
+ textColor,
+ fontSize,
+}) => (
+ <Container
+ onClick={onClick}
+ cursor="pointer"
+ backgroundColor={backgroundColor}
+ padding="0.8em"
+ borderTop="0"
+ border={border}
+ shouldDarkenOnHover={true}
+ borderRadius={isLast ? '0px 0px 4px 4px' : undefined}
+ width="100%"
+ >
+ <Text fontSize={fontSize} fontColor={textColor}>
+ {text}
+ </Text>
+ </Container>
+);
diff --git a/packages/website/ts/pages/documentation/developers_page.tsx b/packages/website/ts/pages/documentation/developers_page.tsx
index a84be7bfe..fcca2b6ad 100644
--- a/packages/website/ts/pages/documentation/developers_page.tsx
+++ b/packages/website/ts/pages/documentation/developers_page.tsx
@@ -2,6 +2,7 @@ import { colors, constants as sharedConstants, utils as sharedUtils } from '@0x/
import * as _ from 'lodash';
import * as React from 'react';
import DocumentTitle from 'react-document-title';
+import { Helmet } from 'react-helmet';
import { DocsLogo } from 'ts/components/documentation/docs_logo';
import { DocsTopBar } from 'ts/components/documentation/docs_top_bar';
import { Container } from 'ts/components/ui/container';
@@ -146,6 +147,9 @@ export class DevelopersPage extends React.Component<DevelopersPageProps, Develop
} 50%, ${colors.white} 100%)`}
>
<DocumentTitle title="0x Docs" />
+ <Helmet>
+ <link rel="stylesheet" href="/css/github-gist.css" />
+ </Helmet>
<Container className="flex mx-auto" height="100vh">
<Container
className="sm-hide xs-hide relative"
diff --git a/packages/website/ts/pages/instant/action_link.tsx b/packages/website/ts/pages/instant/action_link.tsx
new file mode 100644
index 000000000..c196f03ef
--- /dev/null
+++ b/packages/website/ts/pages/instant/action_link.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { utils } from 'ts/utils/utils';
+
+export interface ActionLinkProps {
+ displayText: string;
+ linkSrc?: string;
+ onClick?: () => void;
+ fontSize?: number;
+ color?: string;
+ className?: string;
+}
+
+export class ActionLink extends React.Component<ActionLinkProps> {
+ public static defaultProps = {
+ fontSize: 16,
+ color: colors.white,
+ };
+ public render(): React.ReactNode {
+ const { displayText, fontSize, color, className } = this.props;
+ return (
+ <Container className={`flex items-center ${className}`} onClick={this._handleClick} cursor="pointer">
+ <Container>
+ <Text fontSize="16px" fontColor={color}>
+ {displayText}
+ </Text>
+ </Container>
+ <Container paddingTop="1px" paddingLeft="6px">
+ <i className="zmdi zmdi-chevron-right bold" style={{ fontSize, color }} />
+ </Container>
+ </Container>
+ );
+ }
+
+ private readonly _handleClick = (event: React.MouseEvent<HTMLElement>) => {
+ if (!_.isUndefined(this.props.onClick)) {
+ this.props.onClick();
+ } else if (!_.isUndefined(this.props.linkSrc)) {
+ utils.openUrl(this.props.linkSrc);
+ }
+ };
+}
diff --git a/packages/website/ts/pages/instant/code_demo.tsx b/packages/website/ts/pages/instant/code_demo.tsx
new file mode 100644
index 000000000..e57e39dff
--- /dev/null
+++ b/packages/website/ts/pages/instant/code_demo.tsx
@@ -0,0 +1,148 @@
+import * as React from 'react';
+import SyntaxHighlighter from 'react-syntax-highlighter';
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+
+const CustomPre = styled.pre`
+ margin: 0px;
+ line-height: 24px;
+ overflow: scroll;
+ width: 600px;
+ height: 100%;
+ max-height: 800px;
+ border-radius: 4px;
+ code {
+ background-color: inherit !important;
+ border-radius: 0px;
+ font-family: 'Roboto Mono', sans-serif;
+ border: none;
+ }
+ code:first-of-type {
+ background-color: #2a2a2a !important;
+ color: #999;
+ min-height: 100%;
+ text-align: center;
+ padding-right: 5px !important;
+ padding-left: 5px;
+ margin-right: 15px;
+ line-height: 25px;
+ padding-top: 10px;
+ }
+ code:last-of-type {
+ position: relative;
+ top: 10px;
+ }
+`;
+
+const customStyle = {
+ 'hljs-comment': {
+ color: '#7e7887',
+ },
+ 'hljs-quote': {
+ color: '#7e7887',
+ },
+ 'hljs-variable': {
+ color: '#be4678',
+ },
+ 'hljs-template-variable': {
+ color: '#be4678',
+ },
+ 'hljs-attribute': {
+ color: '#be4678',
+ },
+ 'hljs-regexp': {
+ color: '#be4678',
+ },
+ 'hljs-link': {
+ color: '#be4678',
+ },
+ 'hljs-tag': {
+ color: '#61f5ff',
+ },
+ 'hljs-name': {
+ color: '#61f5ff',
+ },
+ 'hljs-selector-id': {
+ color: '#be4678',
+ },
+ 'hljs-selector-class': {
+ color: '#be4678',
+ },
+ 'hljs-number': {
+ color: '#c994ff',
+ },
+ 'hljs-meta': {
+ color: '#aa573c',
+ },
+ 'hljs-built_in': {
+ color: '#aa573c',
+ },
+ 'hljs-builtin-name': {
+ color: '#aa573c',
+ },
+ 'hljs-literal': {
+ color: '#aa573c',
+ },
+ 'hljs-type': {
+ color: '#aa573c',
+ },
+ 'hljs-params': {
+ color: '#aa573c',
+ },
+ 'hljs-string': {
+ color: '#bcff88',
+ },
+ 'hljs-symbol': {
+ color: '#2a9292',
+ },
+ 'hljs-bullet': {
+ color: '#2a9292',
+ },
+ 'hljs-title': {
+ color: '#576ddb',
+ },
+ 'hljs-section': {
+ color: '#576ddb',
+ },
+ 'hljs-keyword': {
+ color: '#955ae7',
+ },
+ 'hljs-selector-tag': {
+ color: '#955ae7',
+ },
+ 'hljs-deletion': {
+ color: '#19171c',
+ display: 'inline-block',
+ width: '100%',
+ backgroundColor: '#be4678',
+ },
+ 'hljs-addition': {
+ color: '#19171c',
+ display: 'inline-block',
+ width: '100%',
+ backgroundColor: '#2a9292',
+ },
+ hljs: {
+ display: 'block',
+ overflowX: 'hidden',
+ background: colors.instantSecondaryBackground,
+ color: 'white',
+ fontSize: '12px',
+ },
+ 'hljs-emphasis': {
+ fontStyle: 'italic',
+ },
+ 'hljs-strong': {
+ fontWeight: 'bold',
+ },
+};
+
+export interface CodeDemoProps {
+ children: string;
+}
+
+export const CodeDemo: React.StatelessComponent<CodeDemoProps> = props => (
+ <SyntaxHighlighter language="html" style={customStyle} showLineNumbers={true} PreTag={CustomPre}>
+ {props.children}
+ </SyntaxHighlighter>
+);
diff --git a/packages/website/ts/pages/instant/config_generator.tsx b/packages/website/ts/pages/instant/config_generator.tsx
new file mode 100644
index 000000000..efd1be096
--- /dev/null
+++ b/packages/website/ts/pages/instant/config_generator.tsx
@@ -0,0 +1,291 @@
+import { StandardRelayerAPIOrderProvider } from '@0x/asset-buyer';
+import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses';
+import { assetDataUtils } from '@0x/order-utils';
+import { ObjectMap } from '@0x/types';
+import * as _ from 'lodash';
+import Slider from 'material-ui/Slider';
+import * as React from 'react';
+
+import { CheckMark } from 'ts/components/ui/check_mark';
+import { Container } from 'ts/components/ui/container';
+import { MultiSelect } from 'ts/components/ui/multi_select';
+import { Select, SelectItemConfig } from 'ts/components/ui/select';
+import { Spinner } from 'ts/components/ui/spinner';
+import { Text } from 'ts/components/ui/text';
+import { ConfigGeneratorAddressInput } from 'ts/pages/instant/config_generator_address_input';
+import { colors } from 'ts/style/colors';
+import { WebsiteBackendTokenInfo } from 'ts/types';
+import { backendClient } from 'ts/utils/backend_client';
+import { constants } from 'ts/utils/constants';
+
+import { ZeroExInstantBaseConfig } from '../../../../instant/src/types';
+
+export interface ConfigGeneratorProps {
+ value: ZeroExInstantBaseConfig;
+ onConfigChange: (config: ZeroExInstantBaseConfig) => void;
+}
+
+export interface ConfigGeneratorState {
+ isLoadingAvailableTokens: boolean;
+ // Address to token info
+ allKnownTokens: ObjectMap<WebsiteBackendTokenInfo>;
+ availableTokens?: WebsiteBackendTokenInfo[];
+}
+
+const SRA_ENDPOINTS = ['https://api.radarrelay.com/0x/v2/', 'https://api.openrelay.xyz/v2/'];
+
+export class ConfigGenerator extends React.Component<ConfigGeneratorProps, ConfigGeneratorState> {
+ public state: ConfigGeneratorState = {
+ isLoadingAvailableTokens: true,
+ allKnownTokens: {},
+ };
+ public componentDidMount(): void {
+ this._setAllKnownTokens(this._setAvailableAssetsFromOrderProvider);
+ }
+ public componentDidUpdate(prevProps: ConfigGeneratorProps): void {
+ if (prevProps.value.orderSource !== this.props.value.orderSource) {
+ this._setAvailableAssetsFromOrderProvider();
+ }
+ }
+ public render(): React.ReactNode {
+ const { value } = this.props;
+ if (!_.isString(value.orderSource)) {
+ throw new Error('ConfigGenerator component only supports string values as an orderSource.');
+ }
+ return (
+ <Container minWidth="350px">
+ <ConfigGeneratorSection title="Standard relayer API endpoint">
+ <Select value={value.orderSource} items={this._generateItems()} />
+ </ConfigGeneratorSection>
+ <ConfigGeneratorSection {...this._getTokenSelectorProps()}>
+ {this._renderTokenMultiSelectOrSpinner()}
+ </ConfigGeneratorSection>
+ <ConfigGeneratorSection title="Transaction fee ETH address" marginBottom="10px" isOptional={true}>
+ <ConfigGeneratorAddressInput
+ value={value.affiliateInfo ? value.affiliateInfo.feeRecipient : ''}
+ onChange={this._handleAffiliateAddressChange}
+ />
+ </ConfigGeneratorSection>
+ <ConfigGeneratorSection
+ title="Fee percentage"
+ actionText="Learn more"
+ onActionTextClick={this._handleAffiliatePercentageLearnMoreClick}
+ >
+ <Slider
+ min={0}
+ max={0.05}
+ step={0.0025}
+ value={value.affiliateInfo.feePercentage}
+ onChange={this._handleAffiliatePercentageChange}
+ />
+ </ConfigGeneratorSection>
+ </Container>
+ );
+ }
+ private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => {
+ if (_.isUndefined(this.props.value.availableAssetDatas)) {
+ return {
+ title: 'What tokens can users buy?',
+ actionText: 'Unselect All',
+ onActionTextClick: this._handleUnselectAllClick,
+ };
+ }
+ return {
+ title: 'What tokens can users buy?',
+ actionText: 'Select All',
+ onActionTextClick: this._handleSelectAllClick,
+ };
+ };
+ private readonly _generateItems = (): SelectItemConfig[] => {
+ return _.map(SRA_ENDPOINTS, endpoint => ({
+ text: endpoint,
+ onClick: this._handleSRASelection.bind(this, endpoint),
+ }));
+ };
+ private readonly _getAllKnownAssetDatas = (): string[] => {
+ return _.map(this.state.allKnownTokens, token => assetDataUtils.encodeERC20AssetData(token.address));
+ };
+ private readonly _handleAffiliatePercentageLearnMoreClick = (): void => {
+ window.open('/wiki#Learn-About-Affiliate-Fees', '_blank');
+ };
+ private readonly _handleSRASelection = (sraEndpoint: string) => {
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ orderSource: sraEndpoint,
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleAffiliateAddressChange = (address: string) => {
+ const oldConfig: ZeroExInstantBaseConfig = this.props.value;
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...oldConfig,
+ affiliateInfo: {
+ feeRecipient: address,
+ feePercentage: oldConfig.affiliateInfo.feePercentage,
+ },
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleAffiliatePercentageChange = (event: any, value: number) => {
+ const oldConfig: ZeroExInstantBaseConfig = this.props.value;
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...oldConfig,
+ affiliateInfo: {
+ feeRecipient: oldConfig.affiliateInfo.feeRecipient,
+ feePercentage: value,
+ },
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleSelectAllClick = () => {
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ availableAssetDatas: undefined,
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleUnselectAllClick = () => {
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ availableAssetDatas: [],
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleTokenClick = (assetData: string) => {
+ const { value } = this.props;
+ const { allKnownTokens } = this.state;
+ let newAvailableAssetDatas: string[] = [];
+ const availableAssetDatas = value.availableAssetDatas;
+ if (_.isUndefined(availableAssetDatas)) {
+ // It being undefined means it's all tokens.
+ const allKnownAssetDatas = this._getAllKnownAssetDatas();
+ newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData);
+ } else if (!_.includes(availableAssetDatas, assetData)) {
+ // Add it
+ newAvailableAssetDatas = [...availableAssetDatas, assetData];
+ } else {
+ // Remove it
+ newAvailableAssetDatas = _.pull(availableAssetDatas, assetData);
+ }
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ availableAssetDatas: newAvailableAssetDatas,
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _setAllKnownTokens = async (callback: () => void): Promise<void> => {
+ const tokenInfos = await backendClient.getTokenInfosAsync();
+ const allKnownTokens = _.reduce(
+ tokenInfos,
+ (acc, tokenInfo) => {
+ acc[tokenInfo.address] = tokenInfo;
+ return acc;
+ },
+ {} as ObjectMap<WebsiteBackendTokenInfo>,
+ );
+ this.setState({ allKnownTokens }, callback);
+ };
+ private readonly _setAvailableAssetsFromOrderProvider = async (): Promise<void> => {
+ const { value } = this.props;
+ if (!_.isUndefined(value.orderSource) && _.isString(value.orderSource)) {
+ this.setState({ isLoadingAvailableTokens: true });
+ const networkId = constants.NETWORK_ID_MAINNET;
+ const sraOrderProvider = new StandardRelayerAPIOrderProvider(value.orderSource, networkId);
+ const etherTokenAddress = getContractAddressesForNetworkOrThrow(networkId).etherToken;
+ const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress);
+ const assetDatas = await sraOrderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData);
+ const availableTokens = _.compact(
+ _.map(assetDatas, assetData => {
+ const address = assetDataUtils.decodeAssetDataOrThrow(assetData).tokenAddress;
+ return this.state.allKnownTokens[address];
+ }),
+ );
+ this.setState({ availableTokens, isLoadingAvailableTokens: false });
+ }
+ };
+ private readonly _renderTokenMultiSelectOrSpinner = (): React.ReactNode => {
+ const { value } = this.props;
+ const { availableTokens, isLoadingAvailableTokens } = this.state;
+ const multiSelectHeight = '200px';
+ if (isLoadingAvailableTokens) {
+ return (
+ <Container
+ className="flex flex-column items-center justify-center"
+ height={multiSelectHeight}
+ backgroundColor={colors.white}
+ borderRadius="4px"
+ width="100%"
+ >
+ <Container position="relative" left="12px" marginBottom="20px">
+ <Spinner />
+ </Container>
+ <Text fontSize="16px">Loading...</Text>
+ </Container>
+ );
+ }
+ const items = _.map(availableTokens, token => {
+ const assetData = assetDataUtils.encodeERC20AssetData(token.address);
+ return {
+ value: assetDataUtils.encodeERC20AssetData(token.address),
+ renderItemContent: (isSelected: boolean) => (
+ <Container className="flex items-center">
+ <Container marginRight="10px">
+ <CheckMark isChecked={isSelected} />
+ </Container>
+ <Text
+ fontSize="16px"
+ fontColor={isSelected ? colors.mediumBlue : colors.darkerGrey}
+ fontWeight={300}
+ >
+ <b>{token.symbol}</b> — {token.name}
+ </Text>
+ </Container>
+ ),
+ onClick: this._handleTokenClick.bind(this, assetData),
+ };
+ });
+ return <MultiSelect items={items} selectedValues={value.availableAssetDatas} height={multiSelectHeight} />;
+ };
+}
+
+export interface ConfigGeneratorSectionProps {
+ title: string;
+ actionText?: string;
+ onActionTextClick?: () => void;
+ isOptional?: boolean;
+ marginBottom?: string;
+}
+
+export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = ({
+ title,
+ actionText,
+ onActionTextClick,
+ isOptional,
+ marginBottom,
+ children,
+}) => (
+ <Container marginBottom={marginBottom}>
+ <Container marginBottom="10px" className="flex justify-between items-center">
+ <Container>
+ <Text fontColor={colors.white} fontSize="16px" lineHeight="18px">
+ {title}
+ </Text>
+ {isOptional && (
+ <Text fontColor={colors.grey} fontSize="16px" lineHeight="18px">
+ (optional)
+ </Text>
+ )}
+ </Container>
+ {actionText && (
+ <Text fontSize="12px" fontColor={colors.grey} onClick={onActionTextClick}>
+ {actionText}
+ </Text>
+ )}
+ </Container>
+ {children}
+ </Container>
+);
+
+ConfigGeneratorSection.defaultProps = {
+ marginBottom: '30px',
+};
diff --git a/packages/website/ts/pages/instant/config_generator_address_input.tsx b/packages/website/ts/pages/instant/config_generator_address_input.tsx
new file mode 100644
index 000000000..2e5a6e533
--- /dev/null
+++ b/packages/website/ts/pages/instant/config_generator_address_input.tsx
@@ -0,0 +1,58 @@
+import { colors } from '@0x/react-shared';
+import { addressUtils } from '@0x/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Container } from 'ts/components/ui/container';
+import { Input } from 'ts/components/ui/input';
+import { Text } from 'ts/components/ui/text';
+
+export interface ConfigGeneratorAddressInputProps {
+ value?: string;
+ onChange?: (address: string) => void;
+}
+
+export interface ConfigGeneratorAddressInputState {
+ errMsg: string;
+}
+
+export class ConfigGeneratorAddressInput extends React.Component<
+ ConfigGeneratorAddressInputProps,
+ ConfigGeneratorAddressInputState
+> {
+ public state = {
+ errMsg: '',
+ };
+ public render(): React.ReactNode {
+ const { errMsg } = this.state;
+ const hasError = !_.isEmpty(errMsg);
+ const border = hasError ? '1px solid red' : undefined;
+ return (
+ <Container height="80px">
+ <Input
+ width="100%"
+ fontSize="16px"
+ value={this.props.value}
+ onChange={this._handleChange}
+ placeholder="0xe99...aa8da4"
+ border={border}
+ />
+ <Container marginTop="5px" isHidden={!hasError} height="25px">
+ <Text fontSize="14px" fontColor={colors.grey} fontStyle="italic">
+ {errMsg}
+ </Text>
+ </Container>
+ </Container>
+ );
+ }
+
+ private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
+ const address = event.target.value;
+ const isValidAddress = addressUtils.isAddress(address.toLowerCase()) || address === '';
+ const errMsg = isValidAddress ? '' : 'Please enter a valid Ethereum address';
+ this.setState({
+ errMsg,
+ });
+ this.props.onChange(address);
+ };
+}
diff --git a/packages/website/ts/pages/instant/configurator.tsx b/packages/website/ts/pages/instant/configurator.tsx
index c836739bb..5700bdc1d 100644
--- a/packages/website/ts/pages/instant/configurator.tsx
+++ b/packages/website/ts/pages/instant/configurator.tsx
@@ -1,12 +1,99 @@
+import * as _ from 'lodash';
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { ActionLink } from 'ts/pages/instant/action_link';
+import { CodeDemo } from 'ts/pages/instant/code_demo';
+import { ConfigGenerator } from 'ts/pages/instant/config_generator';
import { colors } from 'ts/style/colors';
+import { ZeroExInstantBaseConfig } from '../../../../instant/src/types';
+
export interface ConfiguratorProps {
hash: string;
}
-export const Configurator = (props: ConfiguratorProps) => (
- <Container id={props.hash} height="400px" backgroundColor={colors.instantTertiaryBackground} />
-);
+export interface ConfiguratorState {
+ instantConfig: ZeroExInstantBaseConfig;
+}
+
+export class Configurator extends React.Component<ConfiguratorProps> {
+ public state: ConfiguratorState = {
+ instantConfig: {
+ orderSource: 'https://api.radarrelay.com/0x/v2/',
+ availableAssetDatas: [],
+ affiliateInfo: {
+ feeRecipient: '',
+ feePercentage: 0.01,
+ },
+ },
+ };
+ public render(): React.ReactNode {
+ const { hash } = this.props;
+ const codeToDisplay = this._generateCodeDemoCode();
+ return (
+ <Container
+ className="flex justify-center py4 px3"
+ id={hash}
+ backgroundColor={colors.instantTertiaryBackground}
+ >
+ <Container className="mx3">
+ <Container className="mb3">
+ <Text fontSize="20px" lineHeight="28px" fontColor={colors.white} fontWeight={500}>
+ 0x Instant Configurator
+ </Text>
+ </Container>
+ <ConfigGenerator value={this.state.instantConfig} onConfigChange={this._handleConfigChange} />
+ </Container>
+ <Container className="mx3" height="550px">
+ <Container className="mb3 flex justify-between">
+ <Text fontSize="20px" lineHeight="28px" fontColor={colors.white} fontWeight={500}>
+ Code Snippet
+ </Text>
+ <ActionLink displayText="Explore the Docs" linkSrc="/docs/instant" color={colors.grey} />
+ </Container>
+ <CodeDemo key={codeToDisplay}>{codeToDisplay}</CodeDemo>
+ </Container>
+ </Container>
+ );
+ }
+ private readonly _handleConfigChange = (config: ZeroExInstantBaseConfig) => {
+ this.setState({
+ instantConfig: config,
+ });
+ };
+ private readonly _generateCodeDemoCode = (): string => {
+ const { instantConfig } = this.state;
+ return `<head>
+ <script src="https://instant.0xproject.com/instant.js"></script>
+</head>
+<body>
+ <script>
+ zeroExInstant.render({
+ liquiditySource: '${instantConfig.orderSource}',${
+ !_.isUndefined(instantConfig.affiliateInfo) && instantConfig.affiliateInfo.feeRecipient
+ ? `\n affiliateInfo: {
+ feeRecipient: '${instantConfig.affiliateInfo.feeRecipient}',
+ feePercentage: ${instantConfig.affiliateInfo.feePercentage}
+ }`
+ : ''
+ }${
+ !_.isUndefined(instantConfig.availableAssetDatas)
+ ? `\n availableAssetDatas: ${this._renderAvailableAssetDatasString(
+ instantConfig.availableAssetDatas,
+ )}`
+ : ''
+ }
+ }, 'body');
+ </script>
+</body>`;
+ };
+ private readonly _renderAvailableAssetDatasString = (availableAssetDatas: string[]): string => {
+ const stringAvailableAssetDatas = availableAssetDatas.map(assetData => `'${assetData}'`);
+ if (availableAssetDatas.length < 2) {
+ return `[${stringAvailableAssetDatas.join(', ')}]`;
+ }
+ return `[\n\t\t${stringAvailableAssetDatas.join(', \n\t\t')}\n ]`;
+ };
+}
diff --git a/packages/website/ts/pages/instant/features.tsx b/packages/website/ts/pages/instant/features.tsx
index 9c1668c1c..230a8496b 100644
--- a/packages/website/ts/pages/instant/features.tsx
+++ b/packages/website/ts/pages/instant/features.tsx
@@ -4,6 +4,7 @@ import * as React from 'react';
import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
+import { ActionLink, ActionLinkProps } from 'ts/pages/instant/action_link';
import { colors } from 'ts/style/colors';
import { ScreenWidths } from 'ts/types';
import { utils } from 'ts/utils/utils';
@@ -61,17 +62,11 @@ export const Features = (props: FeatureProps) => {
);
};
-interface LinkInfo {
- displayText: string;
- linkSrc?: string;
- onClick?: () => void;
-}
-
interface FeatureItemProps {
imgSrc: string;
title: string;
description: string;
- linkInfos: LinkInfo[];
+ linkInfos: ActionLinkProps[];
screenWidth: ScreenWidths;
}
@@ -95,36 +90,11 @@ const FeatureItem = (props: FeatureItemProps) => {
</Text>
</Container>
<Container className="flex" marginTop="28px">
- {_.map(linkInfos, linkInfo => {
- const onClick = (event: React.MouseEvent<HTMLElement>) => {
- if (!_.isUndefined(linkInfo.onClick)) {
- linkInfo.onClick();
- } else if (!_.isUndefined(linkInfo.linkSrc)) {
- utils.openUrl(linkInfo.linkSrc);
- }
- };
- return (
- <Container
- key={linkInfo.linkSrc}
- className="flex items-center"
- marginRight="32px"
- onClick={onClick}
- cursor="pointer"
- >
- <Container>
- <Text fontSize="16px" fontColor={colors.white}>
- {linkInfo.displayText}
- </Text>
- </Container>
- <Container paddingTop="1px" paddingLeft="6px">
- <i
- className="zmdi zmdi-chevron-right bold"
- style={{ fontSize: 16, color: colors.white }}
- />
- </Container>
- </Container>
- );
- })}
+ {_.map(linkInfos, linkInfo => (
+ <Container key={linkInfo.displayText} marginRight="32px">
+ <ActionLink {...linkInfo} />
+ </Container>
+ ))}
</Container>
</Container>
);
diff --git a/yarn.lock b/yarn.lock
index e3f047296..ed01009aa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1531,6 +1531,12 @@
dependencies:
"@types/react" "*"
+"@types/react-syntax-highlighter@^0.0.8":
+ version "0.0.8"
+ resolved "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-0.0.8.tgz#ed44e2ead992c513327bcf2ede5eda7daa981421"
+ dependencies:
+ "@types/react" "*"
+
"@types/react-tap-event-plugin@0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/react-tap-event-plugin/-/react-tap-event-plugin-0.0.30.tgz#123f35080412f489b6770c5a65c159ff96654cb5"
@@ -4090,6 +4096,12 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined-
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db"
+ dependencies:
+ trim "0.0.1"
+
commander@2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
@@ -6367,6 +6379,12 @@ fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+fault@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
+ dependencies:
+ format "^0.2.2"
+
faye-websocket@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -6665,6 +6683,10 @@ format-util@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95"
+format@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -7516,6 +7538,19 @@ hash.js@1.1.3, hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
+hast-util-parse-selector@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz#4ddbae1ae12c124e3eb91b581d2556441766f0ab"
+
+hastscript@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/hastscript/-/hastscript-5.0.0.tgz#fee10382c1bc4ba3f1be311521d368c047d2c43a"
+ dependencies:
+ comma-separated-tokens "^1.0.0"
+ hast-util-parse-selector "^2.2.0"
+ property-information "^5.0.1"
+ space-separated-tokens "^1.0.0"
+
hawk@3.1.3, hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
@@ -7564,7 +7599,7 @@ heap@~0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac"
-highlight.js@^9.0.0, highlight.js@^9.11.0:
+highlight.js@^9.0.0, highlight.js@^9.11.0, highlight.js@~9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
@@ -9925,6 +9960,13 @@ lowercase-keys@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+lowlight@~1.9.1:
+ version "1.9.2"
+ resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1"
+ dependencies:
+ fault "^1.0.2"
+ highlight.js "~9.12.0"
+
lru-cache@2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
@@ -11497,6 +11539,17 @@ parse-entities@^1.1.0:
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
+parse-entities@^1.1.2:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4"
+ dependencies:
+ character-entities "^1.0.0"
+ character-entities-legacy "^1.0.0"
+ character-reference-invalid "^1.0.0"
+ is-alphanumerical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-hexadecimal "^1.0.0"
+
parse-filepath@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
@@ -12093,7 +12146,7 @@ pretty-hrtime@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
-prismjs@^1.15.0:
+prismjs@^1.15.0, prismjs@^1.8.4, prismjs@~1.15.0:
version "1.15.0"
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9"
optionalDependencies:
@@ -12193,6 +12246,12 @@ prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8,
loose-envify "^1.3.1"
object-assign "^4.1.1"
+property-information@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/property-information/-/property-information-5.0.1.tgz#c3b09f4f5750b1634c0b24205adbf78f18bdf94f"
+ dependencies:
+ xtend "^4.0.1"
+
proto-list@~1.2.1:
version "1.2.4"
resolved "http://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -12656,6 +12715,16 @@ react-side-effect@^1.0.2, react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"
+react-syntax-highlighter@^10.1.1:
+ version "10.1.1"
+ resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-10.1.1.tgz#1bf7ad4f2f16d2978b04594407b670671b4d3316"
+ dependencies:
+ babel-runtime "^6.18.0"
+ highlight.js "~9.12.0"
+ lowlight "~1.9.1"
+ prismjs "^1.8.4"
+ refractor "^2.4.1"
+
react-tabs@^2.0.0:
version "2.2.2"
resolved "https://registry.npmjs.org/react-tabs/-/react-tabs-2.2.2.tgz#2f2935da379889484751d1df47c1b639e5ee835d"
@@ -12981,6 +13050,14 @@ redux@^3.6.0:
loose-envify "^1.1.0"
symbol-observable "^1.0.3"
+refractor@^2.4.1:
+ version "2.6.2"
+ resolved "https://registry.npmjs.org/refractor/-/refractor-2.6.2.tgz#8e0877ab8864165275aafeea5d9c8eebe871552f"
+ dependencies:
+ hastscript "^5.0.0"
+ parse-entities "^1.1.2"
+ prismjs "~1.15.0"
+
regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
@@ -14203,6 +14280,12 @@ source-map@~0.2.0:
dependencies:
amdefine ">=0.0.4"
+space-separated-tokens@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
+ dependencies:
+ trim "0.0.1"
+
sparkles@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"