diff options
author | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-10-20 06:40:44 +0800 |
---|---|---|
committer | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-10-20 06:40:44 +0800 |
commit | 30b077099306b8f2b522d0bc462da49fa9ee42e2 (patch) | |
tree | 05df98f7788993c2953d00acf64840de2ad6e9d4 /packages/instant | |
parent | d2766d7ced990efd5a91441b459f36e8a21f8513 (diff) | |
parent | a017f5e38561a152e8a757b340c1b0c6b3a3e21f (diff) | |
download | dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar.gz dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar.bz2 dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar.lz dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar.xz dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.tar.zst dexon-sol-tools-30b077099306b8f2b522d0bc462da49fa9ee42e2.zip |
Merge branch 'feature/instant/beta-render-et-al' into feature/instant/failure-state
Diffstat (limited to 'packages/instant')
22 files changed, 327 insertions, 189 deletions
diff --git a/packages/instant/.discharge.json b/packages/instant/.discharge.json new file mode 100644 index 000000000..9ade97d01 --- /dev/null +++ b/packages/instant/.discharge.json @@ -0,0 +1,13 @@ +{ + "domain": "0x-instant-dogfood", + "build_command": "yarn build:umd:prod", + "upload_directory": "public", + "index_key": "index.html", + "error_key": "index.html", + "trailing_slashes": true, + "cache": 3600, + "aws_profile": "default", + "aws_region": "us-east-1", + "cdn": false, + "dns_configured": true +} diff --git a/packages/instant/README.md b/packages/instant/README.md index 55b4404e7..07b01ac95 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -46,6 +46,18 @@ The package is also available as a UMD module named `zeroExInstant`. </body> ``` +## Deploying + +You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com for easy sharing. + +To build and deploy the site run + +``` +yarn deploy +``` + +**NOTE: On deploying the site, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** + ## Contributing We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository. diff --git a/packages/instant/package.json b/packages/instant/package.json index e5c4ee12d..1a6e9d2e9 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -22,6 +22,7 @@ "rebuild_and_test": "run-s clean build test", "test:circleci": "yarn test:coverage", "clean": "shx rm -rf lib coverage scripts", + "deploy": "discharge deploy", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { @@ -61,6 +62,7 @@ "ts-optchain": "^0.1.1" }, "devDependencies": { + "@static/discharge": "^1.2.2", "@0x/tslint-config": "^1.0.9", "@types/enzyme": "^3.1.14", "@types/enzyme-adapter-react-16": "^1.0.3", diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html index 1f6a0a9e9..14555fc64 100644 --- a/packages/instant/public/index.html +++ b/packages/instant/public/index.html @@ -25,7 +25,7 @@ <div id="zeroExInstantContainer"></div> <script> zeroExInstant.render({ - sraApiUrl: 'https://api.radarrelay.com/0x/v2/', + liquiditySource: 'https://api.radarrelay.com/0x/v2/', assetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', }); </script> diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx new file mode 100644 index 000000000..6ef8f0ac3 --- /dev/null +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Pulse } from './animations/pulse'; + +import { Text } from './ui'; + +interface PlainPlaceholder { + color: ColorOption; +} +const PlainPlaceholder: React.StatelessComponent<PlainPlaceholder> = props => ( + <Text fontWeight="bold" fontColor={props.color}> + — + </Text> +); + +export interface AmountPlaceholderProps { + color: ColorOption; + isPulsating: boolean; +} +export const AmountPlaceholder: React.StatelessComponent<AmountPlaceholderProps> = props => { + if (props.isPulsating) { + return ( + <Pulse> + <PlainPlaceholder color={props.color} /> + </Pulse> + ); + } else { + return <PlainPlaceholder color={props.color} />; + } +}; diff --git a/packages/instant/src/components/animations/pulse.tsx b/packages/instant/src/components/animations/pulse.tsx new file mode 100644 index 000000000..01d6ea070 --- /dev/null +++ b/packages/instant/src/components/animations/pulse.tsx @@ -0,0 +1,15 @@ +import { keyframes, styled } from '../../style/theme'; + +const pulsingKeyframes = keyframes` + 0%, 100% { + opacity: 0.2; + } + 50% { + opacity: 100; + } +`; +export const Pulse = styled.div` + animation-name: ${pulsingKeyframes} + animation-duration: 2s; + animation-iteration-count: infinite; +`; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 6fc5a0963..5d39e4048 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -7,95 +7,90 @@ import { ColorOption } from '../style/theme'; import { AsyncProcessState } from '../types'; import { format } from '../util/format'; +import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; buyOrderState: AsyncProcessState; } -const Placeholder = () => ( - <Text fontWeight="bold" fontColor={ColorOption.white}> - — - </Text> -); -const displaytotalEthBaseAmount = ({ - selectedAssetAmount, - totalEthBaseAmount, -}: InstantHeadingProps): React.ReactNode => { - if (_.isUndefined(selectedAssetAmount)) { - return '0 ETH'; +const placeholderColor = ColorOption.white; +export class InstantHeading extends React.Component<InstantHeadingProps, {}> { + public render(): React.ReactNode { + return ( + <Container + backgroundColor={ColorOption.primaryColor} + padding="20px" + width="100%" + borderRadius="3px 3px 0px 0px" + > + <Container marginBottom="5px"> + <Text + letterSpacing="1px" + fontColor={ColorOption.white} + opacity={0.7} + fontWeight={500} + textTransform="uppercase" + fontSize="12px" + > + {this._renderTopText()} + </Text> + </Container> + <Flex direction="row" justify="space-between"> + <SelectedAssetAmountInput fontSize="45px" /> + <Flex direction="column" justify="space-between"> + <Container marginBottom="5px">{this._placeholderOrAmount(this._ethAmount)}</Container> + <Container opacity={0.7}>{this._placeholderOrAmount(this._dollarAmount)}</Container> + </Flex> + </Flex> + </Container> + ); } - return format.ethBaseAmount(totalEthBaseAmount, 4, <Placeholder />); -}; -const displayUsdAmount = ({ - totalEthBaseAmount, - selectedAssetAmount, - ethUsdPrice, -}: InstantHeadingProps): React.ReactNode => { - if (_.isUndefined(selectedAssetAmount)) { - return '$0.00'; - } - return format.ethBaseAmountInUsd(totalEthBaseAmount, ethUsdPrice, 2, <Placeholder />); -}; + private _renderTopText(): React.ReactNode { + if (this.props.buyOrderState === AsyncProcessState.FAILURE) { + return 'Order failed'; + } -const loadingOrAmount = (quoteState: AsyncProcessState, amount: React.ReactNode): React.ReactNode => { - if (quoteState === AsyncProcessState.PENDING) { - return ( - <Text fontWeight="bold" fontColor={ColorOption.white}> - …loading - </Text> - ); - } else { - return amount; + return 'I want to buy'; } -}; -const topText = (buyOrderState: AsyncProcessState): string => { - if (buyOrderState === AsyncProcessState.FAILURE) { - return 'Order failed'; + private _placeholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { + if (this.props.quoteRequestState === AsyncProcessState.PENDING) { + return <AmountPlaceholder isPulsating={true} color={placeholderColor} />; + } + if (_.isUndefined(this.props.selectedAssetAmount)) { + return <AmountPlaceholder isPulsating={false} color={placeholderColor} />; + } + return amountFunction(); } - return 'I want to buy'; -}; + private readonly _ethAmount = (): React.ReactNode => { + return ( + <Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> + {format.ethBaseAmount( + this.props.totalEthBaseAmount, + 4, + <AmountPlaceholder isPulsating={false} color={placeholderColor} />, + )} + </Text> + ); + }; -export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = props => { - return ( - <Container - backgroundColor={ColorOption.primaryColor} - padding="20px" - width="100%" - borderRadius="3px 3px 0px 0px" - > - <Container marginBottom="5px"> - <Text - letterSpacing="1px" - fontColor={ColorOption.white} - opacity={0.7} - fontWeight={500} - textTransform="uppercase" - fontSize="12px" - > - {topText(props.buyOrderState)} - </Text> - </Container> - <Flex direction="row" justify="space-between"> - <SelectedAssetAmountInput fontSize="45px" /> - <Flex direction="column" justify="space-between"> - <Container marginBottom="5px"> - <Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> - {loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))} - </Text> - </Container> - <Text fontSize="16px" fontColor={ColorOption.white} opacity={0.7}> - {loadingOrAmount(props.quoteState, displayUsdAmount(props))} - </Text> - </Flex> - </Flex> - </Container> - ); -}; + private readonly _dollarAmount = (): React.ReactNode => { + return ( + <Text fontSize="16px" fontColor={ColorOption.white}> + {format.ethBaseAmountInUsd( + this.props.totalEthBaseAmount, + this.props.ethUsdPrice, + 2, + <AmountPlaceholder isPulsating={false} color={ColorOption.white} />, + )} + </Text> + ); + }; +} diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index ad4a87714..704009d89 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -7,13 +7,14 @@ import { oc } from 'ts-optchain'; import { ColorOption } from '../style/theme'; import { format } from '../util/format'; +import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; ethUsdPrice?: BigNumber; + isLoading: boolean; } - export class OrderDetails extends React.Component<OrderDetailsProps> { public render(): React.ReactNode { const { buyQuoteInfo, ethUsdPrice } = this.props; @@ -39,13 +40,20 @@ export class OrderDetails extends React.Component<OrderDetailsProps> { ethAmount={ethAssetPrice} ethUsdPrice={ethUsdPrice} isEthAmountInBaseUnits={false} + isLoading={this.props.isLoading} + /> + <EthAmountRow + rowLabel="Fee" + ethAmount={ethTokenFee} + ethUsdPrice={ethUsdPrice} + isLoading={this.props.isLoading} /> - <EthAmountRow rowLabel="Fee" ethAmount={ethTokenFee} ethUsdPrice={ethUsdPrice} /> <EthAmountRow rowLabel="Total Cost" ethAmount={totalEthAmount} ethUsdPrice={ethUsdPrice} shouldEmphasize={true} + isLoading={this.props.isLoading} /> </Container> ); @@ -58,43 +66,50 @@ export interface EthAmountRowProps { isEthAmountInBaseUnits?: boolean; ethUsdPrice?: BigNumber; shouldEmphasize?: boolean; + isLoading: boolean; } -export const EthAmountRow: React.StatelessComponent<EthAmountRowProps> = ({ - rowLabel, - ethAmount, - isEthAmountInBaseUnits, - ethUsdPrice, - shouldEmphasize, -}) => { - const fontWeight = shouldEmphasize ? 700 : 400; - const usdFormatter = isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; - const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; - const usdPriceSection = _.isUndefined(ethUsdPrice) ? null : ( - <Container marginRight="3px" display="inline-block"> - <Text fontColor={ColorOption.lightGrey}>({usdFormatter(ethAmount, ethUsdPrice)})</Text> - </Container> - ); - return ( - <Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}> - <Flex justify="space-between"> - <Text fontWeight={fontWeight} fontColor={ColorOption.grey}> - {rowLabel} - </Text> - <Container> - {usdPriceSection} +export class EthAmountRow extends React.Component<EthAmountRowProps> { + public static defaultProps = { + shouldEmphasize: false, + isEthAmountInBaseUnits: true, + }; + public render(): React.ReactNode { + const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props; + + const fontWeight = shouldEmphasize ? 700 : 400; + const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; + return ( + <Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}> + <Flex justify="space-between"> <Text fontWeight={fontWeight} fontColor={ColorOption.grey}> - {ethFormatter(ethAmount)} + {rowLabel} </Text> - </Container> - </Flex> - </Container> - ); -}; - -EthAmountRow.defaultProps = { - shouldEmphasize: false, - isEthAmountInBaseUnits: true, -}; - -EthAmountRow.displayName = 'EthAmountRow'; + <Container> + {this._renderUsdSection()} + <Text fontWeight={fontWeight} fontColor={ColorOption.grey}> + {ethFormatter( + ethAmount, + 4, + <Container opacity={0.5}> + <AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} /> + </Container>, + )} + </Text> + </Container> + </Flex> + </Container> + ); + } + private _renderUsdSection(): React.ReactNode { + const usdFormatter = this.props.isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; + const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount); + return shouldHideUsdPriceSection ? null : ( + <Container marginRight="3px" display="inline-block"> + <Text fontColor={ColorOption.lightGrey}> + ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)}) + </Text> + </Container> + ); + } +} diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 02b16e39f..5e2218c68 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -27,6 +27,7 @@ export interface ContainerProps { backgroundColor?: ColorOption; hasBoxShadow?: boolean; zIndex?: number; + opacity?: number; } const PlainContainer: React.StatelessComponent<ContainerProps> = ({ children, className }) => ( @@ -54,6 +55,7 @@ export const Container = styled(PlainContainer)` ${props => cssRuleIfExists(props, 'border-top')} ${props => cssRuleIfExists(props, 'border-bottom')} ${props => cssRuleIfExists(props, 'z-index')} + ${props => cssRuleIfExists(props, 'opacity')} ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index 5b75a7556..142e74dae 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -3,13 +3,12 @@ import { ObjectMap } from '@0x/types'; import * as React from 'react'; import { Provider } from 'react-redux'; -import { assetMetaDataMap } from '../data/asset_meta_data_map'; +import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; import { asyncData } from '../redux/async_data'; -import { State } from '../redux/reducer'; +import { INITIAL_STATE, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { theme, ThemeProvider } from '../style/theme'; -import { AssetMetaData } from '../types'; +import { AssetMetaData, Network } from '../types'; import { assetUtils } from '../util/asset'; import { getProvider } from '../util/provider'; @@ -17,32 +16,49 @@ import { ZeroExInstantContainer } from './zero_ex_instant_container'; fonts.include(); -export interface ZeroExInstantProps { +export type ZeroExInstantProps = ZeroExInstantRequiredProps & Partial<ZeroExInstantOptionalProps>; + +export interface ZeroExInstantRequiredProps { // TODO: Change API when we allow the selection of different assetDatas assetData: string; - sraApiUrl: string; + // TODO: Allow for a function that returns orders + liquiditySource: string; +} + +export interface ZeroExInstantOptionalProps { additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; + network: Network; } export class ZeroExInstant extends React.Component<ZeroExInstantProps> { - public static defaultProps = { - additionalAssetMetaDataMap: {}, - }; public store: Store; - constructor(props: ZeroExInstantProps) { - super(props); + private static _mergeInitialStateWithProps(props: ZeroExInstantProps, state: State = INITIAL_STATE): State { + // Create merged object such that properties in props override default settings + const optionalPropsWithDefaults: ZeroExInstantOptionalProps = { + additionalAssetMetaDataMap: props.additionalAssetMetaDataMap || {}, + network: props.network || state.network, + }; + const { network } = optionalPropsWithDefaults; // TODO: Provider needs to not be hard-coded to injected web3. - const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.sraApiUrl); + const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.liquiditySource, { + networkId: network, + }); const completeAssetMetaDataMap = { ...props.additionalAssetMetaDataMap, - ...assetMetaDataMap, + ...state.assetMetaDataMap, }; - const storeStateFromProps: Partial<State> = { + const storeStateFromProps: State = { + ...state, assetBuyer, - selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap), + network, + selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, network), assetMetaDataMap: completeAssetMetaDataMap, }; - this.store = store.create(storeStateFromProps); + return storeStateFromProps; + } + constructor(props: ZeroExInstantProps) { + super(props); + this.store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE)); // tslint:disable-next-line:no-floating-promises asyncData.fetchAndDispatchToStore(this.store); } @@ -50,9 +66,9 @@ export class ZeroExInstant extends React.Component<ZeroExInstantProps> { public render(): React.ReactNode { return ( <Provider store={this.store}> - <ThemeProvider theme={theme}> + <SelectedAssetThemeProvider> <ZeroExInstantContainer /> - </ThemeProvider> + </SelectedAssetThemeProvider> </Provider> ); } diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts index 597bf3088..092aaaf20 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -8,18 +8,21 @@ import { oc } from 'ts-optchain'; import { State } from '../redux/reducer'; import { OrderDetails } from '../components/order_details'; +import { AsyncProcessState } from '../types'; export interface LatestBuyQuoteOrderDetailsProps {} interface ConnectedState { buyQuoteInfo?: BuyQuoteInfo; ethUsdPrice?: BigNumber; + isLoading: boolean; } const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({ // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), ethUsdPrice: state.ethUsdPrice, + isLoading: state.quoteRequestState === AsyncProcessState.PENDING, }); export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect( diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 1d02cab23..b75ec00aa 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -3,8 +3,8 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { SlidingError } from '../components/sliding_error'; -import { LatestErrorDisplay, State } from '../redux/reducer'; -import { Asset } from '../types'; +import { State } from '../redux/reducer'; +import { Asset, DisplayStatus } from '../types'; import { errorUtil } from '../util/error'; export interface LatestErrorComponentProps { @@ -30,7 +30,7 @@ export interface LatestErrorProps {} const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({ asset: state.selectedAsset, latestError: state.latestError, - slidingDirection: state.latestErrorDisplay === LatestErrorDisplay.Present ? 'up' : 'down', + slidingDirection: state.latestErrorDisplay === DisplayStatus.Present ? 'up' : 'down', }); export const LatestError = connect(mapStateToProps)(LatestErrorComponent); diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index 6cd39b855..0d847cf02 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -62,13 +62,13 @@ const updateBuyQuoteAsync = async ( const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals); // mark quote as pending - dispatch(actions.updateBuyQuoteStatePending()); + dispatch(actions.setQuoteRequestStatePending()); let newBuyQuote: BuyQuote | undefined; try { newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue); } catch (error) { - dispatch(actions.updateBuyQuoteStateFailure()); + dispatch(actions.setQuoteRequestStateFailure()); errorUtil.errorFlasher.flashNewError(dispatch, error); return; } @@ -90,11 +90,11 @@ const mapDispatchToProps = ( // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(undefined)); // reset our buy state - dispatch(actions.updatebuyOrderState(AsyncProcessState.NONE)); + dispatch(actions.updateBuyOrderState(AsyncProcessState.NONE)); if (!_.isUndefined(value) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) { // even if it's debounced, give them the illusion it's loading - dispatch(actions.updateBuyQuoteStatePending()); + dispatch(actions.setQuoteRequestStatePending()); // tslint:disable-next-line:no-floating-promises debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value); } diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 7306ec2ec..208bb2582 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -18,7 +18,7 @@ interface ConnectedState { } interface ConnectedDispatch { - onBuyClick: (buyQuote: BuyQuote) => void; + onClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote) => void; onBuyFailure: (buyQuote: BuyQuote) => void; } @@ -29,9 +29,9 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): }); const mapDispatchToProps = (dispatch: Dispatch<Action>, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({ - onBuyClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), - onBuySuccess: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.SUCCESS)), - onBuyFailure: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.FAILURE)), + onClick: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.PENDING)), + onBuySuccess: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.SUCCESS)), + onBuyFailure: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.FAILURE)), }); export const SelectedAssetBuyButton: React.ComponentClass<SelectedAssetBuyButtonProps> = connect( diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts index 743362151..24efed32e 100644 --- a/packages/instant/src/containers/selected_asset_instant_heading.ts +++ b/packages/instant/src/containers/selected_asset_instant_heading.ts @@ -15,7 +15,7 @@ interface ConnectedState { selectedAssetAmount?: BigNumber; totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; buyOrderState: AsyncProcessState; } @@ -23,7 +23,7 @@ const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): Connecte selectedAssetAmount: state.selectedAssetAmount, totalEthBaseAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(), ethUsdPrice: state.ethUsdPrice, - quoteState: state.quoteState, + quoteRequestState: state.quoteRequestState, buyOrderState: state.buyOrderState, }); diff --git a/packages/instant/src/containers/selected_asset_theme_provider.ts b/packages/instant/src/containers/selected_asset_theme_provider.ts new file mode 100644 index 000000000..6e6b83d73 --- /dev/null +++ b/packages/instant/src/containers/selected_asset_theme_provider.ts @@ -0,0 +1,32 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; + +import { State } from '../redux/reducer'; +import { Theme, theme as defaultTheme, ThemeProvider } from '../style/theme'; +import { Asset } from '../types'; + +export interface SelectedAssetThemeProviderProps {} + +interface ConnectedState { + theme: Theme; +} + +const getTheme = (asset?: Asset): Theme => { + if (!_.isUndefined(asset) && !_.isUndefined(asset.metaData.primaryColor)) { + return { + ...defaultTheme, + primaryColor: asset.metaData.primaryColor, + }; + } + return defaultTheme; +}; + +const mapStateToProps = (state: State, _ownProps: SelectedAssetThemeProviderProps): ConnectedState => { + const theme = getTheme(state.selectedAsset); + return { theme }; +}; + +export const SelectedAssetThemeProvider: React.ComponentClass<SelectedAssetThemeProviderProps> = connect( + mapStateToProps, +)(ThemeProvider); diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index d6388d77e..bdb53a395 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -26,8 +26,8 @@ export enum ActionTypes { UPDATE_SELECTED_ASSET_BUY_STATE = 'UPDATE_SELECTED_ASSET_BUY_STATE', UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE', UPDATE_SELECTED_ASSET = 'UPDATE_SELECTED_ASSET', - UPDATE_BUY_QUOTE_STATE_PENDING = 'UPDATE_BUY_QUOTE_STATE_PENDING', - UPDATE_BUY_QUOTE_STATE_FAILURE = 'UPDATE_BUY_QUOTE_STATE_FAILURE', + SET_QUOTE_REQUEST_STATE_PENDING = 'SET_QUOTE_REQUEST_STATE_PENDING', + SET_QUOTE_REQUEST_STATE_FAILURE = 'SET_QUOTE_REQUEST_STATE_FAILURE', SET_ERROR = 'SET_ERROR', HIDE_ERROR = 'HIDE_ERROR', CLEAR_ERROR = 'CLEAR_ERROR', @@ -37,12 +37,12 @@ export enum ActionTypes { export const actions = { updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price), updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount), - updatebuyOrderState: (buyState: AsyncProcessState) => + updateBuyOrderState: (buyState: AsyncProcessState) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, buyState), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), updateSelectedAsset: (assetData?: string) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, assetData), - updateBuyQuoteStatePending: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING), - updateBuyQuoteStateFailure: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE), + setQuoteRequestStatePending: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING), + setQuoteRequestStateFailure: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE), setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error), hideError: () => createAction(ActionTypes.HIDE_ERROR), clearError: () => createAction(ActionTypes.CLEAR_ERROR), diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 10e56f152..260039e3d 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -3,16 +3,14 @@ import { ObjectMap } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { Asset, AssetMetaData, AsyncProcessState } from '../types'; +import { assetMetaDataMap } from '../data/asset_meta_data_map'; +import { Asset, AssetMetaData, AsyncProcessState, DisplayStatus, Network } from '../types'; import { assetUtils } from '../util/asset'; import { Action, ActionTypes } from './actions'; -export enum LatestErrorDisplay { - Present, - Hidden, -} export interface State { + network: Network; assetBuyer?: AssetBuyer; assetMetaDataMap: ObjectMap<AssetMetaData>; selectedAsset?: Asset; @@ -20,23 +18,23 @@ export interface State { buyOrderState: AsyncProcessState; ethUsdPrice?: BigNumber; latestBuyQuote?: BuyQuote; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; latestError?: any; - latestErrorDisplay: LatestErrorDisplay; + latestErrorDisplay: DisplayStatus; } export const INITIAL_STATE: State = { + network: Network.Mainnet, selectedAssetAmount: undefined, - assetMetaDataMap: {}, + assetMetaDataMap, buyOrderState: AsyncProcessState.NONE, ethUsdPrice: undefined, latestBuyQuote: undefined, latestError: undefined, - latestErrorDisplay: LatestErrorDisplay.Hidden, - quoteState: AsyncProcessState.NONE, + latestErrorDisplay: DisplayStatus.Hidden, + quoteRequestState: AsyncProcessState.NONE, }; -// TODO: Figure out why there is an INITIAL_STATE key in the store... export const reducer = (state: State = INITIAL_STATE, action: Action): State => { switch (action.type) { case ActionTypes.UPDATE_ETH_USD_PRICE: @@ -53,19 +51,19 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => return { ...state, latestBuyQuote: action.data, - quoteState: AsyncProcessState.SUCCESS, + quoteRequestState: AsyncProcessState.SUCCESS, }; - case ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING: + case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING: return { ...state, latestBuyQuote: undefined, - quoteState: AsyncProcessState.PENDING, + quoteRequestState: AsyncProcessState.PENDING, }; - case ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE: + case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE: return { ...state, latestBuyQuote: undefined, - quoteState: AsyncProcessState.FAILURE, + quoteRequestState: AsyncProcessState.FAILURE, }; case ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE: return { @@ -76,24 +74,28 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => return { ...state, latestError: action.data, - latestErrorDisplay: LatestErrorDisplay.Present, + latestErrorDisplay: DisplayStatus.Present, }; case ActionTypes.HIDE_ERROR: return { ...state, - latestErrorDisplay: LatestErrorDisplay.Hidden, + latestErrorDisplay: DisplayStatus.Hidden, }; case ActionTypes.CLEAR_ERROR: return { ...state, latestError: undefined, - latestErrorDisplay: LatestErrorDisplay.Hidden, + latestErrorDisplay: DisplayStatus.Hidden, }; case ActionTypes.UPDATE_SELECTED_ASSET: const newSelectedAssetData = action.data; let newSelectedAsset: Asset | undefined; if (!_.isUndefined(newSelectedAssetData)) { - newSelectedAsset = assetUtils.createAssetFromAssetData(newSelectedAssetData, state.assetMetaDataMap); + newSelectedAsset = assetUtils.createAssetFromAssetData( + newSelectedAssetData, + state.assetMetaDataMap, + state.network, + ); } return { ...state, @@ -103,7 +105,7 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => return { ...state, latestBuyQuote: undefined, - quoteState: AsyncProcessState.NONE, + quoteRequestState: AsyncProcessState.NONE, buyOrderState: AsyncProcessState.NONE, selectedAssetAmount: undefined, }; diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 505234299..01deb8690 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -2,17 +2,12 @@ import * as _ from 'lodash'; import { createStore, Store as ReduxStore } from 'redux'; import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; -import { INITIAL_STATE, reducer, State } from './reducer'; +import { reducer, State } from './reducer'; export type Store = ReduxStore<State>; export const store = { - create: (withState: Partial<State>): Store => { - const allInitialState = { - INITIAL_STATE, - ...withState, - }; - return createStore(reducer, allInitialState, devToolsEnhancer({})); + create: (state: State): Store => { + return createStore(reducer, state, devToolsEnhancer({})); }, }; - diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 0454bceea..7323123c3 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -7,6 +7,10 @@ export enum AsyncProcessState { SUCCESS = 'Success', FAILURE = 'Failure', } +export enum DisplayStatus { + Present, + Hidden, +} export type FunctionType = (...args: any[]) => any; export type ActionCreatorsMapObject = ObjectMap<FunctionType>; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index a105a7c2b..4e3b2b946 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -1,4 +1,3 @@ -import { assetDataUtils } from '@0x/order-utils'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; @@ -6,17 +5,17 @@ import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, Network, ZeroExInstantError } from '../types'; export const assetUtils = { - createAssetFromAssetData: (assetData: string, assetMetaDataMap: ObjectMap<AssetMetaData>): Asset => { + createAssetFromAssetData: ( + assetData: string, + assetMetaDataMap: ObjectMap<AssetMetaData>, + network: Network, + ): Asset => { return { assetData, - metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap), + metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap, network), }; }, - getMetaDataOrThrow: ( - assetData: string, - metaDataMap: ObjectMap<AssetMetaData>, - network: Network = Network.Mainnet, - ): AssetMetaData => { + getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => { let mainnetAssetData: string | undefined = assetData; if (network !== Network.Mainnet) { mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); @@ -39,7 +38,7 @@ export const assetUtils = { case AssetProxyId.ERC20: return metaData.symbol.toUpperCase(); case AssetProxyId.ERC721: - return metaData.name.toUpperCase(); + return metaData.name; default: return defaultName; } diff --git a/packages/instant/tslint.json b/packages/instant/tslint.json index abe874a6d..08b76be97 100644 --- a/packages/instant/tslint.json +++ b/packages/instant/tslint.json @@ -2,6 +2,7 @@ "extends": ["@0x/tslint-config"], "rules": { "custom-no-magic-numbers": false, - "semicolon": [true, "always", "ignore-bound-class-methods"] + "semicolon": [true, "always", "ignore-bound-class-methods"], + "max-classes-per-file": false } } |