diff options
author | Fred Carlsen <fred@sjelfull.no> | 2018-11-27 23:19:36 +0800 |
---|---|---|
committer | Fred Carlsen <fred@sjelfull.no> | 2018-11-27 23:19:36 +0800 |
commit | 9b0020a884bdb98f9da434878fec171ab533ba7e (patch) | |
tree | ca285eb7dfd2c68f781b8b74d409fb69665a58dc /packages/instant | |
parent | ec12e46e3f52ebb44338c113cecd073979821fae (diff) | |
parent | fdc4a4e5fa31d8797abf6313f4488147853ed77e (diff) | |
download | dexon-0x-contracts-9b0020a884bdb98f9da434878fec171ab533ba7e.tar.gz dexon-0x-contracts-9b0020a884bdb98f9da434878fec171ab533ba7e.tar.zst dexon-0x-contracts-9b0020a884bdb98f9da434878fec171ab533ba7e.zip |
Merge remote-tracking branch 'upstream/development' into website
Diffstat (limited to 'packages/instant')
-rw-r--r-- | packages/instant/.npmignore | 2 | ||||
-rw-r--r-- | packages/instant/CHANGELOG.json | 12 | ||||
-rw-r--r-- | packages/instant/CHANGELOG.md | 9 | ||||
-rw-r--r-- | packages/instant/package.json | 26 | ||||
-rw-r--r-- | packages/instant/public/index.html | 329 | ||||
-rw-r--r-- | packages/instant/src/components/erc20_asset_amount_input.tsx | 3 | ||||
-rw-r--r-- | packages/instant/src/components/instant_heading.tsx | 4 | ||||
-rw-r--r-- | packages/instant/src/components/scaling_amount_input.tsx | 3 | ||||
-rw-r--r-- | packages/instant/src/components/scaling_input.tsx | 5 | ||||
-rw-r--r-- | packages/instant/src/components/ui/text.tsx | 4 | ||||
-rw-r--r-- | packages/instant/src/components/zero_ex_instant_provider.tsx | 20 | ||||
-rw-r--r-- | packages/instant/src/containers/connected_account_payment_method.ts | 8 | ||||
-rw-r--r-- | packages/instant/src/index.umd.ts | 111 | ||||
-rw-r--r-- | packages/instant/src/redux/analytics_middleware.ts | 34 | ||||
-rw-r--r-- | packages/instant/src/redux/async_data.ts | 2 | ||||
-rw-r--r-- | packages/instant/src/util/analytics.ts | 57 | ||||
-rw-r--r-- | packages/instant/src/util/heap.ts | 4 | ||||
-rw-r--r-- | packages/instant/tsconfig.json | 11 |
18 files changed, 417 insertions, 227 deletions
diff --git a/packages/instant/.npmignore b/packages/instant/.npmignore index 579d65af0..a4f7810c0 100644 --- a/packages/instant/.npmignore +++ b/packages/instant/.npmignore @@ -1,5 +1,5 @@ .* * */ -!lib/src/**/* +!lib/**/* !umd/**/*
\ No newline at end of file diff --git a/packages/instant/CHANGELOG.json b/packages/instant/CHANGELOG.json index fe51488c7..db187140f 100644 --- a/packages/instant/CHANGELOG.json +++ b/packages/instant/CHANGELOG.json @@ -1 +1,11 @@ -[] +[ + { + "timestamp": 1542821676, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/packages/instant/CHANGELOG.md b/packages/instant/CHANGELOG.md index 8b1378917..c8be0c644 100644 --- a/packages/instant/CHANGELOG.md +++ b/packages/instant/CHANGELOG.md @@ -1 +1,10 @@ +<!-- +changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly. +Edit the package's CHANGELOG.json file only. +--> +CHANGELOG + +## v1.0.1 - _November 21, 2018_ + + * Dependencies updated diff --git a/packages/instant/package.json b/packages/instant/package.json index 1813d61e5..4422dc83f 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,13 +1,13 @@ { "name": "@0x/instant", - "version": "1.0.0", + "version": "1.0.1", "engines": { "node": ">=6.12" }, - "description": "0x Instant React Component", "private": true, - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", + "description": "0x Instant React Component", + "main": "lib/index.js", + "types": "lib/index.d.ts", "scripts": { "build": "yarn build:all", "build:all": "run-p build:umd:prod build:commonjs", @@ -45,15 +45,15 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { - "@0x/assert": "^1.0.17", - "@0x/asset-buyer": "^3.0.0", - "@0x/json-schemas": "^2.1.1", - "@0x/order-utils": "^3.0.2", - "@0x/subproviders": "^2.1.4", - "@0x/types": "^1.2.1", + "@0x/assert": "^1.0.18", + "@0x/asset-buyer": "^3.0.1", + "@0x/json-schemas": "^2.1.2", + "@0x/order-utils": "^3.0.3", + "@0x/subproviders": "^2.1.5", + "@0x/types": "^1.3.0", "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.5", - "@0x/web3-wrapper": "^3.1.4", + "@0x/utils": "^2.0.6", + "@0x/web3-wrapper": "^3.1.5", "bowser": "^1.9.4", "copy-to-clipboard": "^3.0.8", "ethereum-types": "^1.1.2", @@ -99,6 +99,6 @@ "webpack-dev-server": "^3.1.9" }, "publishConfig": { - "access": "private" + "access": "public" } } diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html index 253cbb194..df39994ef 100644 --- a/packages/instant/public/index.html +++ b/packages/instant/public/index.html @@ -1,137 +1,206 @@ <!DOCTYPE html> <html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>0x Instant Dev Environment</title> + <link rel="stylesheet" href="/external.css" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" /> + <script type="text/javascript" src="/instant.js" charset="utf-8"></script> + <script type="text/javascript" src="https://unpkg.com/jsuri@1.3.1/Uri.js" charset="utf-8"></script> + <script type="text/javascript" src="https://unpkg.com/bignumber.js@4.1.0/bignumber.js" charset="utf-8"></script> + <style> + .flex-center { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + } -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>0x Instant Dev Environment</title> - <link rel="stylesheet" href="/external.css"> - <script type="text/javascript" src="/instant.js" charset="utf-8"></script> - <script type="text/javascript" src="https://unpkg.com/jsuri@1.3.1/Uri.js" charset="utf-8"></script> - <script type="text/javascript" src="https://unpkg.com/bignumber.js@4.1.0/bignumber.js" charset="utf-8"></script> - <style> - #zeroExInstantContainer { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - } + .flex-center--column { + flex-direction: column; + } - body { - margin: 0; - background-color: rgba(0, 0, 0, 0.2); - } - </style> -</head> + .button { + margin: 10px; + } -<body> - <div id="zeroExInstantContainer"></div> - <script> - const removeUndefined = (obj) => { - for (let k in obj) if (obj[k] === undefined) delete obj[k]; - return obj; - } - BigNumber.config({ - EXPONENTIAL_AT: 1000, - DECIMAL_PLACES: 78, - }); - const providedOrders = [ - // Order selling REP - { - senderAddress: '0x0000000000000000000000000000000000000000', - makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', - takerAddress: '0x0000000000000000000000000000000000000000', - makerFee: new BigNumber('0'), - takerFee: new BigNumber('0'), - makerAssetAmount: new BigNumber('200000000000000000000'), - takerAssetAmount: new BigNumber('10000000000000000000'), - makerAssetData: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', - takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', - expirationTimeSeconds: new BigNumber('1601535600'), - feeRecipientAddress: '0x0000000000000000000000000000000000000000', - salt: new BigNumber('3101985707338942582579795423923841749956600670712030922928319824580764688653'), - signature: '0x1bd4d5686fea801fe33c68c4944356085e7e6cb553eb7073160abd815609f714e85fb47f44b7ffd0a2a1321ac40d72d55163869d0a50fdb5a402132150fe33a08403', - exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' - }, - // Order selling ZRX - { - senderAddress: '0x0000000000000000000000000000000000000000', - makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', - takerAddress: '0x0000000000000000000000000000000000000000', - makerFee: new BigNumber('0'), - takerFee: new BigNumber('0'), - makerAssetAmount: new BigNumber('300000000000000000000'), - takerAssetAmount: new BigNumber('31000000000000000000'), - makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', - takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', - expirationTimeSeconds: new BigNumber('2524636800'), - feeRecipientAddress: '0x0000000000000000000000000000000000000000', - salt: new BigNumber('64592004666704945574675477805199411288137454783320798602050822322450089238268'), - signature: '0x1c13cacddca8d7d8248e91f412377e68f8f1f9891a59a6c1b2eea9f7b33558c30c4fb86a448e08ab7def40a28fb3a3062dcb33bb3c45302447fce5c4288b7c7f5b03', - exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' - }, - // Order selling GNT - { - senderAddress: '0x0000000000000000000000000000000000000000', - makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', - takerAddress: '0x0000000000000000000000000000000000000000', - makerFee: new BigNumber('0'), - takerFee: new BigNumber('0'), - makerAssetAmount: new BigNumber('250000000000000000000'), - takerAssetAmount: new BigNumber('10000000000000000000'), - makerAssetData: '0xf47261b000000000000000000000000031fb614e223706f15d0d3c5f4b08bdf0d5c78623', - takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', - expirationTimeSeconds: new BigNumber('1601535600'), - feeRecipientAddress: '0x0000000000000000000000000000000000000000', - salt: new BigNumber('40204378562212615907903051460421336779451270522691667164301816101569427926606'), - signature: '0x1c788bf4b93769da1e8f195f52f0f59b4a298ac6da30cf6d05a87ed4be5ee974f61352ed1bc6a0844d0962b8c894c9ca08e452431255958a4e98dd93cbe1fbc73803', - exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' - }, - // Order selling MKR - { - senderAddress: '0x0000000000000000000000000000000000000000', - makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', - takerAddress: '0x0000000000000000000000000000000000000000', - makerFee: new BigNumber('0'), - takerFee: new BigNumber('0'), - makerAssetAmount: new BigNumber('200000000000000000000'), - takerAssetAmount: new BigNumber('5000000000000000000'), - makerAssetData: '0xf47261b00000000000000000000000007b6b10caa9e8e9552ba72638ea5b47c25afea1f3', - takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', - expirationTimeSeconds: new BigNumber('1601535600'), - feeRecipientAddress: '0x0000000000000000000000000000000000000000', - salt: new BigNumber('71338269924068280039932133924198049371838034090153601678083172009862985793828'), - signature: '0x1bb3151d57ee1e8fa697767ce83ee4ba77d1ceb8cc1e79c7d77126b3687517704c50c6b3d9cb42c7e7d4478d574b297dfbd1626c5c18a7bc9c2a792c4c07f0797c03', - exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + body { + margin: 0; + height: 100vh; + background-color: rgba(0, 0, 0, 0.2); } - ]; - const queryParams = new Uri(window.location.search); - const renderOptionsDefaults = { - orderSource: 'https://api.radarrelay.com/0x/v2/', - onClose: () => { console.log('0x Instant Closed') } - } - const orderSourceOverride = queryParams.getQueryParamValue('orderSource'); - const availableAssetDatasString = queryParams.getQueryParamValue('availableAssetDatas'); - const feeRecipientOverride = queryParams.getQueryParamValue('feeRecipient'); - const feePercentageOverride = +queryParams.getQueryParamValue('feePercentage'); - let affiliateInfoOverride; - if (feeRecipientOverride !== undefined && feePercentageOverride !== undefined) { - affiliateInfoOverride = { - feeRecipient: feeRecipientOverride, - feePercentage: feePercentageOverride - }; - } - const renderOptionsOverrides = { - orderSource: orderSourceOverride === 'provided' ? providedOrders : orderSourceOverride, - networkId: +queryParams.getQueryParamValue('networkId') || undefined, - defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined, - availableAssetDatas: availableAssetDatasString ? JSON.parse(availableAssetDatasString) : undefined, - defaultSelectedAssetData: queryParams.getQueryParamValue('defaultSelectedAssetData'), - affiliateInfo: affiliateInfoOverride, - } - const renderOptions = Object.assign({}, renderOptionsDefaults, removeUndefined(renderOptionsOverrides)); - zeroExInstant.render(renderOptions); - </script> -</body> + </style> + </head> + + <body> + <div class="flex-center flex-center--column"> + <div><button class="button is-large" onClick="renderWithUrlDefaults()">Open</button></div> + <div> + <button + class="button is-danger" + onClick="renderWithAssetData('0xf47261b00000000000000000000000001985365e9f78359a9b6ad760e32412f4a445e862')" + > + Buy REP + </button> + <button + class="button is-black" + onClick="renderWithAssetData('0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498')" + > + Buy ZRX + </button> + <button + class="button is-info" + onClick="renderWithAssetData('0xf47261b00000000000000000000000006810e776880c02933d47db1b9fc05908e5386b96')" + > + Buy GNO + </button> + </div> + </div> + <div id="zeroExInstantContainer"></div> + <script> + // Simulate history + window.history.pushState({ page: 1 }, '0x Instant'); + window.history.pushState({ page: 2 }, '0x Instant'); + window.history.pushState({ page: 3 }, '0x Instant'); + window.onpopstate = () => console.log("Integrator's onpopstate called"); -</html>
\ No newline at end of file + const removeUndefined = obj => { + for (let k in obj) if (obj[k] === undefined) delete obj[k]; + return obj; + }; + BigNumber.config({ + EXPONENTIAL_AT: 1000, + DECIMAL_PLACES: 78, + }); + const providedOrders = [ + // Order selling REP + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('200000000000000000000'), + takerAssetAmount: new BigNumber('10000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('3101985707338942582579795423923841749956600670712030922928319824580764688653'), + signature: + '0x1bd4d5686fea801fe33c68c4944356085e7e6cb553eb7073160abd815609f714e85fb47f44b7ffd0a2a1321ac40d72d55163869d0a50fdb5a402132150fe33a08403', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', + }, + // Order selling ZRX + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('300000000000000000000'), + takerAssetAmount: new BigNumber('31000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('2524636800'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber( + '64592004666704945574675477805199411288137454783320798602050822322450089238268', + ), + signature: + '0x1c13cacddca8d7d8248e91f412377e68f8f1f9891a59a6c1b2eea9f7b33558c30c4fb86a448e08ab7def40a28fb3a3062dcb33bb3c45302447fce5c4288b7c7f5b03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', + }, + // Order selling GNT + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('250000000000000000000'), + takerAssetAmount: new BigNumber('10000000000000000000'), + makerAssetData: '0xf47261b000000000000000000000000031fb614e223706f15d0d3c5f4b08bdf0d5c78623', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber( + '40204378562212615907903051460421336779451270522691667164301816101569427926606', + ), + signature: + '0x1c788bf4b93769da1e8f195f52f0f59b4a298ac6da30cf6d05a87ed4be5ee974f61352ed1bc6a0844d0962b8c894c9ca08e452431255958a4e98dd93cbe1fbc73803', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', + }, + // Order selling MKR + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('200000000000000000000'), + takerAssetAmount: new BigNumber('5000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000007b6b10caa9e8e9552ba72638ea5b47c25afea1f3', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber( + '71338269924068280039932133924198049371838034090153601678083172009862985793828', + ), + signature: + '0x1bb3151d57ee1e8fa697767ce83ee4ba77d1ceb8cc1e79c7d77126b3687517704c50c6b3d9cb42c7e7d4478d574b297dfbd1626c5c18a7bc9c2a792c4c07f0797c03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', + }, + ]; + const getRenderOptionsOverridesFromUrl = () => { + const queryParams = new Uri(window.location.search); + const orderSourceOverride = queryParams.getQueryParamValue('orderSource'); + const availableAssetDatasString = queryParams.getQueryParamValue('availableAssetDatas'); + const feeRecipientOverride = queryParams.getQueryParamValue('feeRecipient'); + const feePercentageOverride = +queryParams.getQueryParamValue('feePercentage'); + let affiliateInfoOverride; + if (feeRecipientOverride !== undefined && feePercentageOverride !== undefined) { + affiliateInfoOverride = { + feeRecipient: feeRecipientOverride, + feePercentage: feePercentageOverride, + }; + } + const renderOptionsOverrides = { + orderSource: orderSourceOverride === 'provided' ? providedOrders : orderSourceOverride, + networkId: +queryParams.getQueryParamValue('networkId') || undefined, + defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined, + availableAssetDatas: availableAssetDatasString ? JSON.parse(availableAssetDatasString) : undefined, + defaultSelectedAssetData: queryParams.getQueryParamValue('defaultSelectedAssetData'), + affiliateInfo: affiliateInfoOverride, + shouldDisablePushToHistory: !!queryParams.getQueryParamValue('shouldDisablePushToHistory'), + }; + return renderOptionsOverrides; + }; + const render = renderOptionsOverrides => { + const renderOptionsDefaults = { + orderSource: 'https://api.radarrelay.com/0x/v2/', + onClose: () => { + console.log('0x Instant Closed'); + }, + }; + const renderOptions = Object.assign({}, renderOptionsDefaults, removeUndefined(renderOptionsOverrides)); + zeroExInstant.render(renderOptions); + }; + const renderWithUrlDefaults = (renderOptions = {}) => { + const finalOptions = { + ...getRenderOptionsOverridesFromUrl(), + ...renderOptions, + }; + render(finalOptions); + }; + renderWithUrlDefaults(); + const renderWithAssetData = assetData => { + renderWithUrlDefaults({ + defaultSelectedAssetData: assetData, + }); + }; + </script> + </body> +</html> diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index b825255c4..ff900842a 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -64,6 +64,9 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput maxFontSizePx={this.props.startingFontSizePx} onAmountChange={this._handleChange} onFontSizeChange={this._handleFontSizeChange} + hasAutofocus={true} + /* We send in a key of asset data to force a rerender of this component when the user selects a new asset. We do this so the autofocus attribute will bring focus onto this input */ + key={asset.assetData} /> </Container> <Container diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 002695269..ace577824 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -107,7 +107,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private readonly _renderEthAmount = (): React.ReactNode => { return ( - <Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> + <Text fontSize="16px" textAlign="right" width="100%" fontColor={ColorOption.white} fontWeight={500}> {format.ethBaseUnitAmount( this.props.totalEthBaseUnitAmount, 4, @@ -119,7 +119,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private readonly _renderDollarAmount = (): React.ReactNode => { return ( - <Text fontSize="16px" fontColor={ColorOption.white}> + <Text fontSize="16px" textAlign="right" width="100%" fontColor={ColorOption.white}> {format.ethBaseUnitAmountInUsd( this.props.totalEthBaseUnitAmount, this.props.ethUsdPrice, diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx index 5dc719293..0861bbe05 100644 --- a/packages/instant/src/components/scaling_amount_input.tsx +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -18,6 +18,7 @@ export interface ScalingAmountInputProps { value?: BigNumber; onAmountChange: (value?: BigNumber) => void; onFontSizeChange: (fontSizePx: number) => void; + hasAutofocus: boolean; } interface ScalingAmountInputState { stringValue: string; @@ -29,6 +30,7 @@ export class ScalingAmountInput extends React.Component<ScalingAmountInputProps, onAmountChange: util.boundNoop, onFontSizeChange: util.boundNoop, isDisabled: false, + hasAutofocus: false, }; public constructor(props: ScalingAmountInputProps) { super(props); @@ -64,6 +66,7 @@ export class ScalingAmountInput extends React.Component<ScalingAmountInputProps, placeholder="0.00" emptyInputWidthCh={3.5} isDisabled={this.props.isDisabled} + hasAutofocus={this.props.hasAutofocus} /> ); } diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index e1599a316..129162a74 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -28,6 +28,7 @@ export interface ScalingInputProps { maxLength?: number; scalingSettings: ScalingSettings; isDisabled: boolean; + hasAutofocus: boolean; } export interface ScalingInputState { @@ -51,6 +52,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu maxLength: 7, scalingSettings: defaultScalingSettings, isDisabled: false, + hasAutofocus: false, }; public state: ScalingInputState = { inputWidthPxAtPhaseChange: undefined, @@ -123,7 +125,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu } } public render(): React.ReactNode { - const { isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; + const { hasAutofocus, isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; const phase = ScalingInput.getPhaseFromProps(this.props); return ( <Input @@ -136,6 +138,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu width={this._calculateWidth(phase)} maxLength={maxLength} disabled={isDisabled} + autoFocus={hasAutofocus} /> ); } diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index fd14cc4d1..8e573d7b9 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -11,6 +11,7 @@ export interface TextProps { fontSize?: string; opacity?: number; letterSpacing?: string; + textAlign?: string; textTransform?: string; lineHeight?: string; className?: string; @@ -22,6 +23,7 @@ export interface TextProps { noWrap?: boolean; display?: string; href?: string; + width?: string; } export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...rest }) => { @@ -51,6 +53,8 @@ export const StyledText = ${props => (props.display ? `display: ${props.display}` : '')}; ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')}; ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')}; + ${props => (props.textAlign ? `text-align: ${props.textAlign}` : '')}; + ${props => (props.width ? `width: ${props.width}` : '')}; &:hover { ${props => props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''}; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 9435d8c7c..fe34c4466 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -20,8 +20,6 @@ import { Heartbeater } from '../util/heartbeater'; import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory'; import { providerStateFactory } from '../util/provider_state_factory'; -fonts.include(); - export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps & Partial<ZeroExInstantProviderOptionalProps>; @@ -88,6 +86,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider } constructor(props: ZeroExInstantProviderProps) { super(props); + fonts.include(); const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props); this._store = store.create(initialAppState); } @@ -126,14 +125,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider // Analytics disableAnalytics(this.props.shouldDisableAnalyticsTracking || false); - analytics.addEventProperties({ - embeddedHost: window.location.host, - embeddedUrl: window.location.href, - networkId: state.network, - providerName: state.providerState.name, - gitSha: process.env.GIT_SHA, - npmVersion: process.env.NPM_PACKAGE_VERSION, - }); + analytics.addEventProperties( + analytics.generateEventProperties( + state.network, + this.props.orderSource, + state.providerState, + window, + this.props.affiliateInfo, + ), + ); analytics.trackInstantOpened(); } public componentWillUnmount(): void { diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts index eacbadfca..cdeb49a25 100644 --- a/packages/instant/src/containers/connected_account_payment_method.ts +++ b/packages/instant/src/containers/connected_account_payment_method.ts @@ -12,6 +12,7 @@ import { Action, actions } from '../redux/actions'; import { asyncData } from '../redux/async_data'; import { State } from '../redux/reducer'; import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent } from '../types'; +import { analytics } from '../util/analytics'; import { envUtil } from '../util/env'; export interface ConnectedAccountPaymentMethodProps {} @@ -40,8 +41,11 @@ const mapDispatchToProps = ( ownProps: ConnectedAccountPaymentMethodProps, ): ConnectedDispatch => ({ openInstallWalletPanel: () => dispatch(actions.openStandardSlidingPanel(StandardSlidingPanelContent.InstallWallet)), - unlockWalletAndDispatchToStore: async (providerState: ProviderState) => - asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true), + unlockWalletAndDispatchToStore: (providerState: ProviderState) => { + analytics.trackAccountUnlockRequested(); + // tslint:disable-next-line:no-floating-promises + asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true); + }, }); const mergeProps = ( diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 5010347b3..3a8694d6a 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -5,40 +5,50 @@ import * as ReactDOM from 'react-dom'; import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID } from './constants'; import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index'; import { assert } from './util/assert'; +import { util } from './util/util'; -export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => { - assert.isValidOrderSource('orderSource', props.orderSource); - if (!_.isUndefined(props.defaultSelectedAssetData)) { - assert.isHexString('defaultSelectedAssetData', props.defaultSelectedAssetData); +const isInstantRendered = (): boolean => !!document.getElementById(INJECTED_DIV_ID); + +const validateInstantRenderConfig = (config: ZeroExInstantConfig, selector: string) => { + assert.isValidOrderSource('orderSource', config.orderSource); + if (!_.isUndefined(config.defaultSelectedAssetData)) { + assert.isHexString('defaultSelectedAssetData', config.defaultSelectedAssetData); + } + if (!_.isUndefined(config.additionalAssetMetaDataMap)) { + assert.isValidAssetMetaDataMap('additionalAssetMetaDataMap', config.additionalAssetMetaDataMap); } - if (!_.isUndefined(props.additionalAssetMetaDataMap)) { - assert.isValidAssetMetaDataMap('props.additionalAssetMetaDataMap', props.additionalAssetMetaDataMap); + if (!_.isUndefined(config.defaultAssetBuyAmount)) { + assert.isNumber('defaultAssetBuyAmount', config.defaultAssetBuyAmount); } - if (!_.isUndefined(props.defaultAssetBuyAmount)) { - assert.isNumber('props.defaultAssetBuyAmount', props.defaultAssetBuyAmount); + if (!_.isUndefined(config.networkId)) { + assert.isNumber('networkId', config.networkId); } - if (!_.isUndefined(props.networkId)) { - assert.isNumber('props.networkId', props.networkId); + if (!_.isUndefined(config.availableAssetDatas)) { + assert.areValidAssetDatas('availableAssetDatas', config.availableAssetDatas); } - if (!_.isUndefined(props.availableAssetDatas)) { - assert.areValidAssetDatas('availableAssetDatas', props.availableAssetDatas); + if (!_.isUndefined(config.onClose)) { + assert.isFunction('onClose', config.onClose); } - if (!_.isUndefined(props.onClose)) { - assert.isFunction('props.onClose', props.onClose); + if (!_.isUndefined(config.zIndex)) { + assert.isNumber('zIndex', config.zIndex); } - if (!_.isUndefined(props.zIndex)) { - assert.isNumber('props.zIndex', props.zIndex); + if (!_.isUndefined(config.affiliateInfo)) { + assert.isValidAffiliateInfo('affiliateInfo', config.affiliateInfo); } - if (!_.isUndefined(props.affiliateInfo)) { - assert.isValidAffiliateInfo('props.affiliateInfo', props.affiliateInfo); + if (!_.isUndefined(config.provider)) { + assert.isWeb3Provider('provider', config.provider); } - if (!_.isUndefined(props.provider)) { - assert.isWeb3Provider('props.provider', props.provider); + if (!_.isUndefined(config.shouldDisablePushToHistory)) { + assert.isBoolean('shouldDisablePushToHistory', config.shouldDisablePushToHistory); } - if (!_.isUndefined(props.shouldDisableAnalyticsTracking)) { - assert.isBoolean('props.shouldDisableAnalyticsTracking', props.shouldDisableAnalyticsTracking); + if (!_.isUndefined(config.shouldDisableAnalyticsTracking)) { + assert.isBoolean('shouldDisableAnalyticsTracking', config.shouldDisableAnalyticsTracking); } assert.isString('selector', selector); +}; + +// Render instant and return a callback that allows you to remove it from the DOM. +const renderInstant = (config: ZeroExInstantConfig, selector: string) => { const appendToIfExists = document.querySelector(selector); assert.assert(!_.isNull(appendToIfExists), `Could not find div with selector: ${selector}`); const appendTo = appendToIfExists as Element; @@ -46,14 +56,57 @@ export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFA injectedDiv.setAttribute('id', INJECTED_DIV_ID); injectedDiv.setAttribute('class', INJECTED_DIV_CLASS); appendTo.appendChild(injectedDiv); + const closeInstant = () => { + if (!_.isUndefined(config.onClose)) { + config.onClose(); + } + appendTo.removeChild(injectedDiv); + }; const instantOverlayProps = { - ...props, - onClose: () => { - appendTo.removeChild(injectedDiv); - if (!_.isUndefined(props.onClose)) { - props.onClose(); - } - }, + ...config, + // If we are using the history API, just go back to close + onClose: () => (config.shouldDisablePushToHistory ? closeInstant() : window.history.back()), }; ReactDOM.render(React.createElement(ZeroExInstantOverlay, instantOverlayProps), injectedDiv); + return closeInstant; +}; + +export interface ZeroExInstantConfig extends ZeroExInstantOverlayProps { + shouldDisablePushToHistory?: boolean; +} + +export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => { + validateInstantRenderConfig(config, selector); + if (config.shouldDisablePushToHistory) { + if (!isInstantRendered()) { + renderInstant(config, selector); + } + return; + } + // Before we render, push to history saying that instant is showing for this part of the history. + window.history.pushState({ zeroExInstantShowing: true }, '0x Instant'); + let removeInstant = renderInstant(config, selector); + // If the integrator defined a popstate handler, save it to __zeroExInstantIntegratorsPopStateHandler + // unless we have already done so on a previous render. + const anyWindow = window as any; + if (window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler) { + anyWindow.__zeroExInstantIntegratorsPopStateHandler = window.onpopstate.bind(window); + } + const integratorsOnPopStateHandler = anyWindow.__zeroExInstantIntegratorsPopStateHandler || util.boundNoop; + const onPopStateHandler = (e: PopStateEvent) => { + integratorsOnPopStateHandler(e); + const newState = e.state; + if (newState && newState.zeroExInstantShowing) { + // We have returned to a history state that expects instant to be rendered. + if (!isInstantRendered()) { + removeInstant = renderInstant(config, selector); + } + } else { + // History has changed to a different state. + if (isInstantRendered()) { + removeInstant(); + } + } + }; + window.onpopstate = onPopStateHandler; }; diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index f971dbd33..299c2560e 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -10,20 +10,6 @@ import { Action, ActionTypes } from './actions'; import { State } from './reducer'; -const shouldTriggerWalletReady = (prevAccount: Account, curAccount: Account): boolean => { - const didJustTurnReady = curAccount.state === AccountState.Ready && prevAccount.state !== AccountState.Ready; - if (didJustTurnReady) { - return true; - } - - if (curAccount.state === AccountState.Ready && prevAccount.state === AccountState.Ready) { - // Account was ready, and is now ready again, but address has changed - return curAccount.address !== prevAccount.address; - } - - return false; -}; - export const analyticsMiddleware: Middleware = store => next => middlewareAction => { const prevState = store.getState() as State; const prevAccount = prevState.providerState.account; @@ -35,10 +21,24 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction switch (nextAction.type) { case ActionTypes.SET_ACCOUNT_STATE_READY: - if (curAccount.state === AccountState.Ready && shouldTriggerWalletReady(prevAccount, curAccount)) { + if (curAccount.state === AccountState.Ready) { + const didJustTurnReady = prevAccount.state !== AccountState.Ready; + const didJustUpdateAddress = + prevAccount.state === AccountState.Ready && prevAccount.address !== curAccount.address; const ethAddress = curAccount.address; - analytics.addUserProperties({ ethAddress }); - analytics.trackWalletReady(); + if (didJustTurnReady) { + analytics.trackAccountReady(ethAddress); + analytics.addUserProperties({ lastKnownEthAddress: ethAddress }); + } else if (didJustUpdateAddress) { + analytics.trackAccountAddressChanged(ethAddress); + analytics.addUserProperties({ lastKnownEthAddress: ethAddress }); + } + } + break; + case ActionTypes.SET_ACCOUNT_STATE_LOCKED: + if (prevAccount.state !== AccountState.Locked && curAccount.state === AccountState.Locked) { + // if we are moving from account not locked to account locked, track `Account - Locked` + analytics.trackAccountLocked(); } break; case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE: diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 5d30388b8..6feb760e7 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -5,6 +5,7 @@ import { Dispatch } from 'redux'; import { BIG_NUMBER_ZERO } from '../constants'; import { AccountState, ERC20Asset, OrderProcessState, ProviderState } from '../types'; +import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; import { coinbaseApi } from '../util/coinbase_api'; @@ -58,6 +59,7 @@ export const asyncData = { ? await (provider as any).enable() : await web3Wrapper.getAvailableAddressesAsync(); } catch (e) { + analytics.trackAccountUnlockDenied(); dispatch(actions.setAccountStateLocked()); return; } diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 2ffaac1dd..cec99dd1b 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,6 +1,6 @@ -import { ObjectMap } from '@0x/types'; +import { AffiliateInfo, Network, OrderSource, ProviderState } from '../types'; -import { heapUtil } from './heap'; +import { EventProperties, heapUtil } from './heap'; let isDisabled = false; export const disableAnalytics = (shouldDisableAnalytics: boolean) => { @@ -15,11 +15,15 @@ export const evaluateIfEnabled = (fnCall: () => void) => { enum EventNames { INSTANT_OPENED = 'Instant - Opened', - WALLET_READY = 'Wallet - Ready', + ACCOUNT_LOCKED = 'Account - Locked', + ACCOUNT_READY = 'Account - Ready', + ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested', + ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied', + ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed', } -const track = (eventName: EventNames, eventData: ObjectMap<string | number> = {}): void => { +const track = (eventName: EventNames, eventProperties: EventProperties = {}): void => { evaluateIfEnabled(() => { - heapUtil.evaluateHeapCall(heap => heap.track(eventName, eventData)); + heapUtil.evaluateHeapCall(heap => heap.track(eventName, eventProperties)); }); }; function trackingEventFnWithoutPayload(eventName: EventNames): () => void { @@ -28,16 +32,14 @@ function trackingEventFnWithoutPayload(eventName: EventNames): () => void { }; } // tslint:disable-next-line:no-unused-variable -function trackingEventFnWithPayload<T extends ObjectMap<string | number>>( - eventName: EventNames, -): (eventDataProperties: T) => void { - return (eventDataProperties: T) => { - track(eventName, eventDataProperties); +function trackingEventFnWithPayload(eventName: EventNames): (eventProperties: EventProperties) => void { + return (eventProperties: EventProperties) => { + track(eventName, eventProperties); }; } export interface AnalyticsUserOptions { - ethAddress?: string; + lastKnownEthAddress?: string; ethBalanceInUnitAmount?: string; } export interface AnalyticsEventOptions { @@ -47,7 +49,11 @@ export interface AnalyticsEventOptions { providerName?: string; gitSha?: string; npmVersion?: string; + orderSource?: string; + affiliateAddress?: string; + affiliateFeePercent?: number; } + export const analytics = { addUserProperties: (properties: AnalyticsUserOptions): void => { evaluateIfEnabled(() => { @@ -59,6 +65,33 @@ export const analytics = { heapUtil.evaluateHeapCall(heap => heap.addEventProperties(properties)); }); }, - trackWalletReady: trackingEventFnWithoutPayload(EventNames.WALLET_READY), + generateEventProperties: ( + network: Network, + orderSource: OrderSource, + providerState: ProviderState, + window: Window, + affiliateInfo?: AffiliateInfo, + ): AnalyticsEventOptions => { + const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none'; + const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0; + const orderSourceName = typeof orderSource === 'string' ? orderSource : 'provided'; + return { + embeddedHost: window.location.host, + embeddedUrl: window.location.href, + networkId: network, + providerName: providerState.name, + gitSha: process.env.GIT_SHA, + npmVersion: process.env.NPM_PACKAGE_VERSION, + orderSource: orderSourceName, + affiliateAddress, + affiliateFeePercent, + }; + }, trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED), + trackAccountLocked: trackingEventFnWithoutPayload(EventNames.ACCOUNT_LOCKED), + trackAccountReady: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_READY)({ address }), + trackAccountUnlockRequested: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_REQUESTED), + trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED), + trackAccountAddressChanged: (address: string) => + trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }), }; diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts index 78ec3b3cc..7c53c9918 100644 --- a/packages/instant/src/util/heap.ts +++ b/packages/instant/src/util/heap.ts @@ -6,11 +6,13 @@ import { HEAP_ANALYTICS_ID } from '../constants'; import { AnalyticsEventOptions, AnalyticsUserOptions } from './analytics'; +export type EventProperties = ObjectMap<string | number>; + export interface HeapAnalytics { loaded: boolean; appid: string; identify(id: string, idType: string): void; - track(eventName: string, eventProperties?: ObjectMap<string | number>): void; + track(eventName: string, eventProperties?: EventProperties): void; resetIdentity(): void; addUserProperties(properties: AnalyticsUserOptions): void; addEventProperties(properties: AnalyticsEventOptions): void; diff --git a/packages/instant/tsconfig.json b/packages/instant/tsconfig.json index 28a6190b8..14b0ad8f7 100644 --- a/packages/instant/tsconfig.json +++ b/packages/instant/tsconfig.json @@ -2,16 +2,11 @@ "extends": "../../tsconfig", "compilerOptions": { "outDir": "lib", - "rootDir": ".", + "rootDir": "src", "jsx": "react", - "allowSyntheticDefaultImports": true, "noImplicitAny": true, - "module": "ESNext", - "moduleResolution": "node", - "lib": ["es2015", "dom"], - "target": "es5", - "sourceMap": true + "allowSyntheticDefaultImports": true }, - "include": ["./src/**/*", "./test/**/*"], + "include": ["./src/**/*"], "exclude": ["./src/index.umd.ts"] } |