aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/pages
diff options
context:
space:
mode:
authorFrancesco Agosti <francesco.agosti93@gmail.com>2018-12-06 07:44:54 +0800
committerGitHub <noreply@github.com>2018-12-06 07:44:54 +0800
commit21122f0137c39835e5cf15e1a5c2bdbd20030611 (patch)
treeb0b38b40bac88612a5dad019e37087e4a27ff054 /packages/website/ts/pages
parente6acc0416a0de54d53c8f50e522ee2a952c58965 (diff)
parentc282c2fcc40b6efbac7c91c3d17b204756359a26 (diff)
downloaddexon-0x-contracts-21122f0137c39835e5cf15e1a5c2bdbd20030611.tar.gz
dexon-0x-contracts-21122f0137c39835e5cf15e1a5c2bdbd20030611.tar.zst
dexon-0x-contracts-21122f0137c39835e5cf15e1a5c2bdbd20030611.zip
Merge pull request #1369 from 0xProject/feature/website/instant-configurator
[website][instant] Instant configurator
Diffstat (limited to 'packages/website/ts/pages')
-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.tsx177
-rw-r--r--packages/website/ts/pages/instant/config_generator.tsx306
-rw-r--r--packages/website/ts/pages/instant/config_generator_address_input.tsx59
-rw-r--r--packages/website/ts/pages/instant/configurator.tsx104
-rw-r--r--packages/website/ts/pages/instant/features.tsx53
-rw-r--r--packages/website/ts/pages/instant/fee_percentage_slider.tsx72
-rw-r--r--packages/website/ts/pages/instant/instant.tsx4
-rw-r--r--packages/website/ts/pages/instant/need_more.tsx4
-rw-r--r--packages/website/ts/pages/landing/landing.tsx4
11 files changed, 782 insertions, 51 deletions
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..1bc1980e7
--- /dev/null
+++ b/packages/website/ts/pages/instant/code_demo.tsx
@@ -0,0 +1,177 @@
+import * as React from 'react';
+import * as CopyToClipboard from 'react-copy-to-clipboard';
+import SyntaxHighlighter from 'react-syntax-highlighter';
+
+import { Button } from 'ts/components/ui/button';
+import { Container } from 'ts/components/ui/container';
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+import { zIndex } from 'ts/style/z_index';
+
+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: 98%;
+ 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 interface CodeDemoState {
+ didCopyCode: boolean;
+}
+
+export class CodeDemo extends React.Component<CodeDemoProps, CodeDemoState> {
+ public state: CodeDemoState = {
+ didCopyCode: false,
+ };
+ public render(): React.ReactNode {
+ const copyButtonText = this.state.didCopyCode ? 'Copied!' : 'Copy';
+ return (
+ <Container position="relative" height="100%">
+ <Container position="absolute" top="10px" right="10px" zIndex={zIndex.overlay - 1}>
+ <CopyToClipboard text={this.props.children} onCopy={this._handleCopyClick}>
+ <Button fontSize="14px">
+ <b>{copyButtonText}</b>
+ </Button>
+ </CopyToClipboard>
+ </Container>
+ <SyntaxHighlighter language="html" style={customStyle} showLineNumbers={true} PreTag={CustomPre}>
+ {this.props.children}
+ </SyntaxHighlighter>
+ </Container>
+ );
+ }
+ private readonly _handleCopyClick = () => {
+ this.setState({ didCopyCode: true });
+ };
+}
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..fe70ef04c
--- /dev/null
+++ b/packages/website/ts/pages/instant/config_generator.tsx
@@ -0,0 +1,306 @@
+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 * 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 { FeePercentageSlider } from 'ts/pages/instant/fee_percentage_slider';
+import { colors } from 'ts/style/colors';
+import { WebsitePaths } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+
+import { assetMetaDataMap } from '../../../../instant/src/data/asset_meta_data_map';
+import { ERC20AssetMetaData, ZeroExInstantBaseConfig } from '../../../../instant/src/types';
+
+export interface ConfigGeneratorProps {
+ value: ZeroExInstantBaseConfig;
+ onConfigChange: (config: ZeroExInstantBaseConfig) => void;
+}
+
+export interface ConfigGeneratorState {
+ isLoadingAvailableTokens: boolean;
+ // Address to token info
+ availableTokens?: ObjectMap<ERC20AssetMetaData>;
+}
+
+const SRA_ENDPOINTS = ['https://api.radarrelay.com/0x/v2/', 'https://sra.bamboorelay.com/0x/v2/'];
+
+export class ConfigGenerator extends React.Component<ConfigGeneratorProps, ConfigGeneratorState> {
+ public state: ConfigGeneratorState = {
+ isLoadingAvailableTokens: true,
+ };
+ public componentDidMount(): void {
+ // tslint:disable-next-line:no-floating-promises
+ this._setAvailableAssetsFromOrderProvider();
+ }
+ public componentDidUpdate(prevProps: ConfigGeneratorProps): void {
+ if (prevProps.value.orderSource !== this.props.value.orderSource) {
+ // tslint:disable-next-line:no-floating-promises
+ this._setAvailableAssetsFromOrderProvider();
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ availableAssetDatas: undefined,
+ };
+ this.props.onConfigChange(newConfig);
+ }
+ }
+ 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}
+ >
+ <FeePercentageSlider
+ value={value.affiliateInfo.feePercentage}
+ onChange={this._handleAffiliatePercentageChange}
+ />
+ </ConfigGeneratorSection>
+ </Container>
+ );
+ }
+ private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => {
+ if (_.isEmpty(this.state.availableTokens)) {
+ return {
+ title: 'What tokens can users buy?',
+ };
+ }
+ 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 _handleAffiliatePercentageLearnMoreClick = (): void => {
+ window.open(`${WebsitePaths.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, isValid: boolean) => {
+ const oldConfig: ZeroExInstantBaseConfig = this.props.value;
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...oldConfig,
+ affiliateInfo: {
+ feeRecipient: address,
+ feePercentage: oldConfig.affiliateInfo.feePercentage,
+ },
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ private readonly _handleAffiliatePercentageChange = (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;
+ let newAvailableAssetDatas: string[] = [];
+ const allKnownAssetDatas = _.keys(this.state.availableTokens);
+ const availableAssetDatas = value.availableAssetDatas;
+ if (_.isUndefined(availableAssetDatas)) {
+ // It being undefined means it's all tokens.
+ newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData);
+ } else if (!_.includes(availableAssetDatas, assetData)) {
+ // Add it
+ newAvailableAssetDatas = [...availableAssetDatas, assetData];
+ if (newAvailableAssetDatas.length === allKnownAssetDatas.length) {
+ // If all tokens are manually selected, just show none.
+ newAvailableAssetDatas = undefined;
+ }
+ } else {
+ // Remove it
+ newAvailableAssetDatas = _.pull(availableAssetDatas, assetData);
+ }
+ const newConfig: ZeroExInstantBaseConfig = {
+ ...this.props.value,
+ availableAssetDatas: newAvailableAssetDatas,
+ };
+ this.props.onConfigChange(newConfig);
+ };
+ 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 = _.reduce(
+ assetDatas,
+ (acc, assetData) => {
+ const metaDataIfExists = assetMetaDataMap[assetData] as ERC20AssetMetaData;
+ if (metaDataIfExists) {
+ acc[assetData] = metaDataIfExists;
+ }
+ return acc;
+ },
+ {} as ObjectMap<ERC20AssetMetaData>,
+ );
+ 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 availableAssetDatas = _.keys(availableTokens);
+ if (availableAssetDatas.length === 0) {
+ return (
+ <Container
+ className="flex flex-column items-center justify-center"
+ height={multiSelectHeight}
+ backgroundColor={colors.white}
+ borderRadius="4px"
+ width="100%"
+ >
+ <Text fontSize="16px">No tokens available. Try another endpoint?</Text>
+ </Container>
+ );
+ }
+ const items = _.map(_.keys(availableTokens), assetData => {
+ const metaData = availableTokens[assetData];
+ return {
+ value: assetData,
+ 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>{metaData.symbol.toUpperCase()}</b> — {metaData.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" display="inline">
+ {title}
+ </Text>
+ {isOptional && (
+ <Text fontColor={colors.grey} fontSize="16px" lineHeight="18px" display="inline">
+ {' '}
+ (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..ccbaf4482
--- /dev/null
+++ b/packages/website/ts/pages/instant/config_generator_address_input.tsx
@@ -0,0 +1,59 @@
+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, isValid: boolean) => 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"
+ padding="0.7em 1em"
+ 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, isValidAddress);
+ };
+}
diff --git a/packages/website/ts/pages/instant/configurator.tsx b/packages/website/ts/pages/instant/configurator.tsx
index c836739bb..a6c792edf 100644
--- a/packages/website/ts/pages/instant/configurator.tsx
+++ b/packages/website/ts/pages/instant/configurator.tsx
@@ -1,12 +1,110 @@
+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 { WebsitePaths } from 'ts/types';
+
+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: undefined,
+ affiliateInfo: {
+ feeRecipient: '',
+ feePercentage: 0,
+ },
+ },
+ };
+ 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={`${WebsitePaths.Wiki}#Get-Started-With-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 `<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <script src="https://instant.0xproject.com/instant.js"></script>
+ </head>
+ <body>
+ <script>
+ zeroExInstant.render({
+ orderSource: '${instantConfig.orderSource}',${
+ !_.isUndefined(instantConfig.affiliateInfo) && instantConfig.affiliateInfo.feeRecipient
+ ? `\n affiliateInfo: {
+ feeRecipient: '${instantConfig.affiliateInfo.feeRecipient.toLowerCase()}',
+ feePercentage: ${instantConfig.affiliateInfo.feePercentage}
+ }`
+ : ''
+ }${
+ !_.isUndefined(instantConfig.availableAssetDatas)
+ ? `\n availableAssetDatas: ${this._renderAvailableAssetDatasString(
+ instantConfig.availableAssetDatas,
+ )}`
+ : ''
+ }
+ }, 'body');
+ </script>
+ </body>
+</html>`;
+ };
+ private readonly _renderAvailableAssetDatasString = (availableAssetDatas: string[]): string => {
+ const stringAvailableAssetDatas = availableAssetDatas.map(assetData => `'${assetData}'`);
+ if (availableAssetDatas.length < 2) {
+ return `[${stringAvailableAssetDatas.join(', ')}]`;
+ }
+ return `[\n ${stringAvailableAssetDatas.join(
+ ', \n ',
+ )}\n ]`;
+ };
+}
diff --git a/packages/website/ts/pages/instant/features.tsx b/packages/website/ts/pages/instant/features.tsx
index 9c1668c1c..ed98ceb53 100644
--- a/packages/website/ts/pages/instant/features.tsx
+++ b/packages/website/ts/pages/instant/features.tsx
@@ -4,9 +4,9 @@ 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';
+import { ScreenWidths, WebsitePaths } from 'ts/types';
export interface FeatureProps {
screenWidth: ScreenWidths;
@@ -21,7 +21,7 @@ export const Features = (props: FeatureProps) => {
};
const exploreTheDocsLinkInfo = {
displayText: 'Explore the docs',
- linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Get-Started`,
+ linkSrc: `${WebsitePaths.Wiki}#Get-Started-With-Instant`,
};
const tokenLinkInfos = isSmallScreen ? [getStartedLinkInfo] : [getStartedLinkInfo, exploreTheDocsLinkInfo];
return (
@@ -40,7 +40,7 @@ export const Features = (props: FeatureProps) => {
linkInfos={[
{
displayText: 'Learn about affiliate fees',
- linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Learn-About-Affiliate-Fees`,
+ linkSrc: `${WebsitePaths.Wiki}#Learn-About-Affiliate-Fees`,
},
]}
screenWidth={props.screenWidth}
@@ -52,7 +52,7 @@ export const Features = (props: FeatureProps) => {
linkInfos={[
{
displayText: 'Explore AssetBuyer',
- linkSrc: `${utils.getCurrentBaseUrl()}/docs/asset-buyer`,
+ linkSrc: `${WebsitePaths.Docs}/asset-buyer`,
},
]}
screenWidth={props.screenWidth}
@@ -61,17 +61,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 +89,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/packages/website/ts/pages/instant/fee_percentage_slider.tsx b/packages/website/ts/pages/instant/fee_percentage_slider.tsx
new file mode 100644
index 000000000..4c92883cb
--- /dev/null
+++ b/packages/website/ts/pages/instant/fee_percentage_slider.tsx
@@ -0,0 +1,72 @@
+import Slider from 'rc-slider';
+import 'rc-slider/assets/index.css';
+import * as React from 'react';
+
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { injectGlobal } from 'ts/style/theme';
+
+const SliderWithTooltip = (Slider as any).createSliderWithTooltip(Slider);
+// tslint:disable-next-line:no-unused-expression
+injectGlobal`
+ .rc-slider-tooltip-inner {
+ box-shadow: none !important;
+ background-color: ${colors.white} !important;
+ border-radius: 4px !important;
+ padding: 3px 12px !important;
+ height: auto !important;
+ position: relative;
+ top: 7px;
+ &: after {
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-width: 6px;
+ bottom: 100%;
+ left: 100%;
+ border-bottom-color: ${colors.white};
+ margin-left: -60%;
+ }
+ }
+`;
+
+export interface FeePercentageSliderProps {
+ value: number;
+ onChange: (value: number) => void;
+}
+
+export class FeePercentageSlider extends React.Component<FeePercentageSliderProps> {
+ public render(): React.ReactNode {
+ return (
+ <SliderWithTooltip
+ min={0}
+ max={0.05}
+ step={0.0025}
+ value={this.props.value}
+ onChange={this.props.onChange}
+ tipFormatter={this._feePercentageSliderFormatter}
+ tipProps={{ placement: 'bottom' }}
+ trackStyle={{
+ backgroundColor: '#b4b4b4',
+ }}
+ railStyle={{
+ backgroundColor: '#696969',
+ }}
+ handleStyle={{
+ border: 'none',
+ boxShadow: 'none',
+ }}
+ activeDotStyle={{
+ boxShadow: 'none',
+ border: 'none',
+ }}
+ />
+ );
+ }
+ private readonly _feePercentageSliderFormatter = (value: number): React.ReactNode => {
+ return <Text fontColor={colors.black} fontSize="14px" fontWeight={700}>{`${(value * 100).toFixed(2)}%`}</Text>;
+ };
+}
diff --git a/packages/website/ts/pages/instant/instant.tsx b/packages/website/ts/pages/instant/instant.tsx
index fa6bd1c33..d72585bfa 100644
--- a/packages/website/ts/pages/instant/instant.tsx
+++ b/packages/website/ts/pages/instant/instant.tsx
@@ -14,7 +14,7 @@ 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 { ScreenWidths, WebsitePaths } from 'ts/types';
import { Translate } from 'ts/utils/translate';
import { utils } from 'ts/utils/utils';
@@ -67,7 +67,7 @@ export class Instant extends React.Component<InstantProps, InstantState> {
}
private readonly _onGetStartedClick = () => {
if (this._isSmallScreen()) {
- utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`);
+ utils.openUrl(`${WebsitePaths.Wiki}#Get-Started-With-Instant`);
} else {
this._scrollToConfigurator();
}
diff --git a/packages/website/ts/pages/instant/need_more.tsx b/packages/website/ts/pages/instant/need_more.tsx
index e6d5c3694..70aea7363 100644
--- a/packages/website/ts/pages/instant/need_more.tsx
+++ b/packages/website/ts/pages/instant/need_more.tsx
@@ -4,7 +4,7 @@ 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 { ScreenWidths, WebsitePaths } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
@@ -58,5 +58,5 @@ const onGetInTouchClick = () => {
utils.openUrl(constants.URL_ZEROEX_CHAT);
};
const onDocsClick = () => {
- utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`);
+ utils.openUrl(`${WebsitePaths.Wiki}#Get-Started-With-Instant`);
};
diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx
index bb76efe21..d7ed0524c 100644
--- a/packages/website/ts/pages/landing/landing.tsx
+++ b/packages/website/ts/pages/landing/landing.tsx
@@ -36,8 +36,8 @@ interface Project {
}
const THROTTLE_TIMEOUT = 100;
-const WHATS_NEW_TITLE = 'Introducing the 0x Launch Kit';
-const WHATS_NEW_URL = 'https://blog.0xproject.com/introducing-the-0x-launch-kit-4acdc3453585';
+const WHATS_NEW_TITLE = 'Introducing 0x Instant';
+const WHATS_NEW_URL = WebsitePaths.Instant;
const TITLE_STYLE: React.CSSProperties = {
fontFamily: 'Roboto Mono',
color: colors.grey,