aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/pages/instant
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/pages/instant')
-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.tsx99
-rw-r--r--packages/website/ts/pages/instant/features.tsx116
-rw-r--r--packages/website/ts/pages/instant/instant.tsx87
-rw-r--r--packages/website/ts/pages/instant/introducing_0x_instant.tsx57
-rw-r--r--packages/website/ts/pages/instant/need_more.tsx62
-rw-r--r--packages/website/ts/pages/instant/screenshots.tsx35
10 files changed, 999 insertions, 0 deletions
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
new file mode 100644
index 000000000..5700bdc1d
--- /dev/null
+++ b/packages/website/ts/pages/instant/configurator.tsx
@@ -0,0 +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 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
new file mode 100644
index 000000000..230a8496b
--- /dev/null
+++ b/packages/website/ts/pages/instant/features.tsx
@@ -0,0 +1,116 @@
+import * as _ from 'lodash';
+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';
+
+export interface FeatureProps {
+ screenWidth: ScreenWidths;
+ onGetStartedClick: () => void;
+}
+
+export const Features = (props: FeatureProps) => {
+ const isSmallScreen = props.screenWidth === ScreenWidths.Sm;
+ const getStartedLinkInfo = {
+ displayText: 'Get started',
+ onClick: props.onGetStartedClick,
+ };
+ const exploreTheDocsLinkInfo = {
+ displayText: 'Explore the docs',
+ linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Get-Started`,
+ };
+ const tokenLinkInfos = isSmallScreen ? [getStartedLinkInfo] : [getStartedLinkInfo, exploreTheDocsLinkInfo];
+ return (
+ <Container backgroundColor={colors.instantSecondaryBackground} className="py3 flex flex-column px3">
+ <FeatureItem
+ imgSrc="images/instant/feature_1.svg"
+ title="Support ERC-20 and ERC-721 tokens"
+ description="Seamlessly integrate token purchasing into your product experience by offering digital assets ranging from in-game items to stablecoins."
+ linkInfos={tokenLinkInfos}
+ screenWidth={props.screenWidth}
+ />
+ <FeatureItem
+ imgSrc="images/instant/feature_2.svg"
+ title="Generate revenue for your business"
+ description="With just a few lines of code, you can earn up to 5% in affiliate fees on every transaction from your crypto wallet or dApp."
+ linkInfos={[
+ {
+ displayText: 'Learn about affiliate fees',
+ linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Learn-About-Affiliate-Fees`,
+ },
+ ]}
+ screenWidth={props.screenWidth}
+ />
+ <FeatureItem
+ imgSrc="images/instant/feature_3.svg"
+ title="Easy and Flexible Integration"
+ description="Use our out-of-the-box design or customize the user interface by integrating the AssetBuyer engine. You can also tap into 0x networked liquidity or choose your own liquidity pool."
+ linkInfos={[
+ {
+ displayText: 'Explore AssetBuyer',
+ linkSrc: `${utils.getCurrentBaseUrl()}/docs/asset-buyer`,
+ },
+ ]}
+ screenWidth={props.screenWidth}
+ />
+ </Container>
+ );
+};
+
+interface FeatureItemProps {
+ imgSrc: string;
+ title: string;
+ description: string;
+ linkInfos: ActionLinkProps[];
+ screenWidth: ScreenWidths;
+}
+
+const FeatureItem = (props: FeatureItemProps) => {
+ const { imgSrc, title, description, linkInfos, screenWidth } = props;
+ const isLargeScreen = screenWidth === ScreenWidths.Lg;
+ const maxWidth = isLargeScreen ? '500px' : undefined;
+ const image = (
+ <Container className="center" minWidth="435px" maxHeight="225px">
+ <Image src={imgSrc} additionalStyle={{ filter: 'drop-shadow(0px 4px 4px rgba(0,0,0,.25))' }} />
+ </Container>
+ );
+ const info = (
+ <Container maxWidth={maxWidth}>
+ <Text fontSize="24px" lineHeight="34px" fontColor={colors.white} fontWeight={500}>
+ {title}
+ </Text>
+ <Container marginTop="28px">
+ <Text fontFamily="Roboto Mono" fontSize="14px" lineHeight="2em" fontColor={colors.grey500}>
+ {description}
+ </Text>
+ </Container>
+ <Container className="flex" marginTop="28px">
+ {_.map(linkInfos, linkInfo => (
+ <Container key={linkInfo.displayText} marginRight="32px">
+ <ActionLink {...linkInfo} />
+ </Container>
+ ))}
+ </Container>
+ </Container>
+ );
+ return (
+ <Container className="flex flex-column items-center py4 px3">
+ {isLargeScreen ? (
+ <Container className="flex">
+ {image}
+ <Container marginLeft="115px">{info}</Container>
+ </Container>
+ ) : (
+ <Container className="flex flex-column items-center" width="100%">
+ {image}
+ <Container marginTop="48px">{info}</Container>
+ </Container>
+ )}
+ </Container>
+ );
+};
diff --git a/packages/website/ts/pages/instant/instant.tsx b/packages/website/ts/pages/instant/instant.tsx
new file mode 100644
index 000000000..fa6bd1c33
--- /dev/null
+++ b/packages/website/ts/pages/instant/instant.tsx
@@ -0,0 +1,87 @@
+import { utils as sharedUtils } from '@0x/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+
+import { Footer } from 'ts/components/footer';
+import { MetaTags } from 'ts/components/meta_tags';
+import { TopBar } from 'ts/components/top_bar/top_bar';
+import { Container } from 'ts/components/ui/container';
+import { Configurator } from 'ts/pages/instant/configurator';
+import { Features } from 'ts/pages/instant/features';
+import { Introducing0xInstant } from 'ts/pages/instant/introducing_0x_instant';
+import { NeedMore } from 'ts/pages/instant/need_more';
+import { Screenshots } from 'ts/pages/instant/screenshots';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { colors } from 'ts/style/colors';
+import { ScreenWidths } from 'ts/types';
+import { Translate } from 'ts/utils/translate';
+import { utils } from 'ts/utils/utils';
+
+export interface InstantProps {
+ location: Location;
+ translate: Translate;
+ dispatcher: Dispatcher;
+ screenWidth: ScreenWidths;
+}
+
+export interface InstantState {}
+
+const CONFIGURATOR_HASH = 'configure';
+const THROTTLE_TIMEOUT = 100;
+const DOCUMENT_TITLE = '0x Instant';
+const DOCUMENT_DESCRIPTION = '0x Instant';
+
+export class Instant extends React.Component<InstantProps, InstantState> {
+ // TODO: consolidate this small screen scaffolding into one place (its being used in portal and docs as well)
+ private readonly _throttledScreenWidthUpdate: () => void;
+ public constructor(props: InstantProps) {
+ super(props);
+ this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
+ }
+ public componentDidMount(): void {
+ window.addEventListener('resize', this._throttledScreenWidthUpdate);
+ window.scrollTo(0, 0);
+ }
+ public render(): React.ReactNode {
+ return (
+ <Container overflowX="hidden">
+ <MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
+ <DocumentTitle title={DOCUMENT_TITLE} />
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ style={{ backgroundColor: colors.instantPrimaryBackground, position: 'relative' }}
+ translate={this.props.translate}
+ isNightVersion={true}
+ />
+ <Container backgroundColor={colors.instantPrimaryBackground} />
+ <Introducing0xInstant screenWidth={this.props.screenWidth} onCTAClick={this._onGetStartedClick} />
+ <Screenshots screenWidth={this.props.screenWidth} />
+ <Features screenWidth={this.props.screenWidth} onGetStartedClick={this._onGetStartedClick} />
+ {!this._isSmallScreen() && <Configurator hash={CONFIGURATOR_HASH} />}
+ <NeedMore screenWidth={this.props.screenWidth} />
+ <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} />
+ </Container>
+ );
+ }
+ private readonly _onGetStartedClick = () => {
+ if (this._isSmallScreen()) {
+ utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`);
+ } else {
+ this._scrollToConfigurator();
+ }
+ };
+ private _isSmallScreen(): boolean {
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
+ return isSmallScreen;
+ }
+ private _scrollToConfigurator(): void {
+ sharedUtils.setUrlHash(CONFIGURATOR_HASH);
+ sharedUtils.scrollToHash(CONFIGURATOR_HASH, '');
+ }
+ private _updateScreenWidth(): void {
+ const newScreenWidth = utils.getScreenWidth();
+ this.props.dispatcher.updateScreenWidth(newScreenWidth);
+ }
+}
diff --git a/packages/website/ts/pages/instant/introducing_0x_instant.tsx b/packages/website/ts/pages/instant/introducing_0x_instant.tsx
new file mode 100644
index 000000000..da3f09faa
--- /dev/null
+++ b/packages/website/ts/pages/instant/introducing_0x_instant.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+
+import { Button } from 'ts/components/ui/button';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { ScreenWidths } from 'ts/types';
+
+export interface Introducing0xInstantProps {
+ screenWidth: ScreenWidths;
+ onCTAClick: () => void;
+}
+
+export const Introducing0xInstant = (props: Introducing0xInstantProps) => {
+ const isSmallScreen = props.screenWidth === ScreenWidths.Sm;
+ const zero = (
+ <Text fontColor={colors.white} fontSize="42px" fontWeight="600" fontFamily="Roboto Mono" Tag="span">
+ 0
+ </Text>
+ );
+ const title = isSmallScreen ? (
+ <div>
+ Introducing<br />
+ {zero}x Instant
+ </div>
+ ) : (
+ <div>Introducing {zero}x Instant</div>
+ );
+ return (
+ <div className="clearfix center lg-pt4 md-pt4" style={{ backgroundColor: colors.instantPrimaryBackground }}>
+ <div className="mx-auto inline-block align-middle py4" style={{ lineHeight: '44px', textAlign: 'center' }}>
+ <Container className="sm-center sm-pt3">
+ <Text fontColor={colors.white} fontSize="42px" lineHeight="52px" fontWeight="600">
+ {title}
+ </Text>
+ </Container>
+ <Container className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 sm-center" maxWidth="600px">
+ <Text fontColor={colors.grey500} fontSize="20px" lineHeight="32px" fontFamily="Roboto Mono">
+ A free and flexible way to offer simple crypto
+ <br /> purchasing in any app or website.
+ </Text>
+ </Container>
+ <div className="py3">
+ <Button
+ type="button"
+ backgroundColor={colors.mediumBlue}
+ fontColor={colors.white}
+ fontSize="18px"
+ onClick={props.onCTAClick}
+ >
+ Get Started
+ </Button>
+ </div>
+ </div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/pages/instant/need_more.tsx b/packages/website/ts/pages/instant/need_more.tsx
new file mode 100644
index 000000000..e6d5c3694
--- /dev/null
+++ b/packages/website/ts/pages/instant/need_more.tsx
@@ -0,0 +1,62 @@
+import * as React from 'react';
+
+import { Button } from 'ts/components/ui/button';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { ScreenWidths } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+export interface NeedMoreProps {
+ screenWidth: ScreenWidths;
+}
+export const NeedMore = (props: NeedMoreProps) => {
+ const isSmallScreen = props.screenWidth === ScreenWidths.Sm;
+ const backgroundColor = isSmallScreen ? colors.instantTertiaryBackground : colors.instantSecondaryBackground;
+ const className = isSmallScreen ? 'flex flex-column items-center' : 'flex';
+ const marginRight = isSmallScreen ? undefined : '200px';
+ return (
+ <Container className="flex flex-column items-center py4 px3" backgroundColor={backgroundColor}>
+ <Container className={className}>
+ <Container className="sm-center" marginRight={marginRight}>
+ <Text fontColor={colors.white} fontSize="32px" lineHeight="45px">
+ Need more flexibility?
+ </Text>
+ <Text fontColor={colors.grey500} fontSize="18px" lineHeight="27px">
+ View our full documentation or reach out if you have any questions.
+ </Text>
+ </Container>
+ <Container className="py3 flex">
+ <Container marginRight="20px">
+ <Button
+ type="button"
+ backgroundColor={colors.white}
+ fontColor={backgroundColor}
+ fontSize="18px"
+ onClick={onGetInTouchClick}
+ >
+ Get in Touch
+ </Button>
+ </Container>
+ <Button
+ type="button"
+ backgroundColor={colors.mediumBlue}
+ fontColor={colors.white}
+ fontSize="18px"
+ onClick={onDocsClick}
+ >
+ Explore the Docs
+ </Button>
+ </Container>
+ </Container>
+ </Container>
+ );
+};
+
+const onGetInTouchClick = () => {
+ utils.openUrl(constants.URL_ZEROEX_CHAT);
+};
+const onDocsClick = () => {
+ utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`);
+};
diff --git a/packages/website/ts/pages/instant/screenshots.tsx b/packages/website/ts/pages/instant/screenshots.tsx
new file mode 100644
index 000000000..7dcf17fd1
--- /dev/null
+++ b/packages/website/ts/pages/instant/screenshots.tsx
@@ -0,0 +1,35 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Container } from 'ts/components/ui/container';
+import { colors } from 'ts/style/colors';
+import { ScreenWidths } from 'ts/types';
+
+export interface ScreenshotsProps {
+ screenWidth: ScreenWidths;
+}
+
+export const Screenshots = (props: ScreenshotsProps) => {
+ const isSmallScreen = props.screenWidth === ScreenWidths.Sm;
+ const images = isSmallScreen
+ ? [
+ 'images/instant/rep_screenshot.png',
+ 'images/instant/dai_screenshot.png',
+ 'images/instant/gods_screenshot.png',
+ ]
+ : [
+ 'images/instant/nmr_screenshot.png',
+ 'images/instant/kitty_screenshot.png',
+ 'images/instant/rep_screenshot.png',
+ 'images/instant/dai_screenshot.png',
+ 'images/instant/gods_screenshot.png',
+ 'images/instant/gnt_screenshot.png',
+ ];
+ return (
+ <Container backgroundColor={colors.instantPrimaryBackground} className="py3 flex justify-center">
+ {_.map(images, image => {
+ return <img className="px1 flex-none" width="300px" height="420px" src={image} key={image} />;
+ })}
+ </Container>
+ );
+};