diff options
Diffstat (limited to 'packages/website/ts')
113 files changed, 12753 insertions, 12753 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 711c3329d..5530701c0 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -1,25 +1,25 @@ import { - BlockParam, - BlockRange, - DecodedLogEvent, - ExchangeContractEventArgs, - ExchangeEvents, - IndexedFilterValues, - LogCancelContractEventArgs, - LogFillContractEventArgs, - LogWithDecodedArgs, - Order, - SignedOrder, - Token as ZeroExToken, - TransactionReceiptWithDecodedLogs, - ZeroEx, + BlockParam, + BlockRange, + DecodedLogEvent, + ExchangeContractEventArgs, + ExchangeEvents, + IndexedFilterValues, + LogCancelContractEventArgs, + LogFillContractEventArgs, + LogWithDecodedArgs, + Order, + SignedOrder, + Token as ZeroExToken, + TransactionReceiptWithDecodedLogs, + ZeroEx, } from '0x.js'; import { - InjectedWeb3Subprovider, - ledgerEthereumBrowserClientFactoryAsync, - LedgerSubprovider, - LedgerWalletSubprovider, - RedundantRPCSubprovider, + InjectedWeb3Subprovider, + ledgerEthereumBrowserClientFactoryAsync, + LedgerSubprovider, + LedgerWalletSubprovider, + RedundantRPCSubprovider, } from '@0xproject/subproviders'; import { BigNumber, intervalUtils, promisify } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -31,16 +31,16 @@ import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - BlockchainCallErrs, - BlockchainErrs, - ContractInstance, - EtherscanLinkSuffixes, - ProviderType, - Side, - SignatureData, - Token, - TokenByAddress, - TokenStateByAddress, + BlockchainCallErrs, + BlockchainErrs, + ContractInstance, + EtherscanLinkSuffixes, + ProviderType, + Side, + SignatureData, + Token, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -56,727 +56,727 @@ import * as MintableArtifacts from '../contracts/Mintable.json'; const BLOCK_NUMBER_BACK_TRACK = 50; export class Blockchain { - public networkId: number; - public nodeVersion: string; - private _zeroEx: ZeroEx; - private _dispatcher: Dispatcher; - private _web3Wrapper?: Web3Wrapper; - private _exchangeAddress: string; - private _userAddress: string; - private _cachedProvider: Web3.Provider; - private _ledgerSubprovider: LedgerWalletSubprovider; - private _zrxPollIntervalId: NodeJS.Timer; - private static async _onPageLoadAsync(): Promise<void> { - if (document.readyState === 'complete') { - return; // Already loaded - } - return new Promise<void>((resolve, reject) => { - window.onload = () => resolve(); - }); - } - private static _getNameGivenProvider(provider: Web3.Provider): string { - if (!_.isUndefined((provider as any).isMetaMask)) { - return constants.PROVIDER_NAME_METAMASK; - } - - // HACK: We use the fact that Parity Signer's provider is an instance of their - // internal `Web3FrameProvider` class. - const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); - if (isParitySigner) { - return constants.PROVIDER_NAME_PARITY_SIGNER; - } - - return constants.PROVIDER_NAME_GENERIC; - } - private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); - const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; - const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); - - let provider; - if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { - // We catch all requests involving a users account and send it to the injectedWeb3 - // instance. All other requests go to the public hosted node. - provider = new ProviderEngine(); - provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3)); - provider.addProvider(new FilterSubprovider()); - provider.addProvider(new RedundantRPCSubprovider(publicNodeUrlsIfExistsForNetworkId)); - provider.start(); - } else if (doesInjectedWeb3Exist) { - // Since no public node for this network, all requests go to injectedWeb3 instance - provider = injectedWeb3.currentProvider; - } else { - // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node - // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 - // injected into their browser. - provider = new ProviderEngine(); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - } - - return provider; - } - constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { - this._dispatcher = dispatcher; - this._userAddress = ''; - // tslint:disable-next-line:no-floating-promises - this._onPageLoadInitFireAndForgetAsync(); - } - public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { - const isConnected = !_.isUndefined(newNetworkId); - if (!isConnected) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode); - this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (this.networkId !== newNetworkId) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); - await this._fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); - } - } - public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { - if (this._userAddress !== newUserAddress) { - this._userAddress = newUserAddress; - await this._fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); - } - } - public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { - if (this.nodeVersion !== nodeVersion) { - this.nodeVersion = nodeVersion; - } - } - public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // HACK: temporarily whitelist the new WETH token address `as if` they were - // already in the tokenRegistry. - // TODO: Remove this hack once we've updated the TokenRegistries - // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz - if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && tokenAddress === configs.NEW_WRAPPED_ETHERS[this.networkId]) { - return true; - } - const tokenIfExists = await this._zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); - return !_.isUndefined(tokenIfExists); - } - public getLedgerDerivationPathIfExists(): string { - if (_.isUndefined(this._ledgerSubprovider)) { - return undefined; - } - const path = this._ledgerSubprovider.getPath(); - return path; - } - public updateLedgerDerivationPathIfExists(path: string) { - if (_.isUndefined(this._ledgerSubprovider)) { - return; // noop - } - this._ledgerSubprovider.setPath(path); - } - public updateLedgerDerivationIndex(pathIndex: number) { - if (_.isUndefined(this._ledgerSubprovider)) { - return; // noop - } - this._ledgerSubprovider.setPathIndex(pathIndex); - } - public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // Should actually be Web3.Provider|ProviderEngine union type but it causes issues - // later on in the logic. - let provider; - switch (providerType) { - case ProviderType.Ledger: { - const isU2FSupported = await utils.isU2FSupportedAsync(); - if (!isU2FSupported) { - throw new Error('Cannot update providerType to LEDGER without U2F support'); - } - - // Cache injected provider so that we can switch the user back to it easily - this._cachedProvider = this._web3Wrapper.getProviderObj(); - - this._dispatcher.updateUserAddress(''); // Clear old userAddress - - provider = new ProviderEngine(); - const ledgerWalletConfigs = { - networkId: this.networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(this._ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED - ? constants.NETWORK_ID_MAINNET - : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - this._web3Wrapper.destroy(); - const shouldPollUserAddress = false; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - break; - } - - case ProviderType.Injected: { - if (_.isUndefined(this._cachedProvider)) { - return; // Going from injected to injected, so we noop - } - provider = this._cachedProvider; - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, this.networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - delete this._ledgerSubprovider; - delete this._cachedProvider; - break; - } - - default: - throw utils.spawnSwitchErr('providerType', providerType); - } - - await this._fetchTokenInformationAsync(); - } - public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { - utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - - const txHash = await this._zeroEx.token.setProxyAllowanceAsync( - token.address, - this._userAddress, - amountInBaseUnits, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const allowance = amountInBaseUnits; - this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); - } - public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { - const txHash = await this._zeroEx.token.transferAsync( - token.address, - this._userAddress, - toAddress, - amountInBaseUnits, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); - this._dispatcher.showFlashMessage( - React.createElement(TokenSendCompleted, { - etherScanLinkIfExists, - token, - toAddress, - amountInBaseUnits, - }), - ); - } - public portalOrderToSignedOrder( - maker: string, - taker: string, - makerTokenAddress: string, - takerTokenAddress: string, - makerTokenAmount: BigNumber, - takerTokenAmount: BigNumber, - makerFee: BigNumber, - takerFee: BigNumber, - expirationUnixTimestampSec: BigNumber, - feeRecipient: string, - signatureData: SignatureData, - salt: BigNumber, - ): SignedOrder { - const ecSignature = signatureData; - const exchangeContractAddress = this.getExchangeContractAddressIfExists(); - const takerOrNullAddress = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; - const signedOrder = { - ecSignature, - exchangeContractAddress, - expirationUnixTimestampSec, - feeRecipient, - maker, - makerFee, - makerTokenAddress, - makerTokenAmount, - salt, - taker: takerOrNullAddress, - takerFee, - takerTokenAddress, - takerTokenAmount, - }; - return signedOrder; - } - public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const shouldThrowOnInsufficientBalanceOrAllowance = true; - - const txHash = await this._zeroEx.exchange.fillOrderAsync( - signedOrder, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - this._userAddress, - ); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; - this._zeroEx.exchange.throwLogErrorsAsErrors(logs); - const logFill = _.find(logs, { event: 'LogFill' }); - const args = (logFill.args as any) as LogFillContractEventArgs; - const filledTakerTokenAmount = args.filledTakerTokenAmount; - return filledTakerTokenAmount; - } - public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> { - const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; - this._zeroEx.exchange.throwLogErrorsAsErrors(logs); - const logCancel = _.find(logs, { event: ExchangeEvents.LogCancel }); - const args = (logCancel.args as any) as LogCancelContractEventArgs; - const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; - return cancelledTakerTokenAmount; - } - public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> { - utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const unavailableTakerAmount = await this._zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); - return unavailableTakerAmount; - } - public getExchangeContractAddressIfExists() { - return this._exchangeAddress; - } - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - await this._zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( - signedOrder, - fillTakerTokenAmount, - takerAddress, - ); - } - public async validateCancelOrderThrowIfInvalidAsync( - order: Order, - cancelTakerTokenAmount: BigNumber, - ): Promise<void> { - await this._zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); - } - public isValidAddress(address: string): boolean { - const lowercaseAddress = address.toLowerCase(); - return this._web3Wrapper.isAddress(lowercaseAddress); - } - public async pollTokenBalanceAsync(token: Token) { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - - this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - if (!balance.eq(currBalance)) { - this._dispatcher.replaceTokenBalanceByAddress(token.address, balance); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - } - }, - 5000, - (err: Error) => { - utils.consoleLog(`Polling tokenBalance failed: ${err}`); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - }, - ); - } - public async signOrderHashAsync(orderHash: string): Promise<SignatureData> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const makerAddress = this._userAddress; - // If makerAddress is undefined, this means they have a web3 instance injected into their browser - // but no account addresses associated with it. - if (_.isUndefined(makerAddress)) { - throw new Error('Tried to send a sign request but user has no associated addresses'); - } - const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress); - const signatureData = _.extend({}, ecSignature, { - hash: orderHash, - }); - this._dispatcher.updateSignatureData(signatureData); - return signatureData; - } - public async mintTestTokensAsync(token: Token) { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); - await mintableContract.mint(constants.MINT_AMOUNT, { - from: this._userAddress, - }); - const balanceDelta = constants.MINT_AMOUNT; - this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); - } - public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { - const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); - return balance; - } - public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async doesContractExistAtAddressAsync(address: string) { - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); - return doesContractExist; - } - public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { - const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, tokenAddress); - return tokenBalanceAndAllowance; - } - public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): Promise<BigNumber[]> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - - if (_.isEmpty(ownerAddress)) { - const zero = new BigNumber(0); - return [zero, zero]; - } - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (this._doesUserAddressExist()) { - balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); - allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); - } - return [balance, allowance]; - } - public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { - const tokenStateByAddress: TokenStateByAddress = {}; - for (const token of tokens) { - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (this._doesUserAddressExist()) { - [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - } - const tokenState = { - balance, - allowance, - }; - tokenStateByAddress[token.address] = tokenState; - } - this._dispatcher.updateTokenStateByAddress(tokenStateByAddress); - } - public async getUserAccountsAsync() { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); - return userAccountsIfExists; - } - // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which - // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to - // manually update it. This should only be called by the LedgerConfigDialog. - public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { - this._web3Wrapper.updatePrevUserAddress(newUserAddress); - } - public destroy() { - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - this._web3Wrapper.destroy(); - this._stopWatchingExchangeLogFillEvents(); - } - private async _showEtherScanLinkAndAwaitTransactionMinedAsync( - txHash: string, - ): Promise<TransactionReceiptWithDecodedLogs> { - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); - this._dispatcher.showFlashMessage( - React.createElement(TransactionSubmitted, { - etherScanLinkIfExists, - }), - ); - const receipt = await this._zeroEx.awaitTransactionMinedAsync(txHash); - return receipt; - } - private _doesUserAddressExist(): boolean { - return this._userAddress !== ''; - } - private async _rehydrateStoreWithContractEvents() { - // Ensure we are only ever listening to one set of events - this._stopWatchingExchangeLogFillEvents(); - - if (!this._doesUserAddressExist()) { - return; // short-circuit - } - - if (!_.isUndefined(this._zeroEx)) { - // Since we do not have an index on the `taker` address and want to show - // transactions where an account is either the `maker` or `taker`, we loop - // through all fill events, and filter/cache them client-side. - const filterIndexObj = {}; - await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj); - } - } - private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - // Fetch historical logs - await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); - - // Start a subscription for new logs - this._zeroEx.exchange.subscribe( - ExchangeEvents.LogFill, - indexFilterValues, - async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - if (err) { - // Note: it's not entirely clear from the documentation which - // errors will be thrown by `watch`. For now, let's log the error - // to rollbar and stop watching when one occurs - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(err); // fire and forget - return; - } else { - const decodedLog = decodedLogEvent.log; - if (!this._doesLogEventInvolveUser(decodedLog)) { - return; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - if (decodedLogEvent.isRemoved) { - tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); - } else { - tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); - } - } - }, - ); - } - private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { - const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddress, this.networkId); - const blockRange: BlockRange = { - fromBlock, - toBlock: 'latest' as BlockParam, - }; - const decodedLogs = await this._zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( - ExchangeEvents.LogFill, - blockRange, - indexFilterValues, - ); - for (const decodedLog of decodedLogs) { - if (!this._doesLogEventInvolveUser(decodedLog)) { - continue; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); - } - } - private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args; - const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); - const fill = { - filledTakerTokenAmount: args.filledTakerTokenAmount, - filledMakerTokenAmount: args.filledMakerTokenAmount, - logIndex: decodedLog.logIndex, - maker: args.maker, - orderHash: args.orderHash, - taker: args.taker, - makerToken: args.makerToken, - takerToken: args.takerToken, - paidMakerFee: args.paidMakerFee, - paidTakerFee: args.paidTakerFee, - transactionHash: decodedLog.transactionHash, - blockTimestamp, - }; - return fill; - } - private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args; - const isUserMakerOrTaker = args.maker === this._userAddress || args.taker === this._userAddress; - return isUserMakerOrTaker; - } - private _updateLatestFillsBlockIfNeeded(blockNumber: number) { - const isBlockPending = _.isNull(blockNumber); - if (!isBlockPending) { - // Hack: I've observed the behavior where a client won't register certain fill events - // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply - // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user - // would still attempt to re-fetch events from the previous 50 blocks, but won't need to - // re-fetch all events in all blocks. - // TODO: Debug if this is a race condition, and apply a more precise fix - const blockNumberToSet = - blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK; - tradeHistoryStorage.setFillsLatestBlock(this._userAddress, this.networkId, blockNumberToSet); - } - } - private _stopWatchingExchangeLogFillEvents(): void { - this._zeroEx.exchange.unsubscribeAll(); - } - private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const tokenRegistryTokens = await this._zeroEx.tokenRegistry.getTokensAsync(); - - const tokenByAddress: TokenByAddress = {}; - _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { - // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens - // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry - const iconUrl = configs.ICON_URL_BY_SYMBOL[t.symbol]; - // HACK: Temporarily we hijack the WETH addresses fetched from the tokenRegistry - // so that we can take our time with actually updating it. This ensures that when - // we deploy the new WETH page, everyone will re-fill their trackedTokens with the - // new canonical WETH. - // TODO: Remove this hack once we've updated the TokenRegistries - // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz - let address = t.address; - if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && t.symbol === 'WETH') { - const newEtherTokenAddressIfExists = configs.NEW_WRAPPED_ETHERS[this.networkId]; - if (!_.isUndefined(newEtherTokenAddressIfExists)) { - address = newEtherTokenAddressIfExists; - } - } - const token: Token = { - iconUrl, - address, - name: t.name, - symbol: t.symbol, - decimals: t.decimals, - isTracked: false, - isRegistered: true, - }; - tokenByAddress[token.address] = token; - }); - return tokenByAddress; - } - private async _onPageLoadInitFireAndForgetAsync() { - await Blockchain._onPageLoadAsync(); // wait for page to load - - // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in - // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot - // retrieve it from within the web3Wrapper constructor. This is and should remain the only - // call to a web3 instance outside of web3Wrapper in the entire dapp. - // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing - // Ethereum node, this call will throw. We need to handle this case gracefully - const injectedWeb3 = (window as any).web3; - let networkIdIfExists: number; - if (!_.isUndefined(injectedWeb3)) { - try { - networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)()); - } catch (err) { - // Ignore error and proceed with networkId undefined - } - } - - const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); - const networkId = !_.isUndefined(networkIdIfExists) - ? networkIdIfExists - : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; - const zeroExConfigs = { - networkId, - }; - this._zeroEx = new ZeroEx(provider, zeroExConfigs); - this._updateProviderName(injectedWeb3); - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - } - // This method should always be run after instantiating or updating the provider - // of the ZeroEx instance. - private async _postInstantiationOrUpdatingProviderZeroExAsync() { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - this._exchangeAddress = this._zeroEx.exchange.getContractAddress(); - } - private _updateProviderName(injectedWeb3: Web3) { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); - const providerName = doesInjectedWeb3Exist - ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) - : constants.PROVIDER_NAME_PUBLIC; - this._dispatcher.updateInjectedProviderName(providerName); - } - private async _fetchTokenInformationAsync() { - utils.assert( - !_.isUndefined(this.networkId), - 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', - ); - - this._dispatcher.updateBlockchainIsLoaded(false); - this._dispatcher.clearTokenByAddress(); - - const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); - - // HACK: We need to fetch the userAddress here because otherwise we cannot save the - // tracked tokens in localStorage under the users address nor fetch the token - // balances and allowances and we need to do this in order not to trigger the blockchain - // loading dialog to show up twice. First to load the contracts, and second to load the - // balances and allowances. - this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); - if (!_.isEmpty(this._userAddress)) { - this._dispatcher.updateUserAddress(this._userAddress); - } - - let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); - const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); - if (_.isUndefined(trackedTokensIfExists)) { - trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { - const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); - token.isTracked = true; - return token; - }); - _.each(trackedTokensIfExists, token => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); - }); - } else { - // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array - _.each(trackedTokensIfExists, trackedToken => { - if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { - tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; - } - }); - } - const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); - this._dispatcher.updateTokenByAddress(allTokens); - - // Get balance/allowance for tracked tokens - await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); - - const mostPopularTradingPairTokens: Token[] = [ - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), - ]; - this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); - this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); - this._dispatcher.updateBlockchainIsLoaded(true); - } - private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { - const c = await contract(artifact); - const providerObj = this._web3Wrapper.getProviderObj(); - c.setProvider(providerObj); - - const artifactNetworkConfigs = artifact.networks[this.networkId]; - let contractAddress; - if (!_.isUndefined(address)) { - contractAddress = address; - } else if (!_.isUndefined(artifactNetworkConfigs)) { - contractAddress = artifactNetworkConfigs.address; - } - - if (!_.isUndefined(contractAddress)) { - const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); - throw new Error(BlockchainCallErrs.ContractDoesNotExist); - } - } - - try { - const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address); - return contractInstance; - } catch (err) { - const errMsg = `${err}`; - utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); - if (_.includes(errMsg, 'not been deployed to detected network')) { - throw new Error(BlockchainCallErrs.ContractDoesNotExist); - } else { - await errorReporter.reportAsync(err); - throw new Error(BlockchainCallErrs.UnhandledError); - } - } - } + public networkId: number; + public nodeVersion: string; + private _zeroEx: ZeroEx; + private _dispatcher: Dispatcher; + private _web3Wrapper?: Web3Wrapper; + private _exchangeAddress: string; + private _userAddress: string; + private _cachedProvider: Web3.Provider; + private _ledgerSubprovider: LedgerWalletSubprovider; + private _zrxPollIntervalId: NodeJS.Timer; + private static async _onPageLoadAsync(): Promise<void> { + if (document.readyState === 'complete') { + return; // Already loaded + } + return new Promise<void>((resolve, reject) => { + window.onload = () => resolve(); + }); + } + private static _getNameGivenProvider(provider: Web3.Provider): string { + if (!_.isUndefined((provider as any).isMetaMask)) { + return constants.PROVIDER_NAME_METAMASK; + } + + // HACK: We use the fact that Parity Signer's provider is an instance of their + // internal `Web3FrameProvider` class. + const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); + if (isParitySigner) { + return constants.PROVIDER_NAME_PARITY_SIGNER; + } + + return constants.PROVIDER_NAME_GENERIC; + } + private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; + const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); + + let provider; + if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + // We catch all requests involving a users account and send it to the injectedWeb3 + // instance. All other requests go to the public hosted node. + provider = new ProviderEngine(); + provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3)); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider(publicNodeUrlsIfExistsForNetworkId)); + provider.start(); + } else if (doesInjectedWeb3Exist) { + // Since no public node for this network, all requests go to injectedWeb3 instance + provider = injectedWeb3.currentProvider; + } else { + // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node + // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 + // injected into their browser. + provider = new ProviderEngine(); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + } + + return provider; + } + constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { + this._dispatcher = dispatcher; + this._userAddress = ''; + // tslint:disable-next-line:no-floating-promises + this._onPageLoadInitFireAndForgetAsync(); + } + public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { + const isConnected = !_.isUndefined(newNetworkId); + if (!isConnected) { + this.networkId = newNetworkId; + this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode); + this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (this.networkId !== newNetworkId) { + this.networkId = newNetworkId; + this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); + await this._fetchTokenInformationAsync(); + await this._rehydrateStoreWithContractEvents(); + } + } + public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { + if (this._userAddress !== newUserAddress) { + this._userAddress = newUserAddress; + await this._fetchTokenInformationAsync(); + await this._rehydrateStoreWithContractEvents(); + } + } + public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { + if (this.nodeVersion !== nodeVersion) { + this.nodeVersion = nodeVersion; + } + } + public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + // HACK: temporarily whitelist the new WETH token address `as if` they were + // already in the tokenRegistry. + // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && tokenAddress === configs.NEW_WRAPPED_ETHERS[this.networkId]) { + return true; + } + const tokenIfExists = await this._zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); + return !_.isUndefined(tokenIfExists); + } + public getLedgerDerivationPathIfExists(): string { + if (_.isUndefined(this._ledgerSubprovider)) { + return undefined; + } + const path = this._ledgerSubprovider.getPath(); + return path; + } + public updateLedgerDerivationPathIfExists(path: string) { + if (_.isUndefined(this._ledgerSubprovider)) { + return; // noop + } + this._ledgerSubprovider.setPath(path); + } + public updateLedgerDerivationIndex(pathIndex: number) { + if (_.isUndefined(this._ledgerSubprovider)) { + return; // noop + } + this._ledgerSubprovider.setPathIndex(pathIndex); + } + public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + // Should actually be Web3.Provider|ProviderEngine union type but it causes issues + // later on in the logic. + let provider; + switch (providerType) { + case ProviderType.Ledger: { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } + + // Cache injected provider so that we can switch the user back to it easily + this._cachedProvider = this._web3Wrapper.getProviderObj(); + + this._dispatcher.updateUserAddress(''); // Clear old userAddress + + provider = new ProviderEngine(); + const ledgerWalletConfigs = { + networkId: this.networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, + }; + this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); + provider.addProvider(this._ledgerSubprovider); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.IS_MAINNET_ENABLED + ? constants.NETWORK_ID_MAINNET + : constants.NETWORK_ID_TESTNET; + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + this._web3Wrapper.destroy(); + const shouldPollUserAddress = false; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + break; + } + + case ProviderType.Injected: { + if (_.isUndefined(this._cachedProvider)) { + return; // Going from injected to injected, so we noop + } + provider = this._cachedProvider; + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + delete this._ledgerSubprovider; + delete this._cachedProvider; + break; + } + + default: + throw utils.spawnSwitchErr('providerType', providerType); + } + + await this._fetchTokenInformationAsync(); + } + public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { + utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + + const txHash = await this._zeroEx.token.setProxyAllowanceAsync( + token.address, + this._userAddress, + amountInBaseUnits, + ); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const allowance = amountInBaseUnits; + this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); + } + public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { + const txHash = await this._zeroEx.token.transferAsync( + token.address, + this._userAddress, + toAddress, + amountInBaseUnits, + ); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + this._dispatcher.showFlashMessage( + React.createElement(TokenSendCompleted, { + etherScanLinkIfExists, + token, + toAddress, + amountInBaseUnits, + }), + ); + } + public portalOrderToSignedOrder( + maker: string, + taker: string, + makerTokenAddress: string, + takerTokenAddress: string, + makerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, + makerFee: BigNumber, + takerFee: BigNumber, + expirationUnixTimestampSec: BigNumber, + feeRecipient: string, + signatureData: SignatureData, + salt: BigNumber, + ): SignedOrder { + const ecSignature = signatureData; + const exchangeContractAddress = this.getExchangeContractAddressIfExists(); + const takerOrNullAddress = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; + const signedOrder = { + ecSignature, + exchangeContractAddress, + expirationUnixTimestampSec, + feeRecipient, + maker, + makerFee, + makerTokenAddress, + makerTokenAmount, + salt, + taker: takerOrNullAddress, + takerFee, + takerTokenAddress, + takerTokenAmount, + }; + return signedOrder; + } + public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const shouldThrowOnInsufficientBalanceOrAllowance = true; + + const txHash = await this._zeroEx.exchange.fillOrderAsync( + signedOrder, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + this._userAddress, + ); + const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; + this._zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logFill = _.find(logs, { event: 'LogFill' }); + const args = (logFill.args as any) as LogFillContractEventArgs; + const filledTakerTokenAmount = args.filledTakerTokenAmount; + return filledTakerTokenAmount; + } + public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> { + const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount); + const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; + this._zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logCancel = _.find(logs, { event: ExchangeEvents.LogCancel }); + const args = (logCancel.args as any) as LogCancelContractEventArgs; + const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; + return cancelledTakerTokenAmount; + } + public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> { + utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const unavailableTakerAmount = await this._zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + return unavailableTakerAmount; + } + public getExchangeContractAddressIfExists() { + return this._exchangeAddress; + } + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + await this._zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, + fillTakerTokenAmount, + takerAddress, + ); + } + public async validateCancelOrderThrowIfInvalidAsync( + order: Order, + cancelTakerTokenAmount: BigNumber, + ): Promise<void> { + await this._zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); + } + public isValidAddress(address: string): boolean { + const lowercaseAddress = address.toLowerCase(); + return this._web3Wrapper.isAddress(lowercaseAddress); + } + public async pollTokenBalanceAsync(token: Token) { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + + this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + if (!balance.eq(currBalance)) { + this._dispatcher.replaceTokenBalanceByAddress(token.address, balance); + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + delete this._zrxPollIntervalId; + } + }, + 5000, + (err: Error) => { + utils.consoleLog(`Polling tokenBalance failed: ${err}`); + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + delete this._zrxPollIntervalId; + }, + ); + } + public async signOrderHashAsync(orderHash: string): Promise<SignatureData> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const makerAddress = this._userAddress; + // If makerAddress is undefined, this means they have a web3 instance injected into their browser + // but no account addresses associated with it. + if (_.isUndefined(makerAddress)) { + throw new Error('Tried to send a sign request but user has no associated addresses'); + } + const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress); + const signatureData = _.extend({}, ecSignature, { + hash: orderHash, + }); + this._dispatcher.updateSignatureData(signatureData); + return signatureData; + } + public async mintTestTokensAsync(token: Token) { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); + await mintableContract.mint(constants.MINT_AMOUNT, { + from: this._userAddress, + }); + const balanceDelta = constants.MINT_AMOUNT; + this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); + } + public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { + const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); + return balance; + } + public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async doesContractExistAtAddressAsync(address: string) { + const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); + return doesContractExist; + } + public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { + const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, tokenAddress); + return tokenBalanceAndAllowance; + } + public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): Promise<BigNumber[]> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + + if (_.isEmpty(ownerAddress)) { + const zero = new BigNumber(0); + return [zero, zero]; + } + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this._doesUserAddressExist()) { + balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); + allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); + } + return [balance, allowance]; + } + public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { + const tokenStateByAddress: TokenStateByAddress = {}; + for (const token of tokens) { + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this._doesUserAddressExist()) { + [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + } + const tokenState = { + balance, + allowance, + }; + tokenStateByAddress[token.address] = tokenState; + } + this._dispatcher.updateTokenStateByAddress(tokenStateByAddress); + } + public async getUserAccountsAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); + return userAccountsIfExists; + } + // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which + // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to + // manually update it. This should only be called by the LedgerConfigDialog. + public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { + this._web3Wrapper.updatePrevUserAddress(newUserAddress); + } + public destroy() { + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + this._web3Wrapper.destroy(); + this._stopWatchingExchangeLogFillEvents(); + } + private async _showEtherScanLinkAndAwaitTransactionMinedAsync( + txHash: string, + ): Promise<TransactionReceiptWithDecodedLogs> { + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + this._dispatcher.showFlashMessage( + React.createElement(TransactionSubmitted, { + etherScanLinkIfExists, + }), + ); + const receipt = await this._zeroEx.awaitTransactionMinedAsync(txHash); + return receipt; + } + private _doesUserAddressExist(): boolean { + return this._userAddress !== ''; + } + private async _rehydrateStoreWithContractEvents() { + // Ensure we are only ever listening to one set of events + this._stopWatchingExchangeLogFillEvents(); + + if (!this._doesUserAddressExist()) { + return; // short-circuit + } + + if (!_.isUndefined(this._zeroEx)) { + // Since we do not have an index on the `taker` address and want to show + // transactions where an account is either the `maker` or `taker`, we loop + // through all fill events, and filter/cache them client-side. + const filterIndexObj = {}; + await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj); + } + } + private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + // Fetch historical logs + await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); + + // Start a subscription for new logs + this._zeroEx.exchange.subscribe( + ExchangeEvents.LogFill, + indexFilterValues, + async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + if (err) { + // Note: it's not entirely clear from the documentation which + // errors will be thrown by `watch`. For now, let's log the error + // to rollbar and stop watching when one occurs + // tslint:disable-next-line:no-floating-promises + errorReporter.reportAsync(err); // fire and forget + return; + } else { + const decodedLog = decodedLogEvent.log; + if (!this._doesLogEventInvolveUser(decodedLog)) { + return; // We aren't interested in the fill event + } + this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this._convertDecodedLogToFillAsync(decodedLog); + if (decodedLogEvent.isRemoved) { + tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); + } else { + tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); + } + } + }, + ); + } + private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { + const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddress, this.networkId); + const blockRange: BlockRange = { + fromBlock, + toBlock: 'latest' as BlockParam, + }; + const decodedLogs = await this._zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( + ExchangeEvents.LogFill, + blockRange, + indexFilterValues, + ); + for (const decodedLog of decodedLogs) { + if (!this._doesLogEventInvolveUser(decodedLog)) { + continue; // We aren't interested in the fill event + } + this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this._convertDecodedLogToFillAsync(decodedLog); + tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); + } + } + private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + const args = decodedLog.args; + const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); + const fill = { + filledTakerTokenAmount: args.filledTakerTokenAmount, + filledMakerTokenAmount: args.filledMakerTokenAmount, + logIndex: decodedLog.logIndex, + maker: args.maker, + orderHash: args.orderHash, + taker: args.taker, + makerToken: args.makerToken, + takerToken: args.takerToken, + paidMakerFee: args.paidMakerFee, + paidTakerFee: args.paidTakerFee, + transactionHash: decodedLog.transactionHash, + blockTimestamp, + }; + return fill; + } + private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + const args = decodedLog.args; + const isUserMakerOrTaker = args.maker === this._userAddress || args.taker === this._userAddress; + return isUserMakerOrTaker; + } + private _updateLatestFillsBlockIfNeeded(blockNumber: number) { + const isBlockPending = _.isNull(blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = + blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this._userAddress, this.networkId, blockNumberToSet); + } + } + private _stopWatchingExchangeLogFillEvents(): void { + this._zeroEx.exchange.unsubscribeAll(); + } + private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const tokenRegistryTokens = await this._zeroEx.tokenRegistry.getTokensAsync(); + + const tokenByAddress: TokenByAddress = {}; + _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { + // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens + // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry + const iconUrl = configs.ICON_URL_BY_SYMBOL[t.symbol]; + // HACK: Temporarily we hijack the WETH addresses fetched from the tokenRegistry + // so that we can take our time with actually updating it. This ensures that when + // we deploy the new WETH page, everyone will re-fill their trackedTokens with the + // new canonical WETH. + // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz + let address = t.address; + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && t.symbol === 'WETH') { + const newEtherTokenAddressIfExists = configs.NEW_WRAPPED_ETHERS[this.networkId]; + if (!_.isUndefined(newEtherTokenAddressIfExists)) { + address = newEtherTokenAddressIfExists; + } + } + const token: Token = { + iconUrl, + address, + name: t.name, + symbol: t.symbol, + decimals: t.decimals, + isTracked: false, + isRegistered: true, + }; + tokenByAddress[token.address] = token; + }); + return tokenByAddress; + } + private async _onPageLoadInitFireAndForgetAsync() { + await Blockchain._onPageLoadAsync(); // wait for page to load + + // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in + // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot + // retrieve it from within the web3Wrapper constructor. This is and should remain the only + // call to a web3 instance outside of web3Wrapper in the entire dapp. + // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing + // Ethereum node, this call will throw. We need to handle this case gracefully + const injectedWeb3 = (window as any).web3; + let networkIdIfExists: number; + if (!_.isUndefined(injectedWeb3)) { + try { + networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)()); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + + const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); + const networkId = !_.isUndefined(networkIdIfExists) + ? networkIdIfExists + : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + const zeroExConfigs = { + networkId, + }; + this._zeroEx = new ZeroEx(provider, zeroExConfigs); + this._updateProviderName(injectedWeb3); + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + } + // This method should always be run after instantiating or updating the provider + // of the ZeroEx instance. + private async _postInstantiationOrUpdatingProviderZeroExAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + this._exchangeAddress = this._zeroEx.exchange.getContractAddress(); + } + private _updateProviderName(injectedWeb3: Web3) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const providerName = doesInjectedWeb3Exist + ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) + : constants.PROVIDER_NAME_PUBLIC; + this._dispatcher.updateInjectedProviderName(providerName); + } + private async _fetchTokenInformationAsync() { + utils.assert( + !_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', + ); + + this._dispatcher.updateBlockchainIsLoaded(false); + this._dispatcher.clearTokenByAddress(); + + const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); + + // HACK: We need to fetch the userAddress here because otherwise we cannot save the + // tracked tokens in localStorage under the users address nor fetch the token + // balances and allowances and we need to do this in order not to trigger the blockchain + // loading dialog to show up twice. First to load the contracts, and second to load the + // balances and allowances. + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + if (!_.isEmpty(this._userAddress)) { + this._dispatcher.updateUserAddress(this._userAddress); + } + + let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isUndefined(trackedTokensIfExists)) { + trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + return token; + }); + _.each(trackedTokensIfExists, token => { + trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensIfExists, trackedToken => { + if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { + tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; + } + }); + } + const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); + this._dispatcher.updateTokenByAddress(allTokens); + + // Get balance/allowance for tracked tokens + await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); + + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), + ]; + this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); + this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); + this._dispatcher.updateBlockchainIsLoaded(true); + } + private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { + const c = await contract(artifact); + const providerObj = this._web3Wrapper.getProviderObj(); + c.setProvider(providerObj); + + const artifactNetworkConfigs = artifact.networks[this.networkId]; + let contractAddress; + if (!_.isUndefined(address)) { + contractAddress = address; + } else if (!_.isUndefined(artifactNetworkConfigs)) { + contractAddress = artifactNetworkConfigs.address; + } + + if (!_.isUndefined(contractAddress)) { + const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); + throw new Error(BlockchainCallErrs.ContractDoesNotExist); + } + } + + try { + const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address); + return contractInstance; + } catch (err) { + const errMsg = `${err}`; + utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); + if (_.includes(errMsg, 'not been deployed to detected network')) { + throw new Error(BlockchainCallErrs.ContractDoesNotExist); + } else { + await errorReporter.reportAsync(err); + throw new Error(BlockchainCallErrs.UnhandledError); + } + } + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index e0f61a29b..f555ca6b1 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -9,150 +9,150 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; interface BlockchainErrDialogProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - isOpen: boolean; - userAddress: string; - toggleDialogFn: (isOpen: boolean) => void; - networkId: number; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + isOpen: boolean; + userAddress: string; + toggleDialogFn: (isOpen: boolean) => void; + networkId: number; } export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProps, undefined> { - public render() { - const dialogActions = [ - <FlatButton - key="blockchainErrOk" - label="Ok" - primary={true} - onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} - />, - ]; + public render() { + const dialogActions = [ + <FlatButton + key="blockchainErrOk" + label="Ok" + primary={true} + onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} + />, + ]; - const hasWalletAddress = this.props.userAddress !== ''; - return ( - <Dialog - title={this._getTitle(hasWalletAddress)} - titleStyle={{ fontWeight: 100 }} - actions={dialogActions} - open={this.props.isOpen} - contentStyle={{ width: 400 }} - onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} - autoScrollBodyContent={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - {this._renderExplanation(hasWalletAddress)} - </div> - </Dialog> - ); - } - private _getTitle(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { - return '0x smart contracts not found'; - } else if (!hasWalletAddress) { - return 'Enable wallet communication'; - } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { - return 'Disconnected from Ethereum network'; - } else { - return 'Unexpected error'; - } - } - private _renderExplanation(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { - return this._renderContractsNotDeployedExplanation(); - } else if (!hasWalletAddress) { - return this._renderNoWalletFoundExplanation(); - } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { - return this._renderDisconnectedFromNode(); - } else { - return this._renderUnexpectedErrorExplanation(); - } - } - private _renderDisconnectedFromNode() { - return ( - <div> - You were disconnected from the backing Ethereum node. If using{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>{' '} - or{' '} - <a href={constants.URL_MIST_DOWNLOAD} target="_blank"> - Mist - </a>{' '} - try refreshing the page. If using a locally hosted Ethereum node, make sure it's still running. - </div> - ); - } - private _renderUnexpectedErrorExplanation() { - return <div>We encountered an unexpected error. Please try refreshing the page.</div>; - } - private _renderNoWalletFoundExplanation() { - return ( - <div> - <div> - We were unable to access an Ethereum wallet you control. In order to interact with the 0x portal - dApp, we need a way to interact with one of your Ethereum wallets. There are two easy ways you can - enable us to do that: - </div> - <h4>1. Metamask chrome extension</h4> - <div> - You can install the{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>{' '} - Chrome extension Ethereum wallet. Once installed and set up, refresh this page. - <div className="pt1"> - <span className="bold">Note:</span> If you already have Metamask installed, make sure it is - unlocked. - </div> - </div> - <h4>Parity Signer</h4> - <div> - The{' '} - <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> - Parity Signer Chrome extension - </a>{' '} - lets you connect to a locally running Parity node. Make sure you have started your local Parity node - with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect - to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} - </div> - <div className="pt2"> - <span className="bold">Note:</span> If you have done one of the above steps and are still seeing - this message, we might still be unable to retrieve an Ethereum address by calling - `web3.eth.accounts`. Make sure you have created at least one Ethereum address. - </div> - </div> - ); - } - private _renderContractsNotDeployedExplanation() { - return ( - <div> - <div> - The 0x smart contracts are not deployed on the Ethereum network you are currently connected to - (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) - {configs.IS_MAINNET_ENABLED - ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` - : `.`} - </div> - <h4>Metamask</h4> - <div> - If you are using{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>, you can switch networks in the top left corner of the extension popover. - </div> - <h4>Parity Signer</h4> - <div> - If using the{' '} - <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> - Parity Signer Chrome extension - </a>, make sure to start your local Parity node with{' '} - {configs.IS_MAINNET_ENABLED - ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ + const hasWalletAddress = this.props.userAddress !== ''; + return ( + <Dialog + title={this._getTitle(hasWalletAddress)} + titleStyle={{ fontWeight: 100 }} + actions={dialogActions} + open={this.props.isOpen} + contentStyle={{ width: 400 }} + onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} + autoScrollBodyContent={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + {this._renderExplanation(hasWalletAddress)} + </div> + </Dialog> + ); + } + private _getTitle(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { + return '0x smart contracts not found'; + } else if (!hasWalletAddress) { + return 'Enable wallet communication'; + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { + return 'Disconnected from Ethereum network'; + } else { + return 'Unexpected error'; + } + } + private _renderExplanation(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { + return this._renderContractsNotDeployedExplanation(); + } else if (!hasWalletAddress) { + return this._renderNoWalletFoundExplanation(); + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { + return this._renderDisconnectedFromNode(); + } else { + return this._renderUnexpectedErrorExplanation(); + } + } + private _renderDisconnectedFromNode() { + return ( + <div> + You were disconnected from the backing Ethereum node. If using{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>{' '} + or{' '} + <a href={constants.URL_MIST_DOWNLOAD} target="_blank"> + Mist + </a>{' '} + try refreshing the page. If using a locally hosted Ethereum node, make sure it's still running. + </div> + ); + } + private _renderUnexpectedErrorExplanation() { + return <div>We encountered an unexpected error. Please try refreshing the page.</div>; + } + private _renderNoWalletFoundExplanation() { + return ( + <div> + <div> + We were unable to access an Ethereum wallet you control. In order to interact with the 0x portal + dApp, we need a way to interact with one of your Ethereum wallets. There are two easy ways you can + enable us to do that: + </div> + <h4>1. Metamask chrome extension</h4> + <div> + You can install the{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>{' '} + Chrome extension Ethereum wallet. Once installed and set up, refresh this page. + <div className="pt1"> + <span className="bold">Note:</span> If you already have Metamask installed, make sure it is + unlocked. + </div> + </div> + <h4>Parity Signer</h4> + <div> + The{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>{' '} + lets you connect to a locally running Parity node. Make sure you have started your local Parity node + with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect + to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} + </div> + <div className="pt2"> + <span className="bold">Note:</span> If you have done one of the above steps and are still seeing + this message, we might still be unable to retrieve an Ethereum address by calling + `web3.eth.accounts`. Make sure you have created at least one Ethereum address. + </div> + </div> + ); + } + private _renderContractsNotDeployedExplanation() { + return ( + <div> + <div> + The 0x smart contracts are not deployed on the Ethereum network you are currently connected to + (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} + {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) + {configs.IS_MAINNET_ENABLED + ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` + : `.`} + </div> + <h4>Metamask</h4> + <div> + If you are using{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>, you can switch networks in the top left corner of the extension popover. + </div> + <h4>Parity Signer</h4> + <div> + If using the{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>, make sure to start your local Parity node with{' '} + {configs.IS_MAINNET_ENABLED + ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ or Kovan respectively.' - : '`parity --chain kovan ui` in order to connect to Kovan.'} - </div> - </div> - ); - } + : '`parity --chain kovan ui` in order to connect to Kovan.'} + </div> + </div> + ); + } } diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 45ba5cc9e..661cc1d8c 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -8,156 +8,156 @@ import { Side, Token, TokenState } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { - direction: Side; - onComplete: (direction: Side, value: BigNumber) => void; - onCancelled: () => void; - isOpen: boolean; - token: Token; - tokenState: TokenState; - etherBalance: BigNumber; + direction: Side; + onComplete: (direction: Side, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; + etherBalance: BigNumber; } interface EthWethConversionDialogState { - value?: BigNumber; - shouldShowIncompleteErrs: boolean; - hasErrors: boolean; + value?: BigNumber; + shouldShowIncompleteErrs: boolean; + hasErrors: boolean; } export class EthWethConversionDialog extends React.Component< - EthWethConversionDialogProps, - EthWethConversionDialogState + EthWethConversionDialogProps, + EthWethConversionDialogState > { - constructor() { - super(); - this.state = { - shouldShowIncompleteErrs: false, - hasErrors: false, - }; - } - public render() { - const convertDialogActions = [ - <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, - <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, - ]; - const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; - return ( - <Dialog - title={title} - titleStyle={{ fontWeight: 100 }} - actions={convertDialogActions} - contentStyle={{ width: 448 }} - open={this.props.isOpen} - > - {this._renderConversionDialogBody()} - </Dialog> - ); - } - private _renderConversionDialogBody() { - const explanation = - this.props.direction === Side.Deposit - ? 'Convert your Ether into a tokenized, tradable form.' - : "Convert your Wrapped Ether back into it's native form."; - const isWrappedVersion = this.props.direction === Side.Receive; - return ( - <div> - <div className="pb2">{explanation}</div> - <div className="mx-auto" style={{ maxWidth: 312 }}> - <div className="flex"> - {this._renderCurrency(isWrappedVersion)} - <div style={{ paddingTop: 68 }}> - <i style={{ fontSize: 28, color: colors.darkBlue }} className="zmdi zmdi-arrow-right" /> - </div> - {this._renderCurrency(!isWrappedVersion)} - </div> - <div className="pt2 mx-auto" style={{ width: 245 }}> - {this.props.direction === Side.Receive ? ( - <TokenAmountInput - token={this.props.token} - tokenState={this.props.tokenState} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={false} - onChange={this._onValueChange.bind(this)} - amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} - /> - ) : ( - <EthAmountInput - balance={this.props.etherBalance} - amount={this.state.value} - onChange={this._onValueChange.bind(this)} - shouldCheckBalance={true} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onCancelled} - /> - )} - <div className="pt1" style={{ fontSize: 12 }}> - <div className="left">1 ETH = 1 WETH</div> - {this.props.direction === Side.Receive && ( - <div - className="right" - onClick={this._onMaxClick.bind(this)} - style={{ - color: colors.darkBlue, - textDecoration: 'underline', - cursor: 'pointer', - }} - > - Max - </div> - )} - </div> - </div> - </div> - </div> - ); - } - private _renderCurrency(isWrappedVersion: boolean) { - const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; - const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; - const symbol = isWrappedVersion ? 'WETH' : 'ETH'; - return ( - <div className="mx-auto pt2"> - <div className="center" style={{ color: colors.darkBlue }}> - {name} - </div> - <div className="center py2"> - <img src={iconUrl} style={{ width: 60 }} /> - </div> - <div className="center" style={{ fontSize: 12 }}> - ({symbol}) - </div> - </div> - ); - } - private _onMaxClick() { - this.setState({ - value: this.props.tokenState.balance, - }); - } - private _onValueChange(isValid: boolean, amount?: BigNumber) { - this.setState({ - value: amount, - hasErrors: !isValid, - }); - } - private _onConvertClick() { - if (this.state.hasErrors) { - this.setState({ - shouldShowIncompleteErrs: true, - }); - } else { - const value = this.state.value; - this.setState({ - value: undefined, - }); - this.props.onComplete(this.props.direction, value); - } - } - private _onCancel() { - this.setState({ - value: undefined, - }); - this.props.onCancelled(); - } + constructor() { + super(); + this.state = { + shouldShowIncompleteErrs: false, + hasErrors: false, + }; + } + public render() { + const convertDialogActions = [ + <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, + <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, + ]; + const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; + return ( + <Dialog + title={title} + titleStyle={{ fontWeight: 100 }} + actions={convertDialogActions} + contentStyle={{ width: 448 }} + open={this.props.isOpen} + > + {this._renderConversionDialogBody()} + </Dialog> + ); + } + private _renderConversionDialogBody() { + const explanation = + this.props.direction === Side.Deposit + ? 'Convert your Ether into a tokenized, tradable form.' + : "Convert your Wrapped Ether back into it's native form."; + const isWrappedVersion = this.props.direction === Side.Receive; + return ( + <div> + <div className="pb2">{explanation}</div> + <div className="mx-auto" style={{ maxWidth: 312 }}> + <div className="flex"> + {this._renderCurrency(isWrappedVersion)} + <div style={{ paddingTop: 68 }}> + <i style={{ fontSize: 28, color: colors.darkBlue }} className="zmdi zmdi-arrow-right" /> + </div> + {this._renderCurrency(!isWrappedVersion)} + </div> + <div className="pt2 mx-auto" style={{ width: 245 }}> + {this.props.direction === Side.Receive ? ( + <TokenAmountInput + token={this.props.token} + tokenState={this.props.tokenState} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.value} + onVisitBalancesPageClick={this.props.onCancelled} + /> + ) : ( + <EthAmountInput + balance={this.props.etherBalance} + amount={this.state.value} + onChange={this._onValueChange.bind(this)} + shouldCheckBalance={true} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onCancelled} + /> + )} + <div className="pt1" style={{ fontSize: 12 }}> + <div className="left">1 ETH = 1 WETH</div> + {this.props.direction === Side.Receive && ( + <div + className="right" + onClick={this._onMaxClick.bind(this)} + style={{ + color: colors.darkBlue, + textDecoration: 'underline', + cursor: 'pointer', + }} + > + Max + </div> + )} + </div> + </div> + </div> + </div> + ); + } + private _renderCurrency(isWrappedVersion: boolean) { + const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; + const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; + const symbol = isWrappedVersion ? 'WETH' : 'ETH'; + return ( + <div className="mx-auto pt2"> + <div className="center" style={{ color: colors.darkBlue }}> + {name} + </div> + <div className="center py2"> + <img src={iconUrl} style={{ width: 60 }} /> + </div> + <div className="center" style={{ fontSize: 12 }}> + ({symbol}) + </div> + </div> + ); + } + private _onMaxClick() { + this.setState({ + value: this.props.tokenState.balance, + }); + } + private _onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + value: amount, + hasErrors: !isValid, + }); + } + private _onConvertClick() { + if (this.state.hasErrors) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + value: undefined, + }); + this.props.onComplete(this.props.direction, value); + } + } + private _onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } } diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 8b7760a1a..60db93c52 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -17,245 +17,245 @@ import { utils } from 'ts/utils/utils'; const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; enum LedgerSteps { - CONNECT, - SELECT_ADDRESS, + CONNECT, + SELECT_ADDRESS, } interface LedgerConfigDialogProps { - isOpen: boolean; - toggleDialogFn: (isOpen: boolean) => void; - dispatcher: Dispatcher; - blockchain: Blockchain; - networkId: number; + isOpen: boolean; + toggleDialogFn: (isOpen: boolean) => void; + dispatcher: Dispatcher; + blockchain: Blockchain; + networkId: number; } interface LedgerConfigDialogState { - didConnectFail: boolean; - stepIndex: LedgerSteps; - userAddresses: string[]; - addressBalances: BigNumber[]; - derivationPath: string; - derivationErrMsg: string; + didConnectFail: boolean; + stepIndex: LedgerSteps; + userAddresses: string[]; + addressBalances: BigNumber[]; + derivationPath: string; + derivationErrMsg: string; } export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> { - constructor(props: LedgerConfigDialogProps) { - super(props); - this.state = { - didConnectFail: false, - stepIndex: LedgerSteps.CONNECT, - userAddresses: [], - addressBalances: [], - derivationPath: configs.DEFAULT_DERIVATION_PATH, - derivationErrMsg: '', - }; - } - public render() { - const dialogActions = [ - <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, - ]; - const dialogTitle = - this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; - return ( - <Dialog - title={dialogTitle} - titleStyle={{ fontWeight: 100 }} - actions={dialogActions} - open={this.props.isOpen} - onRequestClose={this._onClose.bind(this)} - autoScrollBodyContent={true} - bodyStyle={{ paddingBottom: 0 }} - > - <div style={{ color: colors.grey700, paddingTop: 1 }}> - {this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep()} - {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep()} - </div> - </Dialog> - ); - } - private _renderConnectStep() { - return ( - <div> - <div className="h4 pt3">Follow these instructions before proceeding:</div> - <ol> - <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> - <li className="pb1">Verify that Browser Support is enabled in Settings</li> - <li className="pb1"> - If no Browser Support is found in settings, verify that you have{' '} - <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> - Firmware >1.2 - </a> - </li> - </ol> - <div className="center pb3"> - <LifeCycleRaisedButton - isPrimary={true} - labelReady="Connect to Ledger" - labelLoading="Connecting..." - labelComplete="Connected!" - onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} - /> - {this.state.didConnectFail && ( - <div className="pt2 left-align" style={{ color: colors.red200 }}> - Failed to connect. Follow the instructions and try again. - </div> - )} - </div> - </div> - ); - } - private _renderSelectAddressStep() { - return ( - <div> - <div> - <Table bodyStyle={{ height: 300 }} onRowSelection={this._onAddressSelected.bind(this)}> - <TableHeader displaySelectAll={false}> - <TableRow> - <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody>{this._renderAddressTableRows()}</TableBody> - </Table> - </div> - <div className="flex pt2" style={{ height: 100 }}> - <div className="overflow-hidden" style={{ width: 180 }}> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText="Update path derivation (advanced)" - value={this.state.derivationPath} - errorText={this.state.derivationErrMsg} - onChange={this._onDerivationPathChanged.bind(this)} - /> - </div> - <div className="pl2" style={{ paddingTop: 28 }}> - <LifeCycleRaisedButton - labelReady="Update" - labelLoading="Updating..." - labelComplete="Updated!" - onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)} - /> - </div> - </div> - </div> - ); - } - private _renderAddressTableRows() { - const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { - const balance = this.state.addressBalances[i]; - const addressTooltipId = `address-${userAddress}`; - const balanceTooltipId = `balance-${userAddress}`; - const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; - // We specifically prefix kovan ETH. - // TODO: We should probably add prefixes for all networks - const isKovanNetwork = networkName === 'Kovan'; - const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; - return ( - <TableRow key={userAddress} style={{ height: 40 }}> - <TableRowColumn colSpan={2}> - <div data-tip={true} data-for={addressTooltipId}> - {userAddress} - </div> - <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip> - </TableRowColumn> - <TableRowColumn> - <div data-tip={true} data-for={balanceTooltipId}> - {balanceString} - </div> - <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip> - </TableRowColumn> - </TableRow> - ); - }); - return rows; - } - private _onClose() { - this.setState({ - didConnectFail: false, - }); - const isOpen = false; - this.props.toggleDialogFn(isOpen); - } - private _onAddressSelected(selectedRowIndexes: number[]) { - const selectedRowIndex = selectedRowIndexes[0]; - this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); - const selectedAddress = this.state.userAddresses[selectedRowIndex]; - const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; - this.props.dispatcher.updateUserAddress(selectedAddress); - this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); - this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); - this.setState({ - stepIndex: LedgerSteps.CONNECT, - }); - const isOpen = false; - this.props.toggleDialogFn(isOpen); - } - private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> { - const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); - let didSucceed; - if (currentlySetPath === this.state.derivationPath) { - didSucceed = true; - return didSucceed; - } - this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); - didSucceed = await this._fetchAddressesAndBalancesAsync(); - if (!didSucceed) { - this.setState({ - derivationErrMsg: 'Failed to connect to Ledger.', - }); - } - return didSucceed; - } - private async _fetchAddressesAndBalancesAsync() { - let userAddresses: string[]; - const addressBalances: BigNumber[] = []; - try { - userAddresses = await this._getUserAddressesAsync(); - for (const address of userAddresses) { - const balance = await this.props.blockchain.getBalanceInEthAsync(address); - addressBalances.push(balance); - } - } catch (err) { - utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); - this.setState({ - didConnectFail: true, - }); - return false; - } - this.setState({ - userAddresses, - addressBalances, - }); - return true; - } - private _onDerivationPathChanged(e: any, derivationPath: string) { - let derivationErrMsg = ''; - if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { - derivationErrMsg = 'Must be valid Ethereum path.'; - } + constructor(props: LedgerConfigDialogProps) { + super(props); + this.state = { + didConnectFail: false, + stepIndex: LedgerSteps.CONNECT, + userAddresses: [], + addressBalances: [], + derivationPath: configs.DEFAULT_DERIVATION_PATH, + derivationErrMsg: '', + }; + } + public render() { + const dialogActions = [ + <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, + ]; + const dialogTitle = + this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; + return ( + <Dialog + title={dialogTitle} + titleStyle={{ fontWeight: 100 }} + actions={dialogActions} + open={this.props.isOpen} + onRequestClose={this._onClose.bind(this)} + autoScrollBodyContent={true} + bodyStyle={{ paddingBottom: 0 }} + > + <div style={{ color: colors.grey700, paddingTop: 1 }}> + {this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep()} + {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep()} + </div> + </Dialog> + ); + } + private _renderConnectStep() { + return ( + <div> + <div className="h4 pt3">Follow these instructions before proceeding:</div> + <ol> + <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> + <li className="pb1">Verify that Browser Support is enabled in Settings</li> + <li className="pb1"> + If no Browser Support is found in settings, verify that you have{' '} + <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> + Firmware >1.2 + </a> + </li> + </ol> + <div className="center pb3"> + <LifeCycleRaisedButton + isPrimary={true} + labelReady="Connect to Ledger" + labelLoading="Connecting..." + labelComplete="Connected!" + onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} + /> + {this.state.didConnectFail && ( + <div className="pt2 left-align" style={{ color: colors.red200 }}> + Failed to connect. Follow the instructions and try again. + </div> + )} + </div> + </div> + ); + } + private _renderSelectAddressStep() { + return ( + <div> + <div> + <Table bodyStyle={{ height: 300 }} onRowSelection={this._onAddressSelected.bind(this)}> + <TableHeader displaySelectAll={false}> + <TableRow> + <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody>{this._renderAddressTableRows()}</TableBody> + </Table> + </div> + <div className="flex pt2" style={{ height: 100 }}> + <div className="overflow-hidden" style={{ width: 180 }}> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText="Update path derivation (advanced)" + value={this.state.derivationPath} + errorText={this.state.derivationErrMsg} + onChange={this._onDerivationPathChanged.bind(this)} + /> + </div> + <div className="pl2" style={{ paddingTop: 28 }}> + <LifeCycleRaisedButton + labelReady="Update" + labelLoading="Updating..." + labelComplete="Updated!" + onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)} + /> + </div> + </div> + </div> + ); + } + private _renderAddressTableRows() { + const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { + const balance = this.state.addressBalances[i]; + const addressTooltipId = `address-${userAddress}`; + const balanceTooltipId = `balance-${userAddress}`; + const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + // We specifically prefix kovan ETH. + // TODO: We should probably add prefixes for all networks + const isKovanNetwork = networkName === 'Kovan'; + const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; + return ( + <TableRow key={userAddress} style={{ height: 40 }}> + <TableRowColumn colSpan={2}> + <div data-tip={true} data-for={addressTooltipId}> + {userAddress} + </div> + <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip> + </TableRowColumn> + <TableRowColumn> + <div data-tip={true} data-for={balanceTooltipId}> + {balanceString} + </div> + <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip> + </TableRowColumn> + </TableRow> + ); + }); + return rows; + } + private _onClose() { + this.setState({ + didConnectFail: false, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private _onAddressSelected(selectedRowIndexes: number[]) { + const selectedRowIndex = selectedRowIndexes[0]; + this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); + const selectedAddress = this.state.userAddresses[selectedRowIndex]; + const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; + this.props.dispatcher.updateUserAddress(selectedAddress); + this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); + this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); + this.setState({ + stepIndex: LedgerSteps.CONNECT, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> { + const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); + let didSucceed; + if (currentlySetPath === this.state.derivationPath) { + didSucceed = true; + return didSucceed; + } + this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); + didSucceed = await this._fetchAddressesAndBalancesAsync(); + if (!didSucceed) { + this.setState({ + derivationErrMsg: 'Failed to connect to Ledger.', + }); + } + return didSucceed; + } + private async _fetchAddressesAndBalancesAsync() { + let userAddresses: string[]; + const addressBalances: BigNumber[] = []; + try { + userAddresses = await this._getUserAddressesAsync(); + for (const address of userAddresses) { + const balance = await this.props.blockchain.getBalanceInEthAsync(address); + addressBalances.push(balance); + } + } catch (err) { + utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); + this.setState({ + didConnectFail: true, + }); + return false; + } + this.setState({ + userAddresses, + addressBalances, + }); + return true; + } + private _onDerivationPathChanged(e: any, derivationPath: string) { + let derivationErrMsg = ''; + if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { + derivationErrMsg = 'Must be valid Ethereum path.'; + } - this.setState({ - derivationPath, - derivationErrMsg, - }); - } - private async _onConnectLedgerClickAsync() { - const didSucceed = await this._fetchAddressesAndBalancesAsync(); - if (didSucceed) { - this.setState({ - stepIndex: LedgerSteps.SELECT_ADDRESS, - }); - } - return didSucceed; - } - private async _getUserAddressesAsync(): Promise<string[]> { - let userAddresses: string[]; - userAddresses = await this.props.blockchain.getUserAccountsAsync(); + this.setState({ + derivationPath, + derivationErrMsg, + }); + } + private async _onConnectLedgerClickAsync() { + const didSucceed = await this._fetchAddressesAndBalancesAsync(); + if (didSucceed) { + this.setState({ + stepIndex: LedgerSteps.SELECT_ADDRESS, + }); + } + return didSucceed; + } + private async _getUserAddressesAsync(): Promise<string[]> { + let userAddresses: string[]; + userAddresses = await this.props.blockchain.getUserAccountsAsync(); - if (_.isEmpty(userAddresses)) { - throw new Error('No addresses retrieved.'); - } - return userAddresses; - } + if (_.isEmpty(userAddresses)) { + throw new Error('No addresses retrieved.'); + } + return userAddresses; + } } diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index 1c5efc978..3ecc454a0 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -4,33 +4,33 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface PortalDisclaimerDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { - return ( - <Dialog - title="0x Portal Disclaimer" - titleStyle={{ fontWeight: 100 }} - actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]} - open={props.isOpen} - onRequestClose={props.onToggleDialog} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - 0x Portal is a free software-based tool intended to help users to buy and sell ERC20-compatible - blockchain tokens through the 0x protocol on a purely peer-to-peer basis. 0x portal is not a - regulated marketplace, exchange or intermediary of any kind, and therefore, you should only use 0x - portal to exchange tokens that are not securities, commodity interests, or any other form of - regulated instrument. 0x has not attempted to screen or otherwise limit the tokens that you may - enter in 0x Portal. By clicking “I Agree” below, you understand that you are solely responsible for - using 0x Portal and buying and selling tokens using 0x Portal in compliance with all applicable laws - and regulations. - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="0x Portal Disclaimer" + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + 0x Portal is a free software-based tool intended to help users to buy and sell ERC20-compatible + blockchain tokens through the 0x protocol on a purely peer-to-peer basis. 0x portal is not a + regulated marketplace, exchange or intermediary of any kind, and therefore, you should only use 0x + portal to exchange tokens that are not securities, commodity interests, or any other form of + regulated instrument. 0x has not attempted to screen or otherwise limit the tokens that you may + enter in 0x Portal. By clicking “I Agree” below, you understand that you are solely responsible for + using 0x Portal and buying and selling tokens using 0x Portal in compliance with all applicable laws + and regulations. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index b9022cd9b..b3dbce598 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -8,110 +8,110 @@ import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { Token, TokenState } from 'ts/types'; interface SendDialogProps { - onComplete: (recipient: string, value: BigNumber) => void; - onCancelled: () => void; - isOpen: boolean; - token: Token; - tokenState: TokenState; + onComplete: (recipient: string, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; } interface SendDialogState { - value?: BigNumber; - recipient: string; - shouldShowIncompleteErrs: boolean; - isAmountValid: boolean; + value?: BigNumber; + recipient: string; + shouldShowIncompleteErrs: boolean; + isAmountValid: boolean; } export class SendDialog extends React.Component<SendDialogProps, SendDialogState> { - constructor() { - super(); - this.state = { - recipient: '', - shouldShowIncompleteErrs: false, - isAmountValid: false, - }; - } - public render() { - const transferDialogActions = [ - <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, - <FlatButton - key="sendTransfer" - disabled={this._hasErrors()} - label="Send" - primary={true} - onTouchTap={this._onSendClick.bind(this)} - />, - ]; - return ( - <Dialog - title="I want to send" - titleStyle={{ fontWeight: 100 }} - actions={transferDialogActions} - open={this.props.isOpen} - > - {this._renderSendDialogBody()} - </Dialog> - ); - } - private _renderSendDialogBody() { - return ( - <div className="mx-auto" style={{ maxWidth: 300 }}> - <div style={{ height: 80 }}> - <AddressInput - initialAddress={this.state.recipient} - updateAddress={this._onRecipientChange.bind(this)} - isRequired={true} - label={'Recipient address'} - hintText={'Address'} - /> - </div> - <TokenAmountInput - label="Amount to send" - token={this.props.token} - tokenState={this.props.tokenState} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={false} - onChange={this._onValueChange.bind(this)} - amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} - /> - </div> - ); - } - private _onRecipientChange(recipient?: string) { - this.setState({ - shouldShowIncompleteErrs: false, - recipient, - }); - } - private _onValueChange(isValid: boolean, amount?: BigNumber) { - this.setState({ - isAmountValid: isValid, - value: amount, - }); - } - private _onSendClick() { - if (this._hasErrors()) { - this.setState({ - shouldShowIncompleteErrs: true, - }); - } else { - const value = this.state.value; - this.setState({ - recipient: undefined, - value: undefined, - }); - this.props.onComplete(this.state.recipient, value); - } - } - private _onCancel() { - this.setState({ - value: undefined, - }); - this.props.onCancelled(); - } - private _hasErrors() { - return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; - } + constructor() { + super(); + this.state = { + recipient: '', + shouldShowIncompleteErrs: false, + isAmountValid: false, + }; + } + public render() { + const transferDialogActions = [ + <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, + <FlatButton + key="sendTransfer" + disabled={this._hasErrors()} + label="Send" + primary={true} + onTouchTap={this._onSendClick.bind(this)} + />, + ]; + return ( + <Dialog + title="I want to send" + titleStyle={{ fontWeight: 100 }} + actions={transferDialogActions} + open={this.props.isOpen} + > + {this._renderSendDialogBody()} + </Dialog> + ); + } + private _renderSendDialogBody() { + return ( + <div className="mx-auto" style={{ maxWidth: 300 }}> + <div style={{ height: 80 }}> + <AddressInput + initialAddress={this.state.recipient} + updateAddress={this._onRecipientChange.bind(this)} + isRequired={true} + label={'Recipient address'} + hintText={'Address'} + /> + </div> + <TokenAmountInput + label="Amount to send" + token={this.props.token} + tokenState={this.props.tokenState} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.value} + onVisitBalancesPageClick={this.props.onCancelled} + /> + </div> + ); + } + private _onRecipientChange(recipient?: string) { + this.setState({ + shouldShowIncompleteErrs: false, + recipient, + }); + } + private _onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + isAmountValid: isValid, + value: amount, + }); + } + private _onSendClick() { + if (this._hasErrors()) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + recipient: undefined, + value: undefined, + }); + this.props.onComplete(this.state.recipient, value); + } + } + private _onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } + private _hasErrors() { + return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; + } } diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx index b1804e95c..3f29d46f8 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -9,94 +9,94 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { Token, TokenByAddress } from 'ts/types'; interface TrackTokenConfirmationDialogProps { - tokens: Token[]; - tokenByAddress: TokenByAddress; - isOpen: boolean; - onToggleDialog: (didConfirmTokenTracking: boolean) => void; - dispatcher: Dispatcher; - networkId: number; - blockchain: Blockchain; - userAddress: string; + tokens: Token[]; + tokenByAddress: TokenByAddress; + isOpen: boolean; + onToggleDialog: (didConfirmTokenTracking: boolean) => void; + dispatcher: Dispatcher; + networkId: number; + blockchain: Blockchain; + userAddress: string; } interface TrackTokenConfirmationDialogState { - isAddingTokenToTracked: boolean; + isAddingTokenToTracked: boolean; } export class TrackTokenConfirmationDialog extends React.Component< - TrackTokenConfirmationDialogProps, - TrackTokenConfirmationDialogState + TrackTokenConfirmationDialogProps, + TrackTokenConfirmationDialogState > { - constructor(props: TrackTokenConfirmationDialogProps) { - super(props); - this.state = { - isAddingTokenToTracked: false, - }; - } - public render() { - const tokens = this.props.tokens; - return ( - <Dialog - title="Tracking confirmation" - titleStyle={{ fontWeight: 100 }} - actions={[ - <FlatButton - key="trackNo" - label="No" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} - />, - <FlatButton - key="trackYes" - label="Yes" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} - />, - ]} - open={this.props.isOpen} - onRequestClose={this.props.onToggleDialog.bind(this, false)} - autoScrollBodyContent={true} - > - <div className="pt2"> - <TrackTokenConfirmation - tokens={tokens} - networkId={this.props.networkId} - tokenByAddress={this.props.tokenByAddress} - isAddingTokenToTracked={this.state.isAddingTokenToTracked} - /> - </div> - </Dialog> - ); - } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { - if (!didUserAcceptTracking) { - this.props.onToggleDialog(didUserAcceptTracking); - return; - } - this.setState({ - isAddingTokenToTracked: true, - }); - for (const token of this.props.tokens) { - const newTokenEntry = { - ...token, - }; + constructor(props: TrackTokenConfirmationDialogProps) { + super(props); + this.state = { + isAddingTokenToTracked: false, + }; + } + public render() { + const tokens = this.props.tokens; + return ( + <Dialog + title="Tracking confirmation" + titleStyle={{ fontWeight: 100 }} + actions={[ + <FlatButton + key="trackNo" + label="No" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} + />, + <FlatButton + key="trackYes" + label="Yes" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} + />, + ]} + open={this.props.isOpen} + onRequestClose={this.props.onToggleDialog.bind(this, false)} + autoScrollBodyContent={true} + > + <div className="pt2"> + <TrackTokenConfirmation + tokens={tokens} + networkId={this.props.networkId} + tokenByAddress={this.props.tokenByAddress} + isAddingTokenToTracked={this.state.isAddingTokenToTracked} + /> + </div> + </Dialog> + ); + } + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.props.onToggleDialog(didUserAcceptTracking); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + for (const token of this.props.tokens) { + const newTokenEntry = { + ...token, + }; - newTokenEntry.isTracked = true; - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); - } + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + } - this.setState({ - isAddingTokenToTracked: false, - }); - this.props.onToggleDialog(didUserAcceptTracking); - } + this.setState({ + isAddingTokenToTracked: false, + }); + this.props.onToggleDialog(didUserAcceptTracking); + } } diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx index 2ea51d07b..098e3e26d 100644 --- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -5,42 +5,42 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface U2fNotSupportedDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { - return ( - <Dialog - title="U2F Not Supported" - titleStyle={{ fontWeight: 100 }} - actions={[<FlatButton key="u2fNo" label="Ok" onTouchTap={props.onToggleDialog.bind(this)} />]} - open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} - autoScrollBodyContent={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - It looks like your browser does not support U2F connections required for us to communicate with your - hardware wallet. Please use a browser that supports U2F connections and try again. - </div> - <div> - <ul> - <li className="pb1">Chrome version 38 or later</li> - <li className="pb1">Opera version 40 of later</li> - <li> - Firefox with{' '} - <a - href={constants.URL_FIREFOX_U2F_ADDON} - target="_blank" - style={{ textDecoration: 'underline' }} - > - this extension - </a>. - </li> - </ul> - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="U2F Not Supported" + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="u2fNo" label="Ok" onTouchTap={props.onToggleDialog.bind(this)} />]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + It looks like your browser does not support U2F connections required for us to communicate with your + hardware wallet. Please use a browser that supports U2F connections and try again. + </div> + <div> + <ul> + <li className="pb1">Chrome version 38 or later</li> + <li className="pb1">Opera version 40 of later</li> + <li> + Firefox with{' '} + <a + href={constants.URL_FIREFOX_U2F_ADDON} + target="_blank" + style={{ textDecoration: 'underline' }} + > + this extension + </a>. + </li> + </ul> + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx index 98436eb50..9e91ff12d 100644 --- a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx +++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx @@ -4,30 +4,30 @@ import { colors } from 'material-ui/styles'; import * as React from 'react'; interface WrappedEthSectionNoticeDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDialogProps) { - return ( - <Dialog - title="Dedicated Wrapped Ether Section" - titleStyle={{ fontWeight: 100 }} - actions={[ - <FlatButton key="acknowledgeWrapEthSection" label="Sounds good" onTouchTap={props.onToggleDialog} />, - ]} - open={props.isOpen} - onRequestClose={props.onToggleDialog} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. Don't worry, unwrapping - Ether tied to the old Wrapped Ether token can be done at any time by clicking on the "Wrap ETH" - section in the menu to the left. - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="Dedicated Wrapped Ether Section" + titleStyle={{ fontWeight: 100 }} + actions={[ + <FlatButton key="acknowledgeWrapEthSection" label="Sounds good" onTouchTap={props.onToggleDialog} />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. Don't worry, unwrapping + Ether tied to the old Wrapped Ether token can be done at any time by clicking on the "Wrap ETH" + section in the menu to the left. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index af1b33eef..300e71f1f 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -12,115 +12,115 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface EthWethConversionButtonProps { - direction: Side; - ethToken: Token; - ethTokenState: TokenState; - dispatcher: Dispatcher; - blockchain: Blockchain; - userEtherBalance: BigNumber; - isOutdatedWrappedEther: boolean; - onConversionSuccessful?: () => void; - isDisabled?: boolean; + direction: Side; + ethToken: Token; + ethTokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + userEtherBalance: BigNumber; + isOutdatedWrappedEther: boolean; + onConversionSuccessful?: () => void; + isDisabled?: boolean; } interface EthWethConversionButtonState { - isEthConversionDialogVisible: boolean; - isEthConversionHappening: boolean; + isEthConversionDialogVisible: boolean; + isEthConversionHappening: boolean; } export class EthWethConversionButton extends React.Component< - EthWethConversionButtonProps, - EthWethConversionButtonState + EthWethConversionButtonProps, + EthWethConversionButtonState > { - public static defaultProps: Partial<EthWethConversionButtonProps> = { - isDisabled: false, - onConversionSuccessful: _.noop, - }; - public constructor(props: EthWethConversionButtonProps) { - super(props); - this.state = { - isEthConversionDialogVisible: false, - isEthConversionHappening: false, - }; - } - public render() { - const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; - let callToActionLabel; - let inProgressLabel; - if (this.props.direction === Side.Deposit) { - callToActionLabel = 'Wrap'; - inProgressLabel = 'Wrapping...'; - } else { - callToActionLabel = 'Unwrap'; - inProgressLabel = 'Unwrapping...'; - } - return ( - <div> - <RaisedButton - style={{ width: '100%' }} - labelStyle={labelStyle} - disabled={this.props.isDisabled || this.state.isEthConversionHappening} - label={this.state.isEthConversionHappening ? inProgressLabel : callToActionLabel} - onClick={this._toggleConversionDialog.bind(this)} - /> - <EthWethConversionDialog - direction={this.props.direction} - isOpen={this.state.isEthConversionDialogVisible} - onComplete={this._onConversionAmountSelectedAsync.bind(this)} - onCancelled={this._toggleConversionDialog.bind(this)} - etherBalance={this.props.userEtherBalance} - token={this.props.ethToken} - tokenState={this.props.ethTokenState} - /> - </div> - ); - } - private _toggleConversionDialog() { - this.setState({ - isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, - }); - } - private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { - this.setState({ - isEthConversionHappening: true, - }); - this._toggleConversionDialog(); - const token = this.props.ethToken; - const tokenState = this.props.ethTokenState; - let balance = tokenState.balance; - try { - if (direction === Side.Deposit) { - await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); - const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); - this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); - balance = balance.plus(value); - } else { - await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); - const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); - this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); - balance = balance.minus(value); - } - if (!this.props.isOutdatedWrappedEther) { - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); - } - this.props.onConversionSuccessful(); - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (!_.includes(errMsg, 'User denied transaction')) { - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - const errorMsg = - direction === Side.Deposit - ? 'Failed to wrap your ETH. Please try again.' - : 'Failed to unwrap your WETH. Please try again.'; - this.props.dispatcher.showFlashMessage(errorMsg); - await errorReporter.reportAsync(err); - } - } - this.setState({ - isEthConversionHappening: false, - }); - } + public static defaultProps: Partial<EthWethConversionButtonProps> = { + isDisabled: false, + onConversionSuccessful: _.noop, + }; + public constructor(props: EthWethConversionButtonProps) { + super(props); + this.state = { + isEthConversionDialogVisible: false, + isEthConversionHappening: false, + }; + } + public render() { + const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; + let callToActionLabel; + let inProgressLabel; + if (this.props.direction === Side.Deposit) { + callToActionLabel = 'Wrap'; + inProgressLabel = 'Wrapping...'; + } else { + callToActionLabel = 'Unwrap'; + inProgressLabel = 'Unwrapping...'; + } + return ( + <div> + <RaisedButton + style={{ width: '100%' }} + labelStyle={labelStyle} + disabled={this.props.isDisabled || this.state.isEthConversionHappening} + label={this.state.isEthConversionHappening ? inProgressLabel : callToActionLabel} + onClick={this._toggleConversionDialog.bind(this)} + /> + <EthWethConversionDialog + direction={this.props.direction} + isOpen={this.state.isEthConversionDialogVisible} + onComplete={this._onConversionAmountSelectedAsync.bind(this)} + onCancelled={this._toggleConversionDialog.bind(this)} + etherBalance={this.props.userEtherBalance} + token={this.props.ethToken} + tokenState={this.props.ethTokenState} + /> + </div> + ); + } + private _toggleConversionDialog() { + this.setState({ + isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, + }); + } + private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + this.setState({ + isEthConversionHappening: true, + }); + this._toggleConversionDialog(); + const token = this.props.ethToken; + const tokenState = this.props.ethTokenState; + let balance = tokenState.balance; + try { + if (direction === Side.Deposit) { + await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); + const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); + this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); + balance = balance.plus(value); + } else { + await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); + const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); + balance = balance.minus(value); + } + if (!this.props.isOutdatedWrappedEther) { + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } + this.props.onConversionSuccessful(); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + const errorMsg = + direction === Side.Deposit + ? 'Failed to wrap your ETH. Please try again.' + : 'Failed to unwrap your WETH. Please try again.'; + this.props.dispatcher.showFlashMessage(errorMsg); + await errorReporter.reportAsync(err); + } + } + this.setState({ + isEthConversionHappening: false, + }); + } } diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 1593d51f0..d074ec787 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -10,13 +10,13 @@ import { Blockchain } from 'ts/blockchain'; import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - EtherscanLinkSuffixes, - OutdatedWrappedEtherByNetworkId, - Side, - Token, - TokenByAddress, - TokenState, - TokenStateByAddress, + EtherscanLinkSuffixes, + OutdatedWrappedEtherByNetworkId, + Side, + Token, + TokenByAddress, + TokenState, + TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -30,345 +30,345 @@ const ETHER_ICON_PATH = '/images/ether.png'; const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; interface OutdatedWETHAddressToIsStateLoaded { - [address: string]: boolean; + [address: string]: boolean; } interface OutdatedWETHStateByAddress { - [address: string]: TokenState; + [address: string]: TokenState; } interface EthWrappersProps { - networkId: number; - blockchain: Blockchain; - dispatcher: Dispatcher; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; + networkId: number; + blockchain: Blockchain; + dispatcher: Dispatcher; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; } interface EthWrappersState { - outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; - outdatedWETHStateByAddress: OutdatedWETHStateByAddress; + outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; + outdatedWETHStateByAddress: OutdatedWETHStateByAddress; } export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { - constructor(props: EthWrappersProps) { - super(props); - const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; - _.each(outdatedWETHAddresses, outdatedWETHAddress => { - outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; - outdatedWETHStateByAddress[outdatedWETHAddress] = { - balance: new BigNumber(0), - allowance: new BigNumber(0), - }; - }); - this.state = { - outdatedWETHAddressToIsStateLoaded, - outdatedWETHStateByAddress, - }; - } - public componentDidMount() { - window.scrollTo(0, 0); - // tslint:disable-next-line:no-floating-promises - this._fetchOutdatedWETHStateAsync(); - } - public render() { - const tokens = _.values(this.props.tokenByAddress); - const etherToken = _.find(tokens, { symbol: 'WETH' }); - const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; - const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); - const isBidirectional = true; - const etherscanUrl = utils.getEtherScanLinkIfExists( - etherToken.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); - return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> - <div className="relative"> - <h3>ETH Wrapper</h3> - <div className="absolute" style={{ top: 0, right: 0 }}> - <a target="_blank" href={constants.URL_WETH_IO} style={{ color: colors.grey }}> - <div className="flex"> - <div>About Wrapped ETH</div> - <div className="pl1"> - <i className="zmdi zmdi-open-in-new" /> - </div> - </div> - </a> - </div> - </div> - <Divider /> - <div> - <div className="py2">Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.</div> - <div> - <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>ETH Token</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableHeaderColumn className="center"> - {this._renderActionColumnTitle(isBidirectional)} - </TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - <TableRow key="ETH"> - <TableRowColumn className="py1"> - <div className="flex"> - <img - style={{ - width: ICON_DIMENSION, - height: ICON_DIMENSION, - }} - src={ETHER_ICON_PATH} - /> - <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> - ETH - </div> - </div> - </TableRowColumn> - <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH - </TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isOutdatedWrappedEther={false} - direction={Side.Deposit} - ethToken={etherToken} - ethTokenState={etherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - /> - </TableRowColumn> - </TableRow> - <TableRow key="WETH"> - <TableRowColumn className="py1"> - {this._renderTokenLink(tokenLabel, etherscanUrl)} - </TableRowColumn> - <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isOutdatedWrappedEther={false} - direction={Side.Receive} - ethToken={etherToken} - ethTokenState={etherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - /> - </TableRowColumn> - </TableRow> - </TableBody> - </Table> - </div> - </div> - <div> - <h4>Outdated WETH</h4> - <Divider /> - <div className="pt2" style={{ lineHeight: 1.5 }}> - The{' '} - <a href="https://blog.0xproject.com/canonical-weth-a9aa7d0279dd" target="_blank"> - canonical WETH - </a>{' '} - contract is updated when necessary. Unwrap outdated WETH in order to
retrieve your ETH and move - it to the updated WETH token. - </div> - <div> - <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>WETH Version</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableHeaderColumn className="center"> - {this._renderActionColumnTitle(!isBidirectional)} - </TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - {this._renderOutdatedWeths(etherToken, etherTokenState)} - </TableBody> - </Table> - </div> - </div> - </div> - ); - } - private _renderActionColumnTitle(isBidirectional: boolean) { - let iconClass = 'zmdi-long-arrow-right'; - let leftSymbol = 'WETH'; - let rightSymbol = 'ETH'; - if (isBidirectional) { - iconClass = 'zmdi-swap'; - leftSymbol = 'ETH'; - rightSymbol = 'WETH'; - } - return ( - <div className="flex mx-auto" style={{ width: 85 }}> - <div style={{ paddingTop: 3 }}>{leftSymbol}</div> - <div className="px1"> - <i style={{ fontSize: 18 }} className={`zmdi ${iconClass}`} /> - </div> - <div style={{ paddingTop: 3 }}>{rightSymbol}</div> - </div> - ); - } - private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { - const rows = _.map( - configs.OUTDATED_WRAPPED_ETHERS, - (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { - const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; - if (_.isUndefined(outdatedWETHIfExists)) { - return null; // noop - } - const timestampMsRange = outdatedWETHIfExists.timestampMsRange; - let dateRange: string; - if (!_.isUndefined(timestampMsRange)) { - const startMoment = moment(timestampMsRange.startTimestampMs); - const endMoment = moment(timestampMsRange.endTimestampMs); - dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; - } else { - dateRange = '-'; - } - const outdatedEtherToken = { - ...etherToken, - address: outdatedWETHIfExists.address, - }; - const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; - const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; - const balanceInEthIfExists = isStateLoaded - ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( - PRECISION, - ) - : undefined; - const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( - this, - outdatedWETHIfExists.address, - ); - const etherscanUrl = utils.getEtherScanLinkIfExists( - outdatedWETHIfExists.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); - return ( - <TableRow key={`weth-${outdatedWETHIfExists.address}`}> - <TableRowColumn className="py1"> - {this._renderTokenLink(tokenLabel, etherscanUrl)} - </TableRowColumn> - <TableRowColumn> - {isStateLoaded ? ( - `${balanceInEthIfExists} WETH` - ) : ( - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - )} - </TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isDisabled={!isStateLoaded} - isOutdatedWrappedEther={true} - direction={Side.Receive} - ethToken={outdatedEtherToken} - ethTokenState={outdatedEtherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - onConversionSuccessful={onConversionSuccessful} - /> - </TableRowColumn> - </TableRow> - ); - }, - ); - return rows; - } - private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { - return ( - <span> - {_.isUndefined(etherscanUrl) ? ( - tokenLabel - ) : ( - <a href={etherscanUrl} target="_blank" style={{ textDecoration: 'none' }}> - {tokenLabel} - </a> - )} - </span> - ); - } - private _renderToken(name: string, address: string, imgPath: string) { - const tooltipId = `tooltip-${address}`; - return ( - <div className="flex"> - <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={imgPath} /> - <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> - <span data-tip={true} data-for={tooltipId}> - {name} - </span> - <ReactTooltip id={tooltipId}>{address}</ReactTooltip> - </div> - </div> - ); - } - private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { - this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: false, - }, - }); - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, - outdatedWETHAddress, - ); - this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: true, - }, - outdatedWETHStateByAddress: { - ...this.state.outdatedWETHStateByAddress, - [outdatedWETHAddress]: { - balance, - allowance, - }, - }, - }); - } - private async _fetchOutdatedWETHStateAsync() { - const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; - for (const address of outdatedWETHAddresses) { - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, - address, - ); - outdatedWETHStateByAddress[address] = { - balance, - allowance, - }; - outdatedWETHAddressToIsStateLoaded[address] = true; - } - this.setState({ - outdatedWETHStateByAddress, - outdatedWETHAddressToIsStateLoaded, - }); - } - private _getOutdatedWETHAddresses(): string[] { - const outdatedWETHAddresses = _.compact( - _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEtherByNetwork => { - const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; - if (_.isUndefined(outdatedWrappedEtherIfExists)) { - return undefined; - } - const address = outdatedWrappedEtherIfExists.address; - return address; - }), - ); - return outdatedWETHAddresses; - } + constructor(props: EthWrappersProps) { + super(props); + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + _.each(outdatedWETHAddresses, outdatedWETHAddress => { + outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; + outdatedWETHStateByAddress[outdatedWETHAddress] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + }; + }); + this.state = { + outdatedWETHAddressToIsStateLoaded, + outdatedWETHStateByAddress, + }; + } + public componentDidMount() { + window.scrollTo(0, 0); + // tslint:disable-next-line:no-floating-promises + this._fetchOutdatedWETHStateAsync(); + } + public render() { + const tokens = _.values(this.props.tokenByAddress); + const etherToken = _.find(tokens, { symbol: 'WETH' }); + const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; + const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); + const isBidirectional = true; + const etherscanUrl = utils.getEtherScanLinkIfExists( + etherToken.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); + return ( + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <div className="relative"> + <h3>ETH Wrapper</h3> + <div className="absolute" style={{ top: 0, right: 0 }}> + <a target="_blank" href={constants.URL_WETH_IO} style={{ color: colors.grey }}> + <div className="flex"> + <div>About Wrapped ETH</div> + <div className="pl1"> + <i className="zmdi zmdi-open-in-new" /> + </div> + </div> + </a> + </div> + </div> + <Divider /> + <div> + <div className="py2">Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.</div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>ETH Token</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + <TableRow key="ETH"> + <TableRowColumn className="py1"> + <div className="flex"> + <img + style={{ + width: ICON_DIMENSION, + height: ICON_DIMENSION, + }} + src={ETHER_ICON_PATH} + /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + ETH + </div> + </div> + </TableRowColumn> + <TableRowColumn> + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Deposit} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + <TableRow key="WETH"> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Receive} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + </TableBody> + </Table> + </div> + </div> + <div> + <h4>Outdated WETH</h4> + <Divider /> + <div className="pt2" style={{ lineHeight: 1.5 }}> + The{' '} + <a href="https://blog.0xproject.com/canonical-weth-a9aa7d0279dd" target="_blank"> + canonical WETH + </a>{' '} + contract is updated when necessary. Unwrap outdated WETH in order to
retrieve your ETH and move + it to the updated WETH token. + </div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>WETH Version</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(!isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + {this._renderOutdatedWeths(etherToken, etherTokenState)} + </TableBody> + </Table> + </div> + </div> + </div> + ); + } + private _renderActionColumnTitle(isBidirectional: boolean) { + let iconClass = 'zmdi-long-arrow-right'; + let leftSymbol = 'WETH'; + let rightSymbol = 'ETH'; + if (isBidirectional) { + iconClass = 'zmdi-swap'; + leftSymbol = 'ETH'; + rightSymbol = 'WETH'; + } + return ( + <div className="flex mx-auto" style={{ width: 85 }}> + <div style={{ paddingTop: 3 }}>{leftSymbol}</div> + <div className="px1"> + <i style={{ fontSize: 18 }} className={`zmdi ${iconClass}`} /> + </div> + <div style={{ paddingTop: 3 }}>{rightSymbol}</div> + </div> + ); + } + private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { + const rows = _.map( + configs.OUTDATED_WRAPPED_ETHERS, + (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { + const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; + if (_.isUndefined(outdatedWETHIfExists)) { + return null; // noop + } + const timestampMsRange = outdatedWETHIfExists.timestampMsRange; + let dateRange: string; + if (!_.isUndefined(timestampMsRange)) { + const startMoment = moment(timestampMsRange.startTimestampMs); + const endMoment = moment(timestampMsRange.endTimestampMs); + dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; + } else { + dateRange = '-'; + } + const outdatedEtherToken = { + ...etherToken, + address: outdatedWETHIfExists.address, + }; + const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; + const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; + const balanceInEthIfExists = isStateLoaded + ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( + PRECISION, + ) + : undefined; + const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( + this, + outdatedWETHIfExists.address, + ); + const etherscanUrl = utils.getEtherScanLinkIfExists( + outdatedWETHIfExists.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); + return ( + <TableRow key={`weth-${outdatedWETHIfExists.address}`}> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn> + {isStateLoaded ? ( + `${balanceInEthIfExists} WETH` + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isDisabled={!isStateLoaded} + isOutdatedWrappedEther={true} + direction={Side.Receive} + ethToken={outdatedEtherToken} + ethTokenState={outdatedEtherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + onConversionSuccessful={onConversionSuccessful} + /> + </TableRowColumn> + </TableRow> + ); + }, + ); + return rows; + } + private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { + return ( + <span> + {_.isUndefined(etherscanUrl) ? ( + tokenLabel + ) : ( + <a href={etherscanUrl} target="_blank" style={{ textDecoration: 'none' }}> + {tokenLabel} + </a> + )} + </span> + ); + } + private _renderToken(name: string, address: string, imgPath: string) { + const tooltipId = `tooltip-${address}`; + return ( + <div className="flex"> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={imgPath} /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + <span data-tip={true} data-for={tooltipId}> + {name} + </span> + <ReactTooltip id={tooltipId}>{address}</ReactTooltip> + </div> + </div> + ); + } + private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: false, + }, + }); + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + outdatedWETHAddress, + ); + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: true, + }, + outdatedWETHStateByAddress: { + ...this.state.outdatedWETHStateByAddress, + [outdatedWETHAddress]: { + balance, + allowance, + }, + }, + }); + } + private async _fetchOutdatedWETHStateAsync() { + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + for (const address of outdatedWETHAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + address, + ); + outdatedWETHStateByAddress[address] = { + balance, + allowance, + }; + outdatedWETHAddressToIsStateLoaded[address] = true; + } + this.setState({ + outdatedWETHStateByAddress, + outdatedWETHAddressToIsStateLoaded, + }); + } + private _getOutdatedWETHAddresses(): string[] { + const outdatedWETHAddresses = _.compact( + _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEtherByNetwork => { + const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; + if (_.isUndefined(outdatedWrappedEtherIfExists)) { + return undefined; + } + const address = outdatedWrappedEtherIfExists.address; + return address; + }), + ); + return outdatedWETHAddresses; + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 249ee419e..1a150e9ee 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -26,665 +26,665 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface FillOrderProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - orderFillAmount: BigNumber; - isOrderInUrl: boolean; - networkId: number; - userAddress: string; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - initialOrder: Order; - dispatcher: Dispatcher; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + orderFillAmount: BigNumber; + isOrderInUrl: boolean; + networkId: number; + userAddress: string; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + initialOrder: Order; + dispatcher: Dispatcher; } interface FillOrderState { - didOrderValidationRun: boolean; - areAllInvolvedTokensTracked: boolean; - globalErrMsg: string; - orderJSON: string; - orderJSONErrMsg: string; - parsedOrder: Order; - didFillOrderSucceed: boolean; - didCancelOrderSucceed: boolean; - unavailableTakerAmount: BigNumber; - isMakerTokenAddressInRegistry: boolean; - isTakerTokenAddressInRegistry: boolean; - isFillWarningDialogOpen: boolean; - isFilling: boolean; - isCancelling: boolean; - isConfirmingTokenTracking: boolean; - tokensToTrack: Token[]; + didOrderValidationRun: boolean; + areAllInvolvedTokensTracked: boolean; + globalErrMsg: string; + orderJSON: string; + orderJSONErrMsg: string; + parsedOrder: Order; + didFillOrderSucceed: boolean; + didCancelOrderSucceed: boolean; + unavailableTakerAmount: BigNumber; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; + isFillWarningDialogOpen: boolean; + isFilling: boolean; + isCancelling: boolean; + isConfirmingTokenTracking: boolean; + tokensToTrack: Token[]; } export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { - private _validator: SchemaValidator; - constructor(props: FillOrderProps) { - super(props); - this.state = { - globalErrMsg: '', - didOrderValidationRun: false, - areAllInvolvedTokensTracked: false, - didFillOrderSucceed: false, - didCancelOrderSucceed: false, - orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), - orderJSONErrMsg: '', - parsedOrder: this.props.initialOrder, - unavailableTakerAmount: new BigNumber(0), - isMakerTokenAddressInRegistry: false, - isTakerTokenAddressInRegistry: false, - isFillWarningDialogOpen: false, - isFilling: false, - isCancelling: false, - isConfirmingTokenTracking: false, - tokensToTrack: [], - }; - this._validator = new SchemaValidator(); - } - public componentWillMount() { - if (!_.isEmpty(this.state.orderJSON)) { - // tslint:disable-next-line:no-floating-promises - this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); - } - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> - <h3>Fill an order</h3> - <Divider /> - <div> - {!this.props.isOrderInUrl && ( - <div> - <div className="pt2 pb2">Paste an order JSON snippet below to begin</div> - <div className="pb2">Order JSON</div> - <FillOrderJSON - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} - /> - {this._renderOrderJsonNotices()} - </div> - )} - <div> - {!_.isUndefined(this.state.parsedOrder) && - this.state.didOrderValidationRun && - this.state.areAllInvolvedTokensTracked && - this._renderVisualOrder()} - </div> - {this.props.isOrderInUrl && ( - <div className="pt2"> - <Card - style={{ - boxShadow: 'none', - backgroundColor: 'none', - border: '1px solid #eceaea', - }} - > - <CardHeader title="Order JSON" actAsExpander={true} showExpandableButton={true} /> - <CardText expandable={true}> - <FillOrderJSON - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} - /> - </CardText> - </Card> - {this._renderOrderJsonNotices()} - </div> - )} - </div> - <FillWarningDialog - isOpen={this.state.isFillWarningDialogOpen} - onToggleDialog={this._onFillWarningClosed.bind(this)} - /> - <TrackTokenConfirmationDialog - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - dispatcher={this.props.dispatcher} - tokens={this.state.tokensToTrack} - isOpen={this.state.isConfirmingTokenTracking} - onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)} - /> - </div> - ); - } - private _renderOrderJsonNotices() { - return ( - <div> - {!_.isUndefined(this.props.initialOrder) && - !this.state.didOrderValidationRun && ( - <div className="pt2"> - <span className="pr1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - <span>Validating order...</span> - </div> - )} - {!_.isEmpty(this.state.orderJSONErrMsg) && ( - <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} /> - )} - </div> - ); - } - private _renderVisualOrder() { - const takerTokenAddress = this.state.parsedOrder.taker.token.address; - const takerToken = this.props.tokenByAddress[takerTokenAddress]; - const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); - const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); - const takerAssetToken = { - amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), - symbol: takerToken.symbol, - }; - const fillToken = this.props.tokenByAddress[takerToken.address]; - const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; - const makerTokenAddress = this.state.parsedOrder.maker.token.address; - const makerToken = this.props.tokenByAddress[makerTokenAddress]; - const makerAssetToken = { - amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), - symbol: makerToken.symbol, - }; - const fillAssetToken = { - amount: this.props.orderFillAmount, - symbol: takerToken.symbol, - }; - const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) - ? this.state.parsedOrder.taker.address - : this.props.userAddress; - const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); - const exchangeRate = orderMakerAmount.div(orderTakerAmount); + private _validator: SchemaValidator; + constructor(props: FillOrderProps) { + super(props); + this.state = { + globalErrMsg: '', + didOrderValidationRun: false, + areAllInvolvedTokensTracked: false, + didFillOrderSucceed: false, + didCancelOrderSucceed: false, + orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), + orderJSONErrMsg: '', + parsedOrder: this.props.initialOrder, + unavailableTakerAmount: new BigNumber(0), + isMakerTokenAddressInRegistry: false, + isTakerTokenAddressInRegistry: false, + isFillWarningDialogOpen: false, + isFilling: false, + isCancelling: false, + isConfirmingTokenTracking: false, + tokensToTrack: [], + }; + this._validator = new SchemaValidator(); + } + public componentWillMount() { + if (!_.isEmpty(this.state.orderJSON)) { + // tslint:disable-next-line:no-floating-promises + this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <h3>Fill an order</h3> + <Divider /> + <div> + {!this.props.isOrderInUrl && ( + <div> + <div className="pt2 pb2">Paste an order JSON snippet below to begin</div> + <div className="pb2">Order JSON</div> + <FillOrderJSON + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + orderJSON={this.state.orderJSON} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} + /> + {this._renderOrderJsonNotices()} + </div> + )} + <div> + {!_.isUndefined(this.state.parsedOrder) && + this.state.didOrderValidationRun && + this.state.areAllInvolvedTokensTracked && + this._renderVisualOrder()} + </div> + {this.props.isOrderInUrl && ( + <div className="pt2"> + <Card + style={{ + boxShadow: 'none', + backgroundColor: 'none', + border: '1px solid #eceaea', + }} + > + <CardHeader title="Order JSON" actAsExpander={true} showExpandableButton={true} /> + <CardText expandable={true}> + <FillOrderJSON + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + orderJSON={this.state.orderJSON} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} + /> + </CardText> + </Card> + {this._renderOrderJsonNotices()} + </div> + )} + </div> + <FillWarningDialog + isOpen={this.state.isFillWarningDialogOpen} + onToggleDialog={this._onFillWarningClosed.bind(this)} + /> + <TrackTokenConfirmationDialog + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + dispatcher={this.props.dispatcher} + tokens={this.state.tokensToTrack} + isOpen={this.state.isConfirmingTokenTracking} + onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)} + /> + </div> + ); + } + private _renderOrderJsonNotices() { + return ( + <div> + {!_.isUndefined(this.props.initialOrder) && + !this.state.didOrderValidationRun && ( + <div className="pt2"> + <span className="pr1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + <span>Validating order...</span> + </div> + )} + {!_.isEmpty(this.state.orderJSONErrMsg) && ( + <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} /> + )} + </div> + ); + } + private _renderVisualOrder() { + const takerTokenAddress = this.state.parsedOrder.taker.token.address; + const takerToken = this.props.tokenByAddress[takerTokenAddress]; + const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); + const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); + const takerAssetToken = { + amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), + symbol: takerToken.symbol, + }; + const fillToken = this.props.tokenByAddress[takerToken.address]; + const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; + const makerTokenAddress = this.state.parsedOrder.maker.token.address; + const makerToken = this.props.tokenByAddress[makerTokenAddress]; + const makerAssetToken = { + amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), + symbol: makerToken.symbol, + }; + const fillAssetToken = { + amount: this.props.orderFillAmount, + symbol: takerToken.symbol, + }; + const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) + ? this.state.parsedOrder.taker.address + : this.props.userAddress; + const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); + const exchangeRate = orderMakerAmount.div(orderTakerAmount); - let orderReceiveAmount = 0; - if (!_.isUndefined(this.props.orderFillAmount)) { - const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); - orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); - } - const isUserMaker = - !_.isUndefined(this.state.parsedOrder) && this.state.parsedOrder.maker.address === this.props.userAddress; - const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); - return ( - <div className="pt3 pb1"> - <div className="clearfix pb2" style={{ width: '100%' }}> - <div className="inline left">Order details</div> - <div className="inline right" style={{ minWidth: 208 }}> - <div className="col col-4 pl2" style={{ color: colors.grey }}> - Maker: - </div> - <div className="col col-2 pr1"> - <Identicon address={this.state.parsedOrder.maker.address} diameter={23} /> - </div> - <div className="col col-6"> - <EthereumAddress - address={this.state.parsedOrder.maker.address} - networkId={this.props.networkId} - /> - </div> - </div> - </div> - <div className="lg-px4 md-px4 sm-px0"> - <div className="lg-px4 md-px4 sm-px1 pt1"> - <VisualOrder - orderTakerAddress={orderTaker} - orderMakerAddress={this.state.parsedOrder.maker.address} - makerAssetToken={makerAssetToken} - takerAssetToken={takerAssetToken} - tokenByAddress={this.props.tokenByAddress} - makerToken={makerToken} - takerToken={takerToken} - networkId={this.props.networkId} - isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry} - isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry} - /> - <div className="center pt3 pb2">Expires: {expiryDate} UTC</div> - </div> - </div> - {!isUserMaker && ( - <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> - <TokenAmountInput - label="Fill amount" - onChange={this._onFillAmountChange.bind(this)} - shouldShowIncompleteErrs={false} - token={fillToken} - tokenState={fillTokenState} - amount={fillAssetToken.amount} - shouldCheckBalance={true} - shouldCheckAllowance={true} - /> - <div - className="absolute sm-hide xs-hide" - style={{ - color: colors.grey400, - right: -247, - top: 39, - width: 242, - }} - > - = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} - </div> - </div> - )} - <div> - {isUserMaker ? ( - <div> - <RaisedButton - style={{ width: '100%' }} - disabled={this.state.isCancelling} - label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'} - onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)} - /> - {this.state.didCancelOrderSucceed && ( - <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} /> - )} - </div> - ) : ( - <div> - <RaisedButton - style={{ width: '100%' }} - disabled={this.state.isFilling} - label={this.state.isFilling ? 'Filling order...' : 'Fill order'} - onClick={this._onFillOrderClick.bind(this)} - /> - {!_.isEmpty(this.state.globalErrMsg) && ( - <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - )} - {this.state.didFillOrderSucceed && ( - <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} /> - )} - </div> - )} - </div> - </div> - ); - } - private _renderFillSuccessMsg() { - return ( - <div> - Order successfully filled. See the trade details in your{' '} - <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}> - trade history - </Link> - </div> - ); - } - private _renderCancelSuccessMsg() { - return <div>Order successfully cancelled.</div>; - } - private _onFillOrderClick() { - if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { - this.setState({ - isFillWarningDialogOpen: true, - }); - } else { - // tslint:disable-next-line:no-floating-promises - this._onFillOrderClickFireAndForgetAsync(); - } - } - private _onFillWarningClosed(didUserCancel: boolean) { - this.setState({ - isFillWarningDialogOpen: false, - }); - if (!didUserCancel) { - // tslint:disable-next-line:no-floating-promises - this._onFillOrderClickFireAndForgetAsync(); - } - } - private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { - this.props.dispatcher.updateOrderFillAmount(amount); - } - private _onFillOrderJSONChanged(event: any) { - const orderJSON = event.target.value; - this.setState({ - didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), - didFillOrderSucceed: false, - }); - // tslint:disable-next-line:no-floating-promises - this._validateFillOrderFireAndForgetAsync(orderJSON); - } - private async _checkForUntrackedTokensAndAskToAdd() { - if (!_.isEmpty(this.state.orderJSONErrMsg)) { - return; - } + let orderReceiveAmount = 0; + if (!_.isUndefined(this.props.orderFillAmount)) { + const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); + orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); + } + const isUserMaker = + !_.isUndefined(this.state.parsedOrder) && this.state.parsedOrder.maker.address === this.props.userAddress; + const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); + return ( + <div className="pt3 pb1"> + <div className="clearfix pb2" style={{ width: '100%' }}> + <div className="inline left">Order details</div> + <div className="inline right" style={{ minWidth: 208 }}> + <div className="col col-4 pl2" style={{ color: colors.grey }}> + Maker: + </div> + <div className="col col-2 pr1"> + <Identicon address={this.state.parsedOrder.maker.address} diameter={23} /> + </div> + <div className="col col-6"> + <EthereumAddress + address={this.state.parsedOrder.maker.address} + networkId={this.props.networkId} + /> + </div> + </div> + </div> + <div className="lg-px4 md-px4 sm-px0"> + <div className="lg-px4 md-px4 sm-px1 pt1"> + <VisualOrder + orderTakerAddress={orderTaker} + orderMakerAddress={this.state.parsedOrder.maker.address} + makerAssetToken={makerAssetToken} + takerAssetToken={takerAssetToken} + tokenByAddress={this.props.tokenByAddress} + makerToken={makerToken} + takerToken={takerToken} + networkId={this.props.networkId} + isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry} + isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry} + /> + <div className="center pt3 pb2">Expires: {expiryDate} UTC</div> + </div> + </div> + {!isUserMaker && ( + <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> + <TokenAmountInput + label="Fill amount" + onChange={this._onFillAmountChange.bind(this)} + shouldShowIncompleteErrs={false} + token={fillToken} + tokenState={fillTokenState} + amount={fillAssetToken.amount} + shouldCheckBalance={true} + shouldCheckAllowance={true} + /> + <div + className="absolute sm-hide xs-hide" + style={{ + color: colors.grey400, + right: -247, + top: 39, + width: 242, + }} + > + = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} + </div> + </div> + )} + <div> + {isUserMaker ? ( + <div> + <RaisedButton + style={{ width: '100%' }} + disabled={this.state.isCancelling} + label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'} + onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)} + /> + {this.state.didCancelOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} /> + )} + </div> + ) : ( + <div> + <RaisedButton + style={{ width: '100%' }} + disabled={this.state.isFilling} + label={this.state.isFilling ? 'Filling order...' : 'Fill order'} + onClick={this._onFillOrderClick.bind(this)} + /> + {!_.isEmpty(this.state.globalErrMsg) && ( + <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> + )} + {this.state.didFillOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} /> + )} + </div> + )} + </div> + </div> + ); + } + private _renderFillSuccessMsg() { + return ( + <div> + Order successfully filled. See the trade details in your{' '} + <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}> + trade history + </Link> + </div> + ); + } + private _renderCancelSuccessMsg() { + return <div>Order successfully cancelled.</div>; + } + private _onFillOrderClick() { + if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { + this.setState({ + isFillWarningDialogOpen: true, + }); + } else { + // tslint:disable-next-line:no-floating-promises + this._onFillOrderClickFireAndForgetAsync(); + } + } + private _onFillWarningClosed(didUserCancel: boolean) { + this.setState({ + isFillWarningDialogOpen: false, + }); + if (!didUserCancel) { + // tslint:disable-next-line:no-floating-promises + this._onFillOrderClickFireAndForgetAsync(); + } + } + private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateOrderFillAmount(amount); + } + private _onFillOrderJSONChanged(event: any) { + const orderJSON = event.target.value; + this.setState({ + didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), + didFillOrderSucceed: false, + }); + // tslint:disable-next-line:no-floating-promises + this._validateFillOrderFireAndForgetAsync(orderJSON); + } + private async _checkForUntrackedTokensAndAskToAdd() { + if (!_.isEmpty(this.state.orderJSONErrMsg)) { + return; + } - const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; - const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; + const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; + const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; - const tokensToTrack = []; - const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); - const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; - if (isUnseenMakerToken) { - tokensToTrack.push({ - ...this.state.parsedOrder.maker.token, - iconUrl: undefined, - isTracked: false, - isRegistered: false, - }); - } else if (!isMakerTokenTracked) { - tokensToTrack.push(makerTokenIfExists); - } - const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); - const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; - if (isUnseenTakerToken) { - tokensToTrack.push({ - ...this.state.parsedOrder.taker.token, - iconUrl: undefined, - isTracked: false, - isRegistered: false, - }); - } else if (!isTakerTokenTracked) { - tokensToTrack.push(takerTokenIfExists); - } - if (!_.isEmpty(tokensToTrack)) { - this.setState({ - isConfirmingTokenTracking: true, - tokensToTrack, - }); - } else { - this.setState({ - areAllInvolvedTokensTracked: true, - }); - } - } - private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { - let orderJSONErrMsg = ''; - let parsedOrder: Order; - try { - const order = JSON.parse(orderJSON); - const validationResult = this._validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - orderJSONErrMsg = 'Submitted order JSON is not a valid order'; - utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); - return; - } - parsedOrder = order; + const tokensToTrack = []; + const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); + const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; + if (isUnseenMakerToken) { + tokensToTrack.push({ + ...this.state.parsedOrder.maker.token, + iconUrl: undefined, + isTracked: false, + isRegistered: false, + }); + } else if (!isMakerTokenTracked) { + tokensToTrack.push(makerTokenIfExists); + } + const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); + const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; + if (isUnseenTakerToken) { + tokensToTrack.push({ + ...this.state.parsedOrder.taker.token, + iconUrl: undefined, + isTracked: false, + isRegistered: false, + }); + } else if (!isTakerTokenTracked) { + tokensToTrack.push(takerTokenIfExists); + } + if (!_.isEmpty(tokensToTrack)) { + this.setState({ + isConfirmingTokenTracking: true, + tokensToTrack, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + } + private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { + let orderJSONErrMsg = ''; + let parsedOrder: Order; + try { + const order = JSON.parse(orderJSON); + const validationResult = this._validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + orderJSONErrMsg = 'Submitted order JSON is not a valid order'; + utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); + return; + } + parsedOrder = order; - const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); - const makerAmount = new BigNumber(parsedOrder.maker.amount); - const takerAmount = new BigNumber(parsedOrder.taker.amount); - const expiration = new BigNumber(parsedOrder.expiration); - const salt = new BigNumber(parsedOrder.salt); - const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); - const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + const makerAmount = new BigNumber(parsedOrder.maker.amount); + const takerAmount = new BigNumber(parsedOrder.taker.amount); + const expiration = new BigNumber(parsedOrder.expiration); + const salt = new BigNumber(parsedOrder.salt); + const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); + const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); - const zeroExOrder: ZeroExOrder = { - exchangeContractAddress: parsedOrder.exchangeContract, - expirationUnixTimestampSec: expiration, - feeRecipient: parsedOrder.feeRecipient, - maker: parsedOrder.maker.address, - makerFee: parsedMakerFee, - makerTokenAddress: parsedOrder.maker.token.address, - makerTokenAmount: makerAmount, - salt, - taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, - takerFee: parsedTakerFee, - takerTokenAddress: parsedOrder.taker.token.address, - takerTokenAmount: takerAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + const zeroExOrder: ZeroExOrder = { + exchangeContractAddress: parsedOrder.exchangeContract, + expirationUnixTimestampSec: expiration, + feeRecipient: parsedOrder.feeRecipient, + maker: parsedOrder.maker.address, + makerFee: parsedMakerFee, + makerTokenAddress: parsedOrder.maker.token.address, + makerTokenAmount: makerAmount, + salt, + taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, + takerFee: parsedTakerFee, + takerTokenAddress: parsedOrder.taker.token.address, + takerTokenAmount: takerAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); - const signature = parsedOrder.signature; - const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); - if (this.props.networkId !== parsedOrder.networkId) { - orderJSONErrMsg = `This order was made on another Ethereum network + const signature = parsedOrder.signature; + const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); + if (this.props.networkId !== parsedOrder.networkId) { + orderJSONErrMsg = `This order was made on another Ethereum network (id: ${parsedOrder.networkId}). Connect to this network to fill.`; - parsedOrder = undefined; - } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { - orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; - parsedOrder = undefined; - } else if (orderHash !== signature.hash) { - orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; - parsedOrder = undefined; - } else if (!isValidSignature) { - orderJSONErrMsg = 'Order signature is invalid'; - parsedOrder = undefined; - } else { - // Update user supplied order cache so that if they navigate away from fill view - // e.g to set a token allowance, when they come back, the fill order persists - this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); - } - } catch (err) { - utils.consoleLog(`Validate order err: ${err}`); - if (!_.isEmpty(orderJSON)) { - orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; - } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - }); - return; - } + parsedOrder = undefined; + } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { + orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; + parsedOrder = undefined; + } else if (orderHash !== signature.hash) { + orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; + parsedOrder = undefined; + } else if (!isValidSignature) { + orderJSONErrMsg = 'Order signature is invalid'; + parsedOrder = undefined; + } else { + // Update user supplied order cache so that if they navigate away from fill view + // e.g to set a token allowance, when they come back, the fill order persists + this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); + } + } catch (err) { + utils.consoleLog(`Validate order err: ${err}`); + if (!_.isEmpty(orderJSON)) { + orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; + } + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + return; + } - let unavailableTakerAmount = new BigNumber(0); - if (!_.isEmpty(orderJSONErrMsg)) { - // Clear cache entry if user updates orderJSON to invalid entry - this.props.dispatcher.updateUserSuppliedOrderCache(undefined); - } else { - const orderHash = parsedOrder.signature.hash; - unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); - const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( - parsedOrder.maker.token.address, - ); - const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( - parsedOrder.taker.token.address, - ); - this.setState({ - isMakerTokenAddressInRegistry, - isTakerTokenAddressInRegistry, - }); - } + let unavailableTakerAmount = new BigNumber(0); + if (!_.isEmpty(orderJSONErrMsg)) { + // Clear cache entry if user updates orderJSON to invalid entry + this.props.dispatcher.updateUserSuppliedOrderCache(undefined); + } else { + const orderHash = parsedOrder.signature.hash; + unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.maker.token.address, + ); + const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.taker.token.address, + ); + this.setState({ + isMakerTokenAddressInRegistry, + isTakerTokenAddressInRegistry, + }); + } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - unavailableTakerAmount, - }); + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + unavailableTakerAmount, + }); - await this._checkForUntrackedTokensAndAskToAdd(); - } - private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { - if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + await this._checkForUntrackedTokensAndAskToAdd(); + } + private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isFilling: true, - didFillOrderSucceed: false, - }); + this.setState({ + isFilling: true, + didFillOrderSucceed: false, + }); - const parsedOrder = this.state.parsedOrder; - const takerFillAmount = this.props.orderFillAmount; + const parsedOrder = this.state.parsedOrder; + const takerFillAmount = this.props.orderFillAmount; - if (_.isUndefined(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - isFilling: false, - }); - return; - } - let globalErrMsg = ''; + if (_.isUndefined(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; - if (_.isUndefined(takerFillAmount)) { - globalErrMsg = 'You must specify a fill amount'; - } + if (_.isUndefined(takerFillAmount)) { + globalErrMsg = 'You must specify a fill amount'; + } - const signedOrder = this.props.blockchain.portalOrderToSignedOrder( - parsedOrder.maker.address, - parsedOrder.taker.address, - parsedOrder.maker.token.address, - parsedOrder.taker.token.address, - new BigNumber(parsedOrder.maker.amount), - new BigNumber(parsedOrder.taker.amount), - new BigNumber(parsedOrder.maker.feeAmount), - new BigNumber(parsedOrder.taker.feeAmount), - new BigNumber(this.state.parsedOrder.expiration), - parsedOrder.feeRecipient, - parsedOrder.signature, - new BigNumber(parsedOrder.salt), - ); - if (_.isEmpty(globalErrMsg)) { - try { - await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( - signedOrder, - takerFillAmount, - this.props.userAddress, - ); - } catch (err) { - globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); - } - } - if (!_.isEmpty(globalErrMsg)) { - this.setState({ - isFilling: false, - globalErrMsg, - }); - return; - } - try { - const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( - signedOrder, - this.props.orderFillAmount, - ); - // After fill completes, let's update the token balances - const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; - const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; - const tokens = [makerToken, takerToken]; - await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.setState({ - isFilling: false, - didFillOrderSucceed: true, - globalErrMsg: '', - unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), - }); - return; - } catch (err) { - this.setState({ - isFilling: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { - return; - } - globalErrMsg = 'Failed to fill order, please refresh and try again'; - utils.consoleLog(`${err}`); - this.setState({ - globalErrMsg, - }); - await errorReporter.reportAsync(err); - return; - } - } - private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> { - if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + new BigNumber(parsedOrder.taker.amount), + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + if (_.isEmpty(globalErrMsg)) { + try { + await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( + signedOrder, + takerFillAmount, + this.props.userAddress, + ); + } catch (err) { + globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); + } + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isFilling: false, + globalErrMsg, + }); + return; + } + try { + const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( + signedOrder, + this.props.orderFillAmount, + ); + // After fill completes, let's update the token balances + const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; + const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; + const tokens = [makerToken, takerToken]; + await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.setState({ + isFilling: false, + didFillOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), + }); + return; + } catch (err) { + this.setState({ + isFilling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to fill order, please refresh and try again'; + utils.consoleLog(`${err}`); + this.setState({ + globalErrMsg, + }); + await errorReporter.reportAsync(err); + return; + } + } + private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isCancelling: true, - didCancelOrderSucceed: false, - }); + this.setState({ + isCancelling: true, + didCancelOrderSucceed: false, + }); - const parsedOrder = this.state.parsedOrder; - const orderHash = parsedOrder.signature.hash; - const takerAddress = this.props.userAddress; + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const takerAddress = this.props.userAddress; - if (_.isUndefined(takerAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - isFilling: false, - }); - return; - } - let globalErrMsg = ''; + if (_.isUndefined(takerAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; - const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); + const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); - const signedOrder = this.props.blockchain.portalOrderToSignedOrder( - parsedOrder.maker.address, - parsedOrder.taker.address, - parsedOrder.maker.token.address, - parsedOrder.taker.token.address, - new BigNumber(parsedOrder.maker.amount), - takerTokenAmount, - new BigNumber(parsedOrder.maker.feeAmount), - new BigNumber(parsedOrder.taker.feeAmount), - new BigNumber(this.state.parsedOrder.expiration), - parsedOrder.feeRecipient, - parsedOrder.signature, - new BigNumber(parsedOrder.salt), - ); - const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); - const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); - try { - await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount); - } catch (err) { - globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); - } - if (!_.isEmpty(globalErrMsg)) { - this.setState({ - isCancelling: false, - globalErrMsg, - }); - return; - } - try { - await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); - this.setState({ - isCancelling: false, - didCancelOrderSucceed: true, - globalErrMsg: '', - unavailableTakerAmount: takerTokenAmount, - }); - return; - } catch (err) { - this.setState({ - isCancelling: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { - return; - } - globalErrMsg = 'Failed to cancel order, please refresh and try again'; - utils.consoleLog(`${err}`); - this.setState({ - globalErrMsg, - }); - await errorReporter.reportAsync(err); - return; - } - } - private _formatCurrencyAmount(amount: BigNumber, decimals: number): number { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; - return roundedUnitAmount; - } - private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { - if (!didConfirmTokenTracking) { - this.setState({ - orderJSON: '', - orderJSONErrMsg: '', - parsedOrder: undefined, - }); - } else { - this.setState({ - areAllInvolvedTokensTracked: true, - }); - } - this.setState({ - isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, - tokensToTrack: [], - }); - } + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + takerTokenAmount, + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); + try { + await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount); + } catch (err) { + globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isCancelling: false, + globalErrMsg, + }); + return; + } + try { + await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); + this.setState({ + isCancelling: false, + didCancelOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: takerTokenAmount, + }); + return; + } catch (err) { + this.setState({ + isCancelling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to cancel order, please refresh and try again'; + utils.consoleLog(`${err}`); + this.setState({ + globalErrMsg, + }); + await errorReporter.reportAsync(err); + return; + } + } + private _formatCurrencyAmount(amount: BigNumber, decimals: number): number { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; + return roundedUnitAmount; + } + private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + if (!didConfirmTokenTracking) { + this.setState({ + orderJSON: '', + orderJSONErrMsg: '', + parsedOrder: undefined, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + this.setState({ + isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, + tokensToTrack: [], + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx index 3446b8a39..f8e43481a 100644 --- a/packages/website/ts/components/fill_order_json.tsx +++ b/packages/website/ts/components/fill_order_json.tsx @@ -10,71 +10,71 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface FillOrderJSONProps { - blockchain: Blockchain; - tokenByAddress: TokenByAddress; - networkId: number; - orderJSON: string; - onFillOrderJSONChanged: (event: any) => void; + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + networkId: number; + orderJSON: string; + onFillOrderJSONChanged: (event: any) => void; } interface FillOrderJSONState {} export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrderJSONState> { - public render() { - const tokenAddresses = _.keys(this.props.tokenByAddress); - const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); - const hintSideToAssetToken = { - [Side.Deposit]: { - amount: new BigNumber(35), - address: tokenAddresses[0], - }, - [Side.Receive]: { - amount: new BigNumber(89), - address: tokenAddresses[1], - }, - }; - const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); - const hintSignatureData = { - hash: '0xf965a9978a0381ab58f5a2408ad967c...', - r: '0xf01103f759e2289a28593eaf22e5820032...', - s: '937862111edcba395f8a9e0cc1b2c5e12320...', - v: 27, - }; - const hintSalt = ZeroEx.generatePseudoRandomSalt(); - const feeRecipient = constants.NULL_ADDRESS; - const hintOrder = utils.generateOrder( - this.props.networkId, - exchangeContract, - hintSideToAssetToken, - hintOrderExpiryTimestamp, - '', - '', - constants.MAKER_FEE, - constants.TAKER_FEE, - feeRecipient, - hintSignatureData, - this.props.tokenByAddress, - hintSalt, - ); - const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; - return ( - <div> - <Paper className="p1 overflow-hidden" style={{ height: 164 }}> - <TextField - id="orderJSON" - hintStyle={{ bottom: 0, top: 0 }} - fullWidth={true} - value={this.props.orderJSON} - onChange={this.props.onFillOrderJSONChanged.bind(this)} - hintText={hintOrderJSON} - multiLine={true} - rows={6} - rowsMax={6} - underlineStyle={{ display: 'none' }} - textareaStyle={{ marginTop: 0 }} - /> - </Paper> - </div> - ); - } + public render() { + const tokenAddresses = _.keys(this.props.tokenByAddress); + const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); + const hintSideToAssetToken = { + [Side.Deposit]: { + amount: new BigNumber(35), + address: tokenAddresses[0], + }, + [Side.Receive]: { + amount: new BigNumber(89), + address: tokenAddresses[1], + }, + }; + const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const hintSignatureData = { + hash: '0xf965a9978a0381ab58f5a2408ad967c...', + r: '0xf01103f759e2289a28593eaf22e5820032...', + s: '937862111edcba395f8a9e0cc1b2c5e12320...', + v: 27, + }; + const hintSalt = ZeroEx.generatePseudoRandomSalt(); + const feeRecipient = constants.NULL_ADDRESS; + const hintOrder = utils.generateOrder( + this.props.networkId, + exchangeContract, + hintSideToAssetToken, + hintOrderExpiryTimestamp, + '', + '', + constants.MAKER_FEE, + constants.TAKER_FEE, + feeRecipient, + hintSignatureData, + this.props.tokenByAddress, + hintSalt, + ); + const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; + return ( + <div> + <Paper className="p1 overflow-hidden" style={{ height: 164 }}> + <TextField + id="orderJSON" + hintStyle={{ bottom: 0, top: 0 }} + fullWidth={true} + value={this.props.orderJSON} + onChange={this.props.onFillOrderJSONChanged.bind(this)} + hintText={hintOrderJSON} + multiLine={true} + rows={6} + rowsMax={6} + underlineStyle={{ display: 'none' }} + textareaStyle={{ marginTop: 0 }} + /> + </Paper> + </div> + ); + } } diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx index 40b04723d..165d21b34 100644 --- a/packages/website/ts/components/fill_warning_dialog.tsx +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -4,42 +4,42 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface FillWarningDialogProps { - isOpen: boolean; - onToggleDialog: (didUserCancel: boolean) => void; + isOpen: boolean; + onToggleDialog: (didUserCancel: boolean) => void; } export function FillWarningDialog(props: FillWarningDialogProps) { - const didCancel = true; - return ( - <Dialog - title="Warning" - titleStyle={{ fontWeight: 100, color: colors.red500 }} - actions={[ - <FlatButton - key="fillWarningCancel" - label="Cancel" - onTouchTap={props.onToggleDialog.bind(this, didCancel)} - />, - <FlatButton - key="fillWarningContinue" - label="Fill Order" - onTouchTap={props.onToggleDialog.bind(this, !didCancel)} - />, - ]} - open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - At least one of the tokens in this order was not found in the token registry smart contract and may - be counterfeit. It is your responsibility to verify the token addresses on Etherscan ( - <a href="https://0xproject.com/wiki#Verifying-Custom-Tokens" target="_blank"> - See this how-to guide - </a>) before filling an order. <b>This action may result in the loss of funds</b>. - </div> - </div> - </Dialog> - ); + const didCancel = true; + return ( + <Dialog + title="Warning" + titleStyle={{ fontWeight: 100, color: colors.red500 }} + actions={[ + <FlatButton + key="fillWarningCancel" + label="Cancel" + onTouchTap={props.onToggleDialog.bind(this, didCancel)} + />, + <FlatButton + key="fillWarningContinue" + label="Fill Order" + onTouchTap={props.onToggleDialog.bind(this, !didCancel)} + />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + At least one of the tokens in this order was not found in the token registry smart contract and may + be counterfeit. It is your responsibility to verify the token addresses on Etherscan ( + <a href="https://0xproject.com/wiki#Verifying-Custom-Tokens" target="_blank"> + See this how-to guide + </a>) before filling an order. <b>This action may result in the loss of funds</b>. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx index 07fca1ddc..18f371624 100644 --- a/packages/website/ts/components/flash_messages/token_send_completed.tsx +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -7,28 +7,28 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TokenSendCompletedProps { - etherScanLinkIfExists?: string; - token: Token; - toAddress: string; - amountInBaseUnits: BigNumber; + etherScanLinkIfExists?: string; + token: Token; + toAddress: string; + amountInBaseUnits: BigNumber; } interface TokenSendCompletedState {} export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> { - public render() { - const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( - <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> - Verify on Etherscan - </a> - ); - const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); - const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); - return ( - <div> - {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} - {etherScanLink} - </div> - ); - } + public render() { + const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> + Verify on Etherscan + </a> + ); + const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); + const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); + return ( + <div> + {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} + {etherScanLink} + </div> + ); + } } diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx index 14464b923..862e382dd 100644 --- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -3,24 +3,24 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface TransactionSubmittedProps { - etherScanLinkIfExists?: string; + etherScanLinkIfExists?: string; } interface TransactionSubmittedState {} export class TransactionSubmitted extends React.Component<TransactionSubmittedProps, TransactionSubmittedState> { - public render() { - if (_.isUndefined(this.props.etherScanLinkIfExists)) { - return <div>Transaction submitted to the network</div>; - } else { - return ( - <div> - Transaction submitted to the network:{' '} - <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> - Verify on Etherscan - </a> - </div> - ); - } - } + public render() { + if (_.isUndefined(this.props.etherScanLinkIfExists)) { + return <div>Transaction submitted to the network</div>; + } else { + return ( + <div> + Transaction submitted to the network:{' '} + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> + Verify on Etherscan + </a> + </div> + ); + } + } } diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index 3a342591c..a0f1a0c96 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -6,106 +6,106 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface MenuItemsBySection { - [sectionName: string]: FooterMenuItem[]; + [sectionName: string]: FooterMenuItem[]; } interface FooterMenuItem { - title: string; - path?: string; - isExternal?: boolean; + title: string; + path?: string; + isExternal?: boolean; } enum Sections { - Documentation = 'Documentation', - Community = 'Community', - Organization = 'Organization', + Documentation = 'Documentation', + Community = 'Community', + Organization = 'Organization', } const ICON_DIMENSION = 16; const menuItemsBySection: MenuItemsBySection = { - Documentation: [ - { - title: '0x.js', - path: WebsitePaths.ZeroExJs, - }, - { - title: '0x Smart Contracts', - path: WebsitePaths.SmartContracts, - }, - { - title: '0x Connect', - path: WebsitePaths.Connect, - }, - { - title: 'Whitepaper', - path: WebsitePaths.Whitepaper, - isExternal: true, - }, - { - title: 'Wiki', - path: WebsitePaths.Wiki, - }, - { - title: 'FAQ', - path: WebsitePaths.FAQ, - }, - ], - Community: [ - { - title: 'Rocket.chat', - isExternal: true, - path: constants.URL_ZEROEX_CHAT, - }, - { - title: 'Blog', - isExternal: true, - path: constants.URL_BLOG, - }, - { - title: 'Twitter', - isExternal: true, - path: constants.URL_TWITTER, - }, - { - title: 'Reddit', - isExternal: true, - path: constants.URL_REDDIT, - }, - { - title: 'Forum', - isExternal: true, - path: constants.URL_DISCOURSE_FORUM, - }, - ], - Organization: [ - { - title: 'About', - isExternal: false, - path: WebsitePaths.About, - }, - { - title: 'Careers', - isExternal: true, - path: constants.URL_ANGELLIST, - }, - { - title: 'Contact', - isExternal: true, - path: 'mailto:team@0xproject.com', - }, - ], + Documentation: [ + { + title: '0x.js', + path: WebsitePaths.ZeroExJs, + }, + { + title: '0x Smart Contracts', + path: WebsitePaths.SmartContracts, + }, + { + title: '0x Connect', + path: WebsitePaths.Connect, + }, + { + title: 'Whitepaper', + path: WebsitePaths.Whitepaper, + isExternal: true, + }, + { + title: 'Wiki', + path: WebsitePaths.Wiki, + }, + { + title: 'FAQ', + path: WebsitePaths.FAQ, + }, + ], + Community: [ + { + title: 'Rocket.chat', + isExternal: true, + path: constants.URL_ZEROEX_CHAT, + }, + { + title: 'Blog', + isExternal: true, + path: constants.URL_BLOG, + }, + { + title: 'Twitter', + isExternal: true, + path: constants.URL_TWITTER, + }, + { + title: 'Reddit', + isExternal: true, + path: constants.URL_REDDIT, + }, + { + title: 'Forum', + isExternal: true, + path: constants.URL_DISCOURSE_FORUM, + }, + ], + Organization: [ + { + title: 'About', + isExternal: false, + path: WebsitePaths.About, + }, + { + title: 'Careers', + isExternal: true, + path: constants.URL_ANGELLIST, + }, + { + title: 'Contact', + isExternal: true, + path: 'mailto:team@0xproject.com', + }, + ], }; const linkStyle = { - color: colors.white, - cursor: 'pointer', + color: colors.white, + cursor: 'pointer', }; const titleToIcon: { [title: string]: string } = { - 'Rocket.chat': 'rocketchat.png', - Blog: 'medium.png', - Twitter: 'twitter.png', - Reddit: 'reddit.png', - Forum: 'discourse.png', + 'Rocket.chat': 'rocketchat.png', + Blog: 'medium.png', + Twitter: 'twitter.png', + Reddit: 'reddit.png', + Forum: 'discourse.png', }; export interface FooterProps {} @@ -113,100 +113,100 @@ export interface FooterProps {} interface FooterState {} export class Footer extends React.Component<FooterProps, FooterState> { - public render() { - return ( - <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> - <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> - <div className="col lg-col-4 md-col-4 col-12 left"> - <div className="sm-mx-auto" style={{ width: 148 }}> - <div> - <img src="/images/protocol_logo_white.png" height="30" /> - </div> - <div - style={{ - fontSize: 11, - color: colors.grey, - paddingLeft: 37, - paddingTop: 2, - }} - > - © ZeroEx, Intl. - </div> - </div> - </div> - <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> - <div className="col lg-col-4 md-col-4 col-12"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Documentation)} - {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} - </div> - </div> - <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Community)} - {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} - </div> - </div> - <div className="col lg-col-4 md-col-4 col-12"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Organization)} - {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderIcon(fileName: string) { - return ( - <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> - <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> - </div> - ); - } - private _renderMenuItem(item: FooterMenuItem) { - const iconIfExists = titleToIcon[item.title]; - return ( - <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> - {item.isExternal ? ( - <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> - {!_.isUndefined(iconIfExists) ? ( - <div className="sm-mx-auto" style={{ width: 65 }}> - <div className="flex"> - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - <div>{item.title}</div> - </div> - </div> - ) : ( - item.title - )} - </a> - ) : ( - <Link to={item.path} style={linkStyle} className="text-decoration-none"> - <div> - {!_.isUndefined(iconIfExists) && ( - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - )} - {item.title} - </div> - </Link> - )} - </div> - ); - } - private _renderHeader(title: string) { - const headerStyle = { - textTransform: 'uppercase', - color: colors.grey400, - letterSpacing: 2, - fontFamily: 'Roboto Mono', - fontSize: 13, - }; - return ( - <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> - {title} - </div> - ); - } + public render() { + return ( + <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> + <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> + <div className="col lg-col-4 md-col-4 col-12 left"> + <div className="sm-mx-auto" style={{ width: 148 }}> + <div> + <img src="/images/protocol_logo_white.png" height="30" /> + </div> + <div + style={{ + fontSize: 11, + color: colors.grey, + paddingLeft: 37, + paddingTop: 2, + }} + > + © ZeroEx, Intl. + </div> + </div> + </div> + <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> + <div className="col lg-col-4 md-col-4 col-12"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Documentation)} + {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} + </div> + </div> + <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Community)} + {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} + </div> + </div> + <div className="col lg-col-4 md-col-4 col-12"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Organization)} + {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderIcon(fileName: string) { + return ( + <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> + <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> + </div> + ); + } + private _renderMenuItem(item: FooterMenuItem) { + const iconIfExists = titleToIcon[item.title]; + return ( + <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> + {item.isExternal ? ( + <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> + {!_.isUndefined(iconIfExists) ? ( + <div className="sm-mx-auto" style={{ width: 65 }}> + <div className="flex"> + <div className="pr1">{this._renderIcon(iconIfExists)}</div> + <div>{item.title}</div> + </div> + </div> + ) : ( + item.title + )} + </a> + ) : ( + <Link to={item.path} style={linkStyle} className="text-decoration-none"> + <div> + {!_.isUndefined(iconIfExists) && ( + <div className="pr1">{this._renderIcon(iconIfExists)}</div> + )} + {item.title} + </div> + </Link> + )} + </div> + ); + } + private _renderHeader(title: string) { + const headerStyle = { + textTransform: 'uppercase', + color: colors.grey400, + letterSpacing: 2, + fontFamily: 'Roboto Mono', + fontSize: 13, + }; + return ( + <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> + {title} + </div> + ); + } } diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 5eed2fabf..df7d87cfd 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -13,264 +13,264 @@ import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } fro const TOKEN_ICON_DIMENSION = 100; const TILE_DIMENSION = 146; enum AssetViews { - ASSET_PICKER = 'ASSET_PICKER', - NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', - CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', + ASSET_PICKER = 'ASSET_PICKER', + NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', + CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', } interface AssetPickerProps { - userAddress: string; - blockchain: Blockchain; - dispatcher: Dispatcher; - networkId: number; - isOpen: boolean; - currentTokenAddress: string; - onTokenChosen: (tokenAddress: string) => void; - tokenByAddress: TokenByAddress; - tokenVisibility?: TokenVisibility; + userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + networkId: number; + isOpen: boolean; + currentTokenAddress: string; + onTokenChosen: (tokenAddress: string) => void; + tokenByAddress: TokenByAddress; + tokenVisibility?: TokenVisibility; } interface AssetPickerState { - assetView: AssetViews; - hoveredAddress: string | undefined; - chosenTrackTokenAddress: string; - isAddingTokenToTracked: boolean; + assetView: AssetViews; + hoveredAddress: string | undefined; + chosenTrackTokenAddress: string; + isAddingTokenToTracked: boolean; } export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerState> { - public static defaultProps: Partial<AssetPickerProps> = { - tokenVisibility: TokenVisibility.ALL, - }; - private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs }; - constructor(props: AssetPickerProps) { - super(props); - this.state = { - assetView: AssetViews.ASSET_PICKER, - hoveredAddress: undefined, - chosenTrackTokenAddress: undefined, - isAddingTokenToTracked: false, - }; - this._dialogConfigsByAssetView = { - [AssetViews.ASSET_PICKER]: { - title: 'Select token', - isModal: false, - actions: [], - }, - [AssetViews.NEW_TOKEN_FORM]: { - title: 'Add an ERC20 token', - isModal: false, - actions: [], - }, - [AssetViews.CONFIRM_TRACK_TOKEN]: { - title: 'Tracking confirmation', - isModal: true, - actions: [ - <FlatButton - key="noTracking" - label="No" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} - />, - <FlatButton - key="yesTrack" - label="Yes" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} - />, - ], - }, - }; - } - public render() { - const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; - return ( - <Dialog - title={dialogConfigs.title} - titleStyle={{ fontWeight: 100 }} - modal={dialogConfigs.isModal} - open={this.props.isOpen} - actions={dialogConfigs.actions} - onRequestClose={this._onCloseDialog.bind(this)} - > - {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} - {this.state.assetView === AssetViews.NEW_TOKEN_FORM && ( - <NewTokenForm - blockchain={this.props.blockchain} - onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)} - tokenByAddress={this.props.tokenByAddress} - /> - )} - {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()} - </Dialog> - ); - } - private _renderConfirmTrackToken() { - const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; - return ( - <TrackTokenConfirmation - tokens={[token]} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - isAddingTokenToTracked={this.state.isAddingTokenToTracked} - /> - ); - } - private _renderAssetPicker() { - return ( - <div - className="clearfix flex flex-wrap" - style={{ - overflowY: 'auto', - maxWidth: 720, - maxHeight: 356, - marginBottom: 10, - }} - > - {this._renderGridTiles()} - </div> - ); - } - private _renderGridTiles() { - let isHovered; - let tileStyles; - const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { - if ( - (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || - (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) - ) { - return null; // Skip - } - isHovered = this.state.hoveredAddress === address; - tileStyles = { - cursor: 'pointer', - opacity: isHovered ? 0.6 : 1, - }; - return ( - <div - key={address} - style={{ - width: TILE_DIMENSION, - height: TILE_DIMENSION, - ...tileStyles, - }} - className="p2 mx-auto" - onClick={this._onChooseToken.bind(this, address)} - onMouseEnter={this._onToggleHover.bind(this, address, true)} - onMouseLeave={this._onToggleHover.bind(this, address, false)} - > - <div className="p1 center"> - <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> - </div> - <div className="center">{token.name}</div> - </div> - ); - }); - const otherTokenKey = 'otherToken'; - isHovered = this.state.hoveredAddress === otherTokenKey; - tileStyles = { - cursor: 'pointer', - opacity: isHovered ? 0.6 : 1, - }; - if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { - gridTiles.push( - <div - key={otherTokenKey} - style={{ - width: TILE_DIMENSION, - height: TILE_DIMENSION, - ...tileStyles, - }} - className="p2 mx-auto" - onClick={this._onCustomAssetChosen.bind(this)} - onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)} - onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)} - > - <div className="p1 center"> - <i - style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }} - className="zmdi zmdi-plus-circle" - /> - </div> - <div className="center">Other ERC20 Token</div> - </div>, - ); - } - return gridTiles; - } - private _onToggleHover(address: string, isHovered: boolean) { - const hoveredAddress = isHovered ? address : undefined; - this.setState({ - hoveredAddress, - }); - } - private _onCloseDialog() { - this.setState({ - assetView: AssetViews.ASSET_PICKER, - }); - this.props.onTokenChosen(this.props.currentTokenAddress); - } - private _onChooseToken(tokenAddress: string) { - const token = this.props.tokenByAddress[tokenAddress]; - if (token.isTracked) { - this.props.onTokenChosen(tokenAddress); - } else { - this.setState({ - assetView: AssetViews.CONFIRM_TRACK_TOKEN, - chosenTrackTokenAddress: tokenAddress, - }); - } - } - private _onCustomAssetChosen() { - this.setState({ - assetView: AssetViews.NEW_TOKEN_FORM, - }); - } - private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { - this.props.dispatcher.updateTokenStateByAddress({ - [newToken.address]: newTokenState, - }); - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); - this.props.dispatcher.addTokenToTokenByAddress(newToken); - this.setState({ - assetView: AssetViews.ASSET_PICKER, - }); - this.props.onTokenChosen(newToken.address); - } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { - if (!didUserAcceptTracking) { - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); - this._onCloseDialog(); - return; - } - this.setState({ - isAddingTokenToTracked: true, - }); - const tokenAddress = this.state.chosenTrackTokenAddress; - const token = this.props.tokenByAddress[tokenAddress]; - const newTokenEntry = { - ...token, - }; + public static defaultProps: Partial<AssetPickerProps> = { + tokenVisibility: TokenVisibility.ALL, + }; + private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs }; + constructor(props: AssetPickerProps) { + super(props); + this.state = { + assetView: AssetViews.ASSET_PICKER, + hoveredAddress: undefined, + chosenTrackTokenAddress: undefined, + isAddingTokenToTracked: false, + }; + this._dialogConfigsByAssetView = { + [AssetViews.ASSET_PICKER]: { + title: 'Select token', + isModal: false, + actions: [], + }, + [AssetViews.NEW_TOKEN_FORM]: { + title: 'Add an ERC20 token', + isModal: false, + actions: [], + }, + [AssetViews.CONFIRM_TRACK_TOKEN]: { + title: 'Tracking confirmation', + isModal: true, + actions: [ + <FlatButton + key="noTracking" + label="No" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} + />, + <FlatButton + key="yesTrack" + label="Yes" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} + />, + ], + }, + }; + } + public render() { + const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; + return ( + <Dialog + title={dialogConfigs.title} + titleStyle={{ fontWeight: 100 }} + modal={dialogConfigs.isModal} + open={this.props.isOpen} + actions={dialogConfigs.actions} + onRequestClose={this._onCloseDialog.bind(this)} + > + {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} + {this.state.assetView === AssetViews.NEW_TOKEN_FORM && ( + <NewTokenForm + blockchain={this.props.blockchain} + onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)} + tokenByAddress={this.props.tokenByAddress} + /> + )} + {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()} + </Dialog> + ); + } + private _renderConfirmTrackToken() { + const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; + return ( + <TrackTokenConfirmation + tokens={[token]} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + isAddingTokenToTracked={this.state.isAddingTokenToTracked} + /> + ); + } + private _renderAssetPicker() { + return ( + <div + className="clearfix flex flex-wrap" + style={{ + overflowY: 'auto', + maxWidth: 720, + maxHeight: 356, + marginBottom: 10, + }} + > + {this._renderGridTiles()} + </div> + ); + } + private _renderGridTiles() { + let isHovered; + let tileStyles; + const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { + if ( + (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) + ) { + return null; // Skip + } + isHovered = this.state.hoveredAddress === address; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + return ( + <div + key={address} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} + className="p2 mx-auto" + onClick={this._onChooseToken.bind(this, address)} + onMouseEnter={this._onToggleHover.bind(this, address, true)} + onMouseLeave={this._onToggleHover.bind(this, address, false)} + > + <div className="p1 center"> + <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> + </div> + <div className="center">{token.name}</div> + </div> + ); + }); + const otherTokenKey = 'otherToken'; + isHovered = this.state.hoveredAddress === otherTokenKey; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { + gridTiles.push( + <div + key={otherTokenKey} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} + className="p2 mx-auto" + onClick={this._onCustomAssetChosen.bind(this)} + onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)} + onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)} + > + <div className="p1 center"> + <i + style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }} + className="zmdi zmdi-plus-circle" + /> + </div> + <div className="center">Other ERC20 Token</div> + </div>, + ); + } + return gridTiles; + } + private _onToggleHover(address: string, isHovered: boolean) { + const hoveredAddress = isHovered ? address : undefined; + this.setState({ + hoveredAddress, + }); + } + private _onCloseDialog() { + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(this.props.currentTokenAddress); + } + private _onChooseToken(tokenAddress: string) { + const token = this.props.tokenByAddress[tokenAddress]; + if (token.isTracked) { + this.props.onTokenChosen(tokenAddress); + } else { + this.setState({ + assetView: AssetViews.CONFIRM_TRACK_TOKEN, + chosenTrackTokenAddress: tokenAddress, + }); + } + } + private _onCustomAssetChosen() { + this.setState({ + assetView: AssetViews.NEW_TOKEN_FORM, + }); + } + private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { + this.props.dispatcher.updateTokenStateByAddress({ + [newToken.address]: newTokenState, + }); + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); + this.props.dispatcher.addTokenToTokenByAddress(newToken); + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(newToken.address); + } + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this._onCloseDialog(); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + const tokenAddress = this.state.chosenTrackTokenAddress; + const token = this.props.tokenByAddress[tokenAddress]; + const newTokenEntry = { + ...token, + }; - newTokenEntry.isTracked = true; - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); - this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); - this.props.onTokenChosen(tokenAddress); - } + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.props.onTokenChosen(tokenAddress); + } } diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index b10b2d609..3ae0d48a7 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -19,335 +19,335 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; import { - AlertTypes, - BlockchainErrs, - HashData, - Side, - SideToAssetToken, - SignatureData, - Token, - TokenByAddress, - TokenStateByAddress, + AlertTypes, + BlockchainErrs, + HashData, + Side, + SideToAssetToken, + SignatureData, + Token, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; enum SigningState { - UNSIGNED, - SIGNING, - SIGNED, + UNSIGNED, + SIGNING, + SIGNED, } interface GenerateOrderFormProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - hashData: HashData; - orderExpiryTimestamp: BigNumber; - networkId: number; - userAddress: string; - orderSignatureData: SignatureData; - orderTakerAddress: string; - orderSalt: BigNumber; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + orderExpiryTimestamp: BigNumber; + networkId: number; + userAddress: string; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderSalt: BigNumber; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; } interface GenerateOrderFormState { - globalErrMsg: string; - shouldShowIncompleteErrs: boolean; - signingState: SigningState; + globalErrMsg: string; + shouldShowIncompleteErrs: boolean; + signingState: SigningState; } export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, GenerateOrderFormState> { - private _validator: SchemaValidator; - constructor(props: GenerateOrderFormProps) { - super(props); - this.state = { - globalErrMsg: '', - shouldShowIncompleteErrs: false, - signingState: SigningState.UNSIGNED, - }; - this._validator = new SchemaValidator(); - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - const dispatcher = this.props.dispatcher; - const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; - const depositToken = this.props.tokenByAddress[depositTokenAddress]; - const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; - const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; - const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; - const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; - const takerExplanation = - 'If a taker is specified, only they are<br> \ + private _validator: SchemaValidator; + constructor(props: GenerateOrderFormProps) { + super(props); + this.state = { + globalErrMsg: '', + shouldShowIncompleteErrs: false, + signingState: SigningState.UNSIGNED, + }; + this._validator = new SchemaValidator(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const dispatcher = this.props.dispatcher; + const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; + const depositToken = this.props.tokenByAddress[depositTokenAddress]; + const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; + const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; + const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; + const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; + const takerExplanation = + 'If a taker is specified, only they are<br> \ allowed to fill this order. If no taker is<br> \ specified, anyone is able to fill it.'; - const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); - return ( - <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> - <h3>Generate an order</h3> - <Divider /> - <div className="mx-auto" style={{ maxWidth: 580 }}> - <div className="pt3"> - <div className="mx-auto clearfix"> - <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> - <TokenInput - userAddress={this.props.userAddress} - blockchain={this.props.blockchain} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - label="Selling" - side={Side.Deposit} - networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.Deposit]} - updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} - tokenByAddress={this.props.tokenByAddress} - /> - <TokenAmountInput - label="Sell amount" - token={depositToken} - tokenState={depositTokenState} - amount={this.props.sideToAssetToken[Side.Deposit].amount} - onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={true} - /> - </div> - <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide"> - <div className="p1"> - <SwapIcon swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} /> - </div> - </div> - <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> - <TokenInput - userAddress={this.props.userAddress} - blockchain={this.props.blockchain} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - label="Buying" - side={Side.Receive} - networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.Receive]} - updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} - tokenByAddress={this.props.tokenByAddress} - /> - <TokenAmountInput - label="Receive amount" - token={receiveToken} - tokenState={receiveTokenState} - amount={this.props.sideToAssetToken[Side.Receive].amount} - onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={false} - shouldCheckAllowance={false} - /> - </div> - </div> - </div> - <div className="pt1 sm-pb2 lg-px4 md-px4"> - <div className="lg-px3 md-px3"> - <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div> - <ExpirationInput - orderExpiryTimestamp={this.props.orderExpiryTimestamp} - updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)} - /> - </div> - </div> - <div className="pt1 flex mx-auto"> - <IdenticonAddressInput - label="Taker" - initialAddress={this.props.orderTakerAddress} - updateOrderAddress={this._updateOrderAddress.bind(this)} - /> - <div className="pt3"> - <div className="pl1"> - <HelpTooltip explanation={takerExplanation} /> - </div> - </div> - </div> - <div> - <HashInput - blockchain={this.props.blockchain} - blockchainIsLoaded={this.props.blockchainIsLoaded} - hashData={this.props.hashData} - label="Order Hash" - /> - </div> - <div className="pt2"> - <div className="center"> - <LifeCycleRaisedButton - labelReady="Sign hash" - labelLoading="Signing..." - labelComplete="Hash signed!" - onClickAsyncFn={this._onSignClickedAsync.bind(this)} - /> - </div> - {this.state.globalErrMsg !== '' && ( - <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - )} - </div> - </div> - <Dialog - title="Order JSON" - titleStyle={{ fontWeight: 100 }} - modal={false} - open={this.state.signingState === SigningState.SIGNED} - onRequestClose={this._onCloseOrderJSONDialog.bind(this)} - > - <OrderJSON - exchangeContractIfExists={exchangeContractIfExists} - orderExpiryTimestamp={this.props.orderExpiryTimestamp} - orderSignatureData={this.props.orderSignatureData} - orderTakerAddress={this.props.orderTakerAddress} - orderMakerAddress={this.props.userAddress} - orderSalt={this.props.orderSalt} - orderMakerFee={this.props.hashData.makerFee} - orderTakerFee={this.props.hashData.takerFee} - orderFeeRecipient={this.props.hashData.feeRecipientAddress} - networkId={this.props.networkId} - sideToAssetToken={this.props.sideToAssetToken} - tokenByAddress={this.props.tokenByAddress} - /> - </Dialog> - </div> - ); - } - private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { - this.props.dispatcher.updateChosenAssetToken(side, { - address: token.address, - amount, - }); - } - private _onCloseOrderJSONDialog() { - // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store - // with a new value so that if a user signs the identical order again, the newly signed - // orderHash will not collide with the previously generated orderHash. - this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); - this.setState({ - signingState: SigningState.UNSIGNED, - }); - } - private async _onSignClickedAsync(): Promise<boolean> { - if (this.props.blockchainErr !== BlockchainErrs.NoError) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } + const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); + return ( + <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> + <h3>Generate an order</h3> + <Divider /> + <div className="mx-auto" style={{ maxWidth: 580 }}> + <div className="pt3"> + <div className="mx-auto clearfix"> + <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> + <TokenInput + userAddress={this.props.userAddress} + blockchain={this.props.blockchain} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + label="Selling" + side={Side.Deposit} + networkId={this.props.networkId} + assetToken={this.props.sideToAssetToken[Side.Deposit]} + updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} + tokenByAddress={this.props.tokenByAddress} + /> + <TokenAmountInput + label="Sell amount" + token={depositToken} + tokenState={depositTokenState} + amount={this.props.sideToAssetToken[Side.Deposit].amount} + onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={true} + /> + </div> + <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide"> + <div className="p1"> + <SwapIcon swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} /> + </div> + </div> + <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> + <TokenInput + userAddress={this.props.userAddress} + blockchain={this.props.blockchain} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + label="Buying" + side={Side.Receive} + networkId={this.props.networkId} + assetToken={this.props.sideToAssetToken[Side.Receive]} + updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} + tokenByAddress={this.props.tokenByAddress} + /> + <TokenAmountInput + label="Receive amount" + token={receiveToken} + tokenState={receiveTokenState} + amount={this.props.sideToAssetToken[Side.Receive].amount} + onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={false} + shouldCheckAllowance={false} + /> + </div> + </div> + </div> + <div className="pt1 sm-pb2 lg-px4 md-px4"> + <div className="lg-px3 md-px3"> + <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div> + <ExpirationInput + orderExpiryTimestamp={this.props.orderExpiryTimestamp} + updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)} + /> + </div> + </div> + <div className="pt1 flex mx-auto"> + <IdenticonAddressInput + label="Taker" + initialAddress={this.props.orderTakerAddress} + updateOrderAddress={this._updateOrderAddress.bind(this)} + /> + <div className="pt3"> + <div className="pl1"> + <HelpTooltip explanation={takerExplanation} /> + </div> + </div> + </div> + <div> + <HashInput + blockchain={this.props.blockchain} + blockchainIsLoaded={this.props.blockchainIsLoaded} + hashData={this.props.hashData} + label="Order Hash" + /> + </div> + <div className="pt2"> + <div className="center"> + <LifeCycleRaisedButton + labelReady="Sign hash" + labelLoading="Signing..." + labelComplete="Hash signed!" + onClickAsyncFn={this._onSignClickedAsync.bind(this)} + /> + </div> + {this.state.globalErrMsg !== '' && ( + <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> + )} + </div> + </div> + <Dialog + title="Order JSON" + titleStyle={{ fontWeight: 100 }} + modal={false} + open={this.state.signingState === SigningState.SIGNED} + onRequestClose={this._onCloseOrderJSONDialog.bind(this)} + > + <OrderJSON + exchangeContractIfExists={exchangeContractIfExists} + orderExpiryTimestamp={this.props.orderExpiryTimestamp} + orderSignatureData={this.props.orderSignatureData} + orderTakerAddress={this.props.orderTakerAddress} + orderMakerAddress={this.props.userAddress} + orderSalt={this.props.orderSalt} + orderMakerFee={this.props.hashData.makerFee} + orderTakerFee={this.props.hashData.takerFee} + orderFeeRecipient={this.props.hashData.feeRecipientAddress} + networkId={this.props.networkId} + sideToAssetToken={this.props.sideToAssetToken} + tokenByAddress={this.props.tokenByAddress} + /> + </Dialog> + </div> + ); + } + private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateChosenAssetToken(side, { + address: token.address, + amount, + }); + } + private _onCloseOrderJSONDialog() { + // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store + // with a new value so that if a user signs the identical order again, the newly signed + // orderHash will not collide with the previously generated orderHash. + this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + } + private async _onSignClickedAsync(): Promise<boolean> { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } - // Check if all required inputs were supplied - const debitToken = this.props.sideToAssetToken[Side.Deposit]; - const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; - const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; - const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; - if ( - !_.isUndefined(debitToken.amount) && - !_.isUndefined(receiveAmount) && - debitToken.amount.gt(0) && - receiveAmount.gt(0) && - this.props.userAddress !== '' && - debitBalance.gte(debitToken.amount) && - debitAllowance.gte(debitToken.amount) - ) { - const didSignSuccessfully = await this._signTransactionAsync(); - if (didSignSuccessfully) { - this.setState({ - globalErrMsg: '', - shouldShowIncompleteErrs: false, - }); - } - return didSignSuccessfully; - } else { - let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; - if (this.props.userAddress === '') { - globalErrMsg = 'You must enable wallet communication'; - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } - this.setState({ - globalErrMsg, - shouldShowIncompleteErrs: true, - }); - return false; - } - } - private async _signTransactionAsync(): Promise<boolean> { - this.setState({ - signingState: SigningState.SIGNING, - }); - const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); - if (_.isUndefined(exchangeContractAddr)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - signingState: SigningState.UNSIGNED, - }); - return false; - } - const hashData = this.props.hashData; + // Check if all required inputs were supplied + const debitToken = this.props.sideToAssetToken[Side.Deposit]; + const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; + const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; + const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; + if ( + !_.isUndefined(debitToken.amount) && + !_.isUndefined(receiveAmount) && + debitToken.amount.gt(0) && + receiveAmount.gt(0) && + this.props.userAddress !== '' && + debitBalance.gte(debitToken.amount) && + debitAllowance.gte(debitToken.amount) + ) { + const didSignSuccessfully = await this._signTransactionAsync(); + if (didSignSuccessfully) { + this.setState({ + globalErrMsg: '', + shouldShowIncompleteErrs: false, + }); + } + return didSignSuccessfully; + } else { + let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; + if (this.props.userAddress === '') { + globalErrMsg = 'You must enable wallet communication'; + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } + this.setState({ + globalErrMsg, + shouldShowIncompleteErrs: true, + }); + return false; + } + } + private async _signTransactionAsync(): Promise<boolean> { + this.setState({ + signingState: SigningState.SIGNING, + }); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + if (_.isUndefined(exchangeContractAddr)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + return false; + } + const hashData = this.props.hashData; - const zeroExOrder: Order = { - exchangeContractAddress: exchangeContractAddr, - expirationUnixTimestampSec: hashData.orderExpiryTimestamp, - feeRecipient: hashData.feeRecipientAddress, - maker: hashData.orderMakerAddress, - makerFee: hashData.makerFee, - makerTokenAddress: hashData.depositTokenContractAddr, - makerTokenAmount: hashData.depositAmount, - salt: hashData.orderSalt, - taker: hashData.orderTakerAddress, - takerFee: hashData.takerFee, - takerTokenAddress: hashData.receiveTokenContractAddr, - takerTokenAmount: hashData.receiveAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + const zeroExOrder: Order = { + exchangeContractAddress: exchangeContractAddr, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); - let globalErrMsg = ''; - try { - const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); - const order = utils.generateOrder( - this.props.networkId, - exchangeContractAddr, - this.props.sideToAssetToken, - hashData.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.userAddress, - hashData.makerFee, - hashData.takerFee, - hashData.feeRecipientAddress, - signatureData, - this.props.tokenByAddress, - hashData.orderSalt, - ); - const validationResult = this._validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - globalErrMsg = 'Order signing failed. Please refresh and try again'; - utils.consoleLog(`Unexpected error occured: Order validation failed: + let globalErrMsg = ''; + try { + const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); + const order = utils.generateOrder( + this.props.networkId, + exchangeContractAddr, + this.props.sideToAssetToken, + hashData.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.userAddress, + hashData.makerFee, + hashData.takerFee, + hashData.feeRecipientAddress, + signatureData, + this.props.tokenByAddress, + hashData.orderSalt, + ); + const validationResult = this._validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + globalErrMsg = 'Order signing failed. Please refresh and try again'; + utils.consoleLog(`Unexpected error occured: Order validation failed: ${validationResult.errors}`); - } - } catch (err) { - const errMsg = `${err}`; - if (utils.didUserDenyWeb3Request(errMsg)) { - globalErrMsg = 'User denied sign request'; - } else { - globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; - utils.consoleLog(`Unexpected error occured: ${err}`); - utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); - } - } - this.setState({ - signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, - globalErrMsg, - }); - return globalErrMsg === ''; - } - private _updateOrderAddress(address?: string): void { - if (!_.isUndefined(address)) { - this.props.dispatcher.updateOrderTakerAddress(address); - } - } + } + } catch (err) { + const errMsg = `${err}`; + if (utils.didUserDenyWeb3Request(errMsg)) { + globalErrMsg = 'User denied sign request'; + } else { + globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; + utils.consoleLog(`Unexpected error occured: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + } + } + this.setState({ + signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, + globalErrMsg, + }); + return globalErrMsg === ''; + } + private _updateOrderAddress(address?: string): void { + if (!_.isUndefined(address)) { + this.props.dispatcher.updateOrderTakerAddress(address); + } + } } diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index d61aac92a..63645be9a 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -11,227 +11,227 @@ import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { - blockchain: Blockchain; - tokenByAddress: TokenByAddress; - onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; } interface NewTokenFormState { - globalErrMsg: string; - name: string; - nameErrText: string; - symbol: string; - symbolErrText: string; - address: string; - shouldShowAddressIncompleteErr: boolean; - decimals: string; - decimalsErrText: string; + globalErrMsg: string; + name: string; + nameErrText: string; + symbol: string; + symbolErrText: string; + address: string; + shouldShowAddressIncompleteErr: boolean; + decimals: string; + decimalsErrText: string; } export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFormState> { - constructor(props: NewTokenFormProps) { - super(props); - this.state = { - address: '', - globalErrMsg: '', - name: '', - nameErrText: '', - shouldShowAddressIncompleteErr: false, - symbol: '', - symbolErrText: '', - decimals: '18', - decimalsErrText: '', - }; - } - public render() { - return ( - <div className="mx-auto pb2" style={{ width: 256 }}> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Name" />} - value={this.state.name} - errorText={this.state.nameErrText} - onChange={this._onTokenNameChanged.bind(this)} - /> - </div> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Symbol" />} - value={this.state.symbol} - errorText={this.state.symbolErrText} - onChange={this._onTokenSymbolChanged.bind(this)} - /> - </div> - <div> - <AddressInput - isRequired={true} - label="Contract address" - initialAddress="" - shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr} - updateAddress={this._onTokenAddressChanged.bind(this)} - /> - </div> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Decimals" />} - value={this.state.decimals} - errorText={this.state.decimalsErrText} - onChange={this._onTokenDecimalsChanged.bind(this)} - /> - </div> - <div className="pt2 mx-auto" style={{ width: 120 }}> - <LifeCycleRaisedButton - labelReady="Add" - labelLoading="Adding..." - labelComplete="Added!" - onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)} - /> - </div> - {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />} - </div> - ); - } - private async _onAddNewTokenClickAsync() { - // Trigger validation of name and symbol - this._onTokenNameChanged(undefined, this.state.name); - this._onTokenSymbolChanged(undefined, this.state.symbol); - this._onTokenDecimalsChanged(undefined, this.state.decimals); + constructor(props: NewTokenFormProps) { + super(props); + this.state = { + address: '', + globalErrMsg: '', + name: '', + nameErrText: '', + shouldShowAddressIncompleteErr: false, + symbol: '', + symbolErrText: '', + decimals: '18', + decimalsErrText: '', + }; + } + public render() { + return ( + <div className="mx-auto pb2" style={{ width: 256 }}> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Name" />} + value={this.state.name} + errorText={this.state.nameErrText} + onChange={this._onTokenNameChanged.bind(this)} + /> + </div> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Symbol" />} + value={this.state.symbol} + errorText={this.state.symbolErrText} + onChange={this._onTokenSymbolChanged.bind(this)} + /> + </div> + <div> + <AddressInput + isRequired={true} + label="Contract address" + initialAddress="" + shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr} + updateAddress={this._onTokenAddressChanged.bind(this)} + /> + </div> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Decimals" />} + value={this.state.decimals} + errorText={this.state.decimalsErrText} + onChange={this._onTokenDecimalsChanged.bind(this)} + /> + </div> + <div className="pt2 mx-auto" style={{ width: 120 }}> + <LifeCycleRaisedButton + labelReady="Add" + labelLoading="Adding..." + labelComplete="Added!" + onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)} + /> + </div> + {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />} + </div> + ); + } + private async _onAddNewTokenClickAsync() { + // Trigger validation of name and symbol + this._onTokenNameChanged(undefined, this.state.name); + this._onTokenSymbolChanged(undefined, this.state.symbol); + this._onTokenDecimalsChanged(undefined, this.state.decimals); - const isAddressIncomplete = this.state.address === ''; - let doesContractExist = false; - if (!isAddressIncomplete) { - doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); - } + const isAddressIncomplete = this.state.address === ''; + let doesContractExist = false; + if (!isAddressIncomplete) { + doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); + } - let hasBalanceAllowanceErr = false; - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (doesContractExist) { - try { - [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - this.state.address, - ); - } catch (err) { - hasBalanceAllowanceErr = true; - } - } + let hasBalanceAllowanceErr = false; + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (doesContractExist) { + try { + [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + this.state.address, + ); + } catch (err) { + hasBalanceAllowanceErr = true; + } + } - let globalErrMsg = ''; - if ( - this.state.nameErrText !== '' || - this.state.symbolErrText !== '' || - this.state.decimalsErrText !== '' || - isAddressIncomplete - ) { - globalErrMsg = 'Please fix the above issues'; - } else if (!doesContractExist) { - globalErrMsg = 'No contract found at supplied address'; - } else if (hasBalanceAllowanceErr) { - globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; - } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { - globalErrMsg = 'A token already exists with this address'; - } + let globalErrMsg = ''; + if ( + this.state.nameErrText !== '' || + this.state.symbolErrText !== '' || + this.state.decimalsErrText !== '' || + isAddressIncomplete + ) { + globalErrMsg = 'Please fix the above issues'; + } else if (!doesContractExist) { + globalErrMsg = 'No contract found at supplied address'; + } else if (hasBalanceAllowanceErr) { + globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; + } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { + globalErrMsg = 'A token already exists with this address'; + } - if (globalErrMsg !== '') { - this.setState({ - globalErrMsg, - shouldShowAddressIncompleteErr: isAddressIncomplete, - }); - return; - } + if (globalErrMsg !== '') { + this.setState({ + globalErrMsg, + shouldShowAddressIncompleteErr: isAddressIncomplete, + }); + return; + } - const newToken: Token = { - address: this.state.address, - decimals: _.parseInt(this.state.decimals), - iconUrl: undefined, - name: this.state.name, - symbol: this.state.symbol.toUpperCase(), - isTracked: true, - isRegistered: false, - }; - const newTokenState: TokenState = { - balance, - allowance, - }; - this.props.onNewTokenSubmitted(newToken, newTokenState); - } - private _onTokenNameChanged(e: any, name: string) { - let nameErrText = ''; - const maxLength = 30; - const tokens = _.values(this.props.tokenByAddress); - const tokenWithNameIfExists = _.find(tokens, { name }); - const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); - if (name === '') { - nameErrText = 'Name is required'; - } else if (!this._isValidName(name)) { - nameErrText = 'Name should only contain letters, digits and spaces'; - } else if (name.length > maxLength) { - nameErrText = `Max length is ${maxLength}`; - } else if (tokenWithNameExists) { - nameErrText = 'Token with this name already exists'; - } + const newToken: Token = { + address: this.state.address, + decimals: _.parseInt(this.state.decimals), + iconUrl: undefined, + name: this.state.name, + symbol: this.state.symbol.toUpperCase(), + isTracked: true, + isRegistered: false, + }; + const newTokenState: TokenState = { + balance, + allowance, + }; + this.props.onNewTokenSubmitted(newToken, newTokenState); + } + private _onTokenNameChanged(e: any, name: string) { + let nameErrText = ''; + const maxLength = 30; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithNameIfExists = _.find(tokens, { name }); + const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); + if (name === '') { + nameErrText = 'Name is required'; + } else if (!this._isValidName(name)) { + nameErrText = 'Name should only contain letters, digits and spaces'; + } else if (name.length > maxLength) { + nameErrText = `Max length is ${maxLength}`; + } else if (tokenWithNameExists) { + nameErrText = 'Token with this name already exists'; + } - this.setState({ - name, - nameErrText, - }); - } - private _onTokenSymbolChanged(e: any, symbol: string) { - let symbolErrText = ''; - const maxLength = 5; - const tokens = _.values(this.props.tokenByAddress); - const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); - if (symbol === '') { - symbolErrText = 'Symbol is required'; - } else if (!this._isAlphanumeric(symbol)) { - symbolErrText = 'Can only include alphanumeric characters'; - } else if (symbol.length > maxLength) { - symbolErrText = `Max length is ${maxLength}`; - } else if (tokenWithSymbolExists) { - symbolErrText = 'Token with symbol already exists'; - } + this.setState({ + name, + nameErrText, + }); + } + private _onTokenSymbolChanged(e: any, symbol: string) { + let symbolErrText = ''; + const maxLength = 5; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); + if (symbol === '') { + symbolErrText = 'Symbol is required'; + } else if (!this._isAlphanumeric(symbol)) { + symbolErrText = 'Can only include alphanumeric characters'; + } else if (symbol.length > maxLength) { + symbolErrText = `Max length is ${maxLength}`; + } else if (tokenWithSymbolExists) { + symbolErrText = 'Token with symbol already exists'; + } - this.setState({ - symbol, - symbolErrText, - }); - } - private _onTokenDecimalsChanged(e: any, decimals: string) { - let decimalsErrText = ''; - const maxLength = 2; - if (decimals === '') { - decimalsErrText = 'Decimals is required'; - } else if (!this._isInteger(decimals)) { - decimalsErrText = 'Must be an integer'; - } else if (decimals.length > maxLength) { - decimalsErrText = `Max length is ${maxLength}`; - } + this.setState({ + symbol, + symbolErrText, + }); + } + private _onTokenDecimalsChanged(e: any, decimals: string) { + let decimalsErrText = ''; + const maxLength = 2; + if (decimals === '') { + decimalsErrText = 'Decimals is required'; + } else if (!this._isInteger(decimals)) { + decimalsErrText = 'Must be an integer'; + } else if (decimals.length > maxLength) { + decimalsErrText = `Max length is ${maxLength}`; + } - this.setState({ - decimals, - decimalsErrText, - }); - } - private _onTokenAddressChanged(address?: string) { - if (!_.isUndefined(address)) { - this.setState({ - address, - }); - } - } - private _isValidName(input: string) { - return /^[a-z0-9 ]+$/i.test(input); - } - private _isInteger(input: string) { - return /^[0-9]+$/i.test(input); - } - private _isAlphanumeric(input: string) { - return /^[a-zA-Z0-9]+$/i.test(input); - } + this.setState({ + decimals, + decimalsErrText, + }); + } + private _onTokenAddressChanged(address?: string) { + if (!_.isUndefined(address)) { + this.setState({ + address, + }); + } + } + private _isValidName(input: string) { + return /^[a-z0-9 ]+$/i.test(input); + } + private _isInteger(input: string) { + return /^[0-9]+$/i.test(input); + } + private _isAlphanumeric(input: string) { + return /^[a-zA-Z0-9]+$/i.test(input); + } } diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx index 236bf9a00..dd4131140 100644 --- a/packages/website/ts/components/inputs/address_input.tsx +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -6,66 +6,66 @@ import { RequiredLabel } from 'ts/components/ui/required_label'; import { colors } from 'ts/utils/colors'; interface AddressInputProps { - disabled?: boolean; - initialAddress: string; - isRequired?: boolean; - hintText?: string; - shouldHideLabel?: boolean; - label?: string; - shouldShowIncompleteErrs?: boolean; - updateAddress: (address?: string) => void; + disabled?: boolean; + initialAddress: string; + isRequired?: boolean; + hintText?: string; + shouldHideLabel?: boolean; + label?: string; + shouldShowIncompleteErrs?: boolean; + updateAddress: (address?: string) => void; } interface AddressInputState { - address: string; - errMsg: string; + address: string; + errMsg: string; } export class AddressInput extends React.Component<AddressInputProps, AddressInputState> { - constructor(props: AddressInputProps) { - super(props); - this.state = { - address: this.props.initialAddress, - errMsg: '', - }; - } - public componentWillReceiveProps(nextProps: AddressInputProps) { - if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { - this.setState({ - errMsg: 'Address is required', - }); - } - } - public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; - const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; - const hintText = this.props.hintText ? this.props.hintText : ''; - return ( - <div className="overflow-hidden"> - <TextField - id={`address-field-${this.props.label}`} - disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled} - fullWidth={true} - hintText={hintText} - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey, display: labelDisplay }} - floatingLabelText={label} - errorText={this.state.errMsg} - value={this.state.address} - onChange={this._onOrderTakerAddressUpdated.bind(this)} - /> - </div> - ); - } - private _onOrderTakerAddressUpdated(e: any) { - const address = e.target.value.toLowerCase(); - const isValidAddress = addressUtils.isAddress(address) || address === ''; - const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; - this.setState({ - address, - errMsg, - }); - const addressIfValid = isValidAddress ? address : undefined; - this.props.updateAddress(addressIfValid); - } + constructor(props: AddressInputProps) { + super(props); + this.state = { + address: this.props.initialAddress, + errMsg: '', + }; + } + public componentWillReceiveProps(nextProps: AddressInputProps) { + if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { + this.setState({ + errMsg: 'Address is required', + }); + } + } + public render() { + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; + const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; + const hintText = this.props.hintText ? this.props.hintText : ''; + return ( + <div className="overflow-hidden"> + <TextField + id={`address-field-${this.props.label}`} + disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled} + fullWidth={true} + hintText={hintText} + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey, display: labelDisplay }} + floatingLabelText={label} + errorText={this.state.errMsg} + value={this.state.address} + onChange={this._onOrderTakerAddressUpdated.bind(this)} + /> + </div> + ); + } + private _onOrderTakerAddressUpdated(e: any) { + const address = e.target.value.toLowerCase(); + const isValidAddress = addressUtils.isAddress(address) || address === ''; + const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; + this.setState({ + address, + errMsg, + }); + const addressIfValid = isValidAddress ? address : undefined; + this.props.updateAddress(addressIfValid); + } } diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 245784824..da46db4f4 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -11,83 +11,83 @@ import { utils } from 'ts/utils/utils'; const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); interface AllowanceToggleProps { - blockchain: Blockchain; - dispatcher: Dispatcher; - onErrorOccurred: (errType: BalanceErrs) => void; - token: Token; - tokenState: TokenState; - userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + onErrorOccurred: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + userAddress: string; } interface AllowanceToggleState { - isSpinnerVisible: boolean; - prevAllowance: BigNumber; + isSpinnerVisible: boolean; + prevAllowance: BigNumber; } export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { - constructor(props: AllowanceToggleProps) { - super(props); - this.state = { - isSpinnerVisible: false, - prevAllowance: props.tokenState.allowance, - }; - } - public componentWillReceiveProps(nextProps: AllowanceToggleProps) { - if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { - this.setState({ - isSpinnerVisible: false, - prevAllowance: nextProps.tokenState.allowance, - }); - } - } - public render() { - return ( - <div className="flex"> - <div> - <Toggle - disabled={this.state.isSpinnerVisible} - toggled={this._isAllowanceSet()} - onToggle={this._onToggleAllowanceAsync.bind(this)} - /> - </div> - {this.state.isSpinnerVisible && ( - <div className="pl1" style={{ paddingTop: 3 }}> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </div> - )} - </div> - ); - } - private async _onToggleAllowanceAsync(): Promise<void> { - if (this.props.userAddress === '') { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } + constructor(props: AllowanceToggleProps) { + super(props); + this.state = { + isSpinnerVisible: false, + prevAllowance: props.tokenState.allowance, + }; + } + public componentWillReceiveProps(nextProps: AllowanceToggleProps) { + if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { + this.setState({ + isSpinnerVisible: false, + prevAllowance: nextProps.tokenState.allowance, + }); + } + } + public render() { + return ( + <div className="flex"> + <div> + <Toggle + disabled={this.state.isSpinnerVisible} + toggled={this._isAllowanceSet()} + onToggle={this._onToggleAllowanceAsync.bind(this)} + /> + </div> + {this.state.isSpinnerVisible && ( + <div className="pl1" style={{ paddingTop: 3 }}> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </div> + )} + </div> + ); + } + private async _onToggleAllowanceAsync(): Promise<void> { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } - this.setState({ - isSpinnerVisible: true, - }); + this.setState({ + isSpinnerVisible: true, + }); - let newAllowanceAmountInBaseUnits = new BigNumber(0); - if (!this._isAllowanceSet()) { - newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; - } - try { - await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); - } catch (err) { - this.setState({ - isSpinnerVisible: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction')) { - return; - } - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); - await errorReporter.reportAsync(err); - } - } - private _isAllowanceSet() { - return !this.props.tokenState.allowance.eq(0); - } + let newAllowanceAmountInBaseUnits = new BigNumber(0); + if (!this._isAllowanceSet()) { + newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; + } + try { + await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); + } catch (err) { + this.setState({ + isSpinnerVisible: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction')) { + return; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); + await errorReporter.reportAsync(err); + } + } + private _isAllowanceSet() { + return !this.props.tokenState.allowance.eq(0); + } } diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 5cc91994e..ddc434b51 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -9,143 +9,143 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { - label?: string; - balance: BigNumber; - amount?: BigNumber; - onChange: ValidatedBigNumberCallback; - shouldShowIncompleteErrs?: boolean; - shouldCheckBalance: boolean; - validate?: (amount: BigNumber) => InputErrMsg; - onVisitBalancesPageClick?: () => void; - shouldHideVisitBalancesLink?: boolean; + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs?: boolean; + shouldCheckBalance: boolean; + validate?: (amount: BigNumber) => InputErrMsg; + onVisitBalancesPageClick?: () => void; + shouldHideVisitBalancesLink?: boolean; } interface BalanceBoundedInputState { - errMsg: InputErrMsg; - amountString: string; + errMsg: InputErrMsg; + amountString: string; } export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { - public static defaultProps: Partial<BalanceBoundedInputProps> = { - shouldShowIncompleteErrs: false, - shouldHideVisitBalancesLink: false, - }; - constructor(props: BalanceBoundedInputProps) { - super(props); - const amountString = this.props.amount ? this.props.amount.toString() : ''; - this.state = { - errMsg: this._validate(amountString, props.balance), - amountString, - }; - } - public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { - if (nextProps === this.props) { - return; - } - const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); - if (!_.isUndefined(nextProps.amount)) { - let shouldResetState = false; - if (!isCurrentAmountNumeric) { - shouldResetState = true; - } else { - const currentAmount = new BigNumber(this.state.amountString); - if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { - shouldResetState = true; - } - } - if (shouldResetState) { - const amountString = nextProps.amount.toString(); - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); - } - } else if (isCurrentAmountNumeric) { - const amountString = ''; - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); - } - } - public render() { - let errorText = this.state.errMsg; - if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { - errorText = 'This field is required'; - } - let label: React.ReactNode | string = ''; - if (!_.isUndefined(this.props.label)) { - label = <RequiredLabel label={this.props.label} />; - } - return ( - <TextField - fullWidth={true} - floatingLabelText={label} - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey, width: 206 }} - errorText={errorText} - value={this.state.amountString} - hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} - onChange={this._onValueChange.bind(this)} - underlineStyle={{ width: 'calc(100% + 50px)' }} - /> - ); - } - private _onValueChange(e: any, amountString: string) { - const errMsg = this._validate(amountString, this.props.balance); - this.setState( - { - amountString, - errMsg, - }, - () => { - const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString)) { - this.props.onChange(isValid, new BigNumber(amountString)); - } else { - this.props.onChange(isValid); - } - }, - ); - } - private _validate(amountString: string, balance: BigNumber): InputErrMsg { - if (!utils.isNumeric(amountString)) { - return amountString !== '' ? 'Must be a number' : ''; - } - const amount = new BigNumber(amountString); - if (amount.eq(0)) { - return 'Cannot be zero'; - } - if (this.props.shouldCheckBalance && amount.gt(balance)) { - return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; - } - const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); - return errMsg; - } - private _renderIncreaseBalanceLink() { - if (this.props.shouldHideVisitBalancesLink) { - return null; - } + public static defaultProps: Partial<BalanceBoundedInputProps> = { + shouldShowIncompleteErrs: false, + shouldHideVisitBalancesLink: false, + }; + constructor(props: BalanceBoundedInputProps) { + super(props); + const amountString = this.props.amount ? this.props.amount.toString() : ''; + this.state = { + errMsg: this._validate(amountString, props.balance), + amountString, + }; + } + public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { + if (nextProps === this.props) { + return; + } + const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); + if (!_.isUndefined(nextProps.amount)) { + let shouldResetState = false; + if (!isCurrentAmountNumeric) { + shouldResetState = true; + } else { + const currentAmount = new BigNumber(this.state.amountString); + if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { + shouldResetState = true; + } + } + if (shouldResetState) { + const amountString = nextProps.amount.toString(); + this.setState({ + errMsg: this._validate(amountString, nextProps.balance), + amountString, + }); + } + } else if (isCurrentAmountNumeric) { + const amountString = ''; + this.setState({ + errMsg: this._validate(amountString, nextProps.balance), + amountString, + }); + } + } + public render() { + let errorText = this.state.errMsg; + if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { + errorText = 'This field is required'; + } + let label: React.ReactNode | string = ''; + if (!_.isUndefined(this.props.label)) { + label = <RequiredLabel label={this.props.label} />; + } + return ( + <TextField + fullWidth={true} + floatingLabelText={label} + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey, width: 206 }} + errorText={errorText} + value={this.state.amountString} + hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} + onChange={this._onValueChange.bind(this)} + underlineStyle={{ width: 'calc(100% + 50px)' }} + /> + ); + } + private _onValueChange(e: any, amountString: string) { + const errMsg = this._validate(amountString, this.props.balance); + this.setState( + { + amountString, + errMsg, + }, + () => { + const isValid = _.isUndefined(errMsg); + if (utils.isNumeric(amountString)) { + this.props.onChange(isValid, new BigNumber(amountString)); + } else { + this.props.onChange(isValid); + } + }, + ); + } + private _validate(amountString: string, balance: BigNumber): InputErrMsg { + if (!utils.isNumeric(amountString)) { + return amountString !== '' ? 'Must be a number' : ''; + } + const amount = new BigNumber(amountString); + if (amount.eq(0)) { + return 'Cannot be zero'; + } + if (this.props.shouldCheckBalance && amount.gt(balance)) { + return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; + } + const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); + return errMsg; + } + private _renderIncreaseBalanceLink() { + if (this.props.shouldHideVisitBalancesLink) { + return null; + } - const increaseBalanceText = 'Increase balance'; - const linkStyle = { - cursor: 'pointer', - color: colors.darkestGrey, - textDecoration: 'underline', - display: 'inline', - }; - if (_.isUndefined(this.props.onVisitBalancesPageClick)) { - return ( - <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> - {increaseBalanceText} - </Link> - ); - } else { - return ( - <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> - {increaseBalanceText} - </div> - ); - } - } + const increaseBalanceText = 'Increase balance'; + const linkStyle = { + cursor: 'pointer', + color: colors.darkestGrey, + textDecoration: 'underline', + display: 'inline', + }; + if (_.isUndefined(this.props.onVisitBalancesPageClick)) { + return ( + <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> + {increaseBalanceText} + </Link> + ); + } else { + return ( + <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> + {increaseBalanceText} + </div> + ); + } + } } diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index 7f9747094..a66f92c8c 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -7,43 +7,43 @@ import { ValidatedBigNumberCallback } from 'ts/types'; import { constants } from 'ts/utils/constants'; interface EthAmountInputProps { - label?: string; - balance: BigNumber; - amount?: BigNumber; - onChange: ValidatedBigNumberCallback; - shouldShowIncompleteErrs: boolean; - onVisitBalancesPageClick?: () => void; - shouldCheckBalance: boolean; - shouldHideVisitBalancesLink?: boolean; + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs: boolean; + onVisitBalancesPageClick?: () => void; + shouldCheckBalance: boolean; + shouldHideVisitBalancesLink?: boolean; } interface EthAmountInputState {} export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> { - public render() { - const amount = this.props.amount - ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) - : undefined; - return ( - <div className="flex overflow-hidden" style={{ height: 63 }}> - <BalanceBoundedInput - label={this.props.label} - balance={this.props.balance} - amount={amount} - onChange={this._onChange.bind(this)} - shouldCheckBalance={this.props.shouldCheckBalance} - shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} - /> - <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> - </div> - ); - } - private _onChange(isValid: boolean, amount?: BigNumber) { - const baseUnitAmountIfExists = _.isUndefined(amount) - ? undefined - : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); - this.props.onChange(isValid, baseUnitAmountIfExists); - } + public render() { + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) + : undefined; + return ( + <div className="flex overflow-hidden" style={{ height: 63 }}> + <BalanceBoundedInput + label={this.props.label} + balance={this.props.balance} + amount={amount} + onChange={this._onChange.bind(this)} + shouldCheckBalance={this.props.shouldCheckBalance} + shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} + shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} + /> + <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> + </div> + ); + } + private _onChange(isValid: boolean, amount?: BigNumber) { + const baseUnitAmountIfExists = _.isUndefined(amount) + ? undefined + : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); + this.props.onChange(isValid, baseUnitAmountIfExists); + } } diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx index cb4ed7bd0..e473648d2 100644 --- a/packages/website/ts/components/inputs/expiration_input.tsx +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -7,94 +7,94 @@ import * as React from 'react'; import { utils } from 'ts/utils/utils'; interface ExpirationInputProps { - orderExpiryTimestamp: BigNumber; - updateOrderExpiry: (unixTimestampSec: BigNumber) => void; + orderExpiryTimestamp: BigNumber; + updateOrderExpiry: (unixTimestampSec: BigNumber) => void; } interface ExpirationInputState { - dateMoment: moment.Moment; - timeMoment: moment.Moment; + dateMoment: moment.Moment; + timeMoment: moment.Moment; } export class ExpirationInput extends React.Component<ExpirationInputProps, ExpirationInputState> { - private _earliestPickableMoment: moment.Moment; - constructor(props: ExpirationInputProps) { - super(props); - // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates - this._earliestPickableMoment = moment().startOf('day'); - const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); - const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); - const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); - this.state = { - dateMoment: didUserSetExpiry ? expirationMoment : undefined, - timeMoment: didUserSetExpiry ? expirationMoment : undefined, - }; - } - public render() { - const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; - const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; - return ( - <div className="clearfix"> - <div className="col col-6 overflow-hidden pr3 flex relative"> - <DatePicker - className="overflow-hidden" - hintText="Date" - mode="landscape" - autoOk={true} - value={date} - onChange={this._onDateChanged.bind(this)} - shouldDisableDate={this._shouldDisableDate.bind(this)} - /> - <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}> - <i className="zmdi zmdi-calendar" /> - </div> - </div> - <div className="col col-5 overflow-hidden flex relative"> - <TimePicker - className="overflow-hidden" - hintText="Time" - autoOk={true} - value={time} - onChange={this._onTimeChanged.bind(this)} - /> - <div className="absolute" style={{ fontSize: 20, right: 9, top: 13, pointerEvents: 'none' }}> - <i className="zmdi zmdi-time" /> - </div> - </div> - <div onClick={this._clearDates.bind(this)} className="col col-1 pt2" style={{ textAlign: 'right' }}> - <i style={{ fontSize: 16, cursor: 'pointer' }} className="zmdi zmdi-close" /> - </div> - </div> - ); - } - private _shouldDisableDate(date: Date): boolean { - return moment(date) - .startOf('day') - .isBefore(this._earliestPickableMoment); - } - private _clearDates() { - this.setState({ - dateMoment: undefined, - timeMoment: undefined, - }); - const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); - this.props.updateOrderExpiry(defaultDateTime); - } - private _onDateChanged(e: any, date: Date) { - const dateMoment = moment(date); - this.setState({ - dateMoment, - }); - const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); - this.props.updateOrderExpiry(timestamp); - } - private _onTimeChanged(e: any, time: Date) { - const timeMoment = moment(time); - this.setState({ - timeMoment, - }); - const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; - const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); - this.props.updateOrderExpiry(timestamp); - } + private _earliestPickableMoment: moment.Moment; + constructor(props: ExpirationInputProps) { + super(props); + // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates + this._earliestPickableMoment = moment().startOf('day'); + const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); + const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); + this.state = { + dateMoment: didUserSetExpiry ? expirationMoment : undefined, + timeMoment: didUserSetExpiry ? expirationMoment : undefined, + }; + } + public render() { + const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; + const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; + return ( + <div className="clearfix"> + <div className="col col-6 overflow-hidden pr3 flex relative"> + <DatePicker + className="overflow-hidden" + hintText="Date" + mode="landscape" + autoOk={true} + value={date} + onChange={this._onDateChanged.bind(this)} + shouldDisableDate={this._shouldDisableDate.bind(this)} + /> + <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}> + <i className="zmdi zmdi-calendar" /> + </div> + </div> + <div className="col col-5 overflow-hidden flex relative"> + <TimePicker + className="overflow-hidden" + hintText="Time" + autoOk={true} + value={time} + onChange={this._onTimeChanged.bind(this)} + /> + <div className="absolute" style={{ fontSize: 20, right: 9, top: 13, pointerEvents: 'none' }}> + <i className="zmdi zmdi-time" /> + </div> + </div> + <div onClick={this._clearDates.bind(this)} className="col col-1 pt2" style={{ textAlign: 'right' }}> + <i style={{ fontSize: 16, cursor: 'pointer' }} className="zmdi zmdi-close" /> + </div> + </div> + ); + } + private _shouldDisableDate(date: Date): boolean { + return moment(date) + .startOf('day') + .isBefore(this._earliestPickableMoment); + } + private _clearDates() { + this.setState({ + dateMoment: undefined, + timeMoment: undefined, + }); + const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); + this.props.updateOrderExpiry(defaultDateTime); + } + private _onDateChanged(e: any, date: Date) { + const dateMoment = moment(date); + this.setState({ + dateMoment, + }); + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); + this.props.updateOrderExpiry(timestamp); + } + private _onTimeChanged(e: any, time: Date) { + const timeMoment = moment(time); + this.setState({ + timeMoment, + }); + const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); + this.props.updateOrderExpiry(timestamp); + } } diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx index 36d7e6140..5a3d34fe6 100644 --- a/packages/website/ts/components/inputs/hash_input.tsx +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -8,55 +8,55 @@ import { HashData, Styles } from 'ts/types'; import { constants } from 'ts/utils/constants'; const styles: Styles = { - textField: { - overflow: 'hidden', - paddingTop: 8, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }, + textField: { + overflow: 'hidden', + paddingTop: 8, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, }; interface HashInputProps { - blockchain: Blockchain; - blockchainIsLoaded: boolean; - hashData: HashData; - label: string; + blockchain: Blockchain; + blockchainIsLoaded: boolean; + hashData: HashData; + label: string; } interface HashInputState {} export class HashInput extends React.Component<HashInputProps, HashInputState> { - public render() { - const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; - return ( - <div> - <FakeTextField label={this.props.label}> - <div style={styles.textField} data-tip={true} data-for="hashTooltip"> - {msgHashHex} - </div> - </FakeTextField> - <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip> - </div> - ); - } - private _generateMessageHashHex() { - const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); - const hashData = this.props.hashData; - const order: Order = { - exchangeContractAddress, - expirationUnixTimestampSec: hashData.orderExpiryTimestamp, - feeRecipient: hashData.feeRecipientAddress, - maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress, - makerFee: hashData.makerFee, - makerTokenAddress: hashData.depositTokenContractAddr, - makerTokenAmount: hashData.depositAmount, - salt: hashData.orderSalt, - taker: hashData.orderTakerAddress, - takerFee: hashData.takerFee, - takerTokenAddress: hashData.receiveTokenContractAddr, - takerTokenAmount: hashData.receiveAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(order); - return orderHash; - } + public render() { + const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; + return ( + <div> + <FakeTextField label={this.props.label}> + <div style={styles.textField} data-tip={true} data-for="hashTooltip"> + {msgHashHex} + </div> + </FakeTextField> + <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip> + </div> + ); + } + private _generateMessageHashHex() { + const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); + const hashData = this.props.hashData; + const order: Order = { + exchangeContractAddress, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + return orderHash; + } } diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx index f14cb4e9c..4cf9af64d 100644 --- a/packages/website/ts/components/inputs/identicon_address_input.tsx +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -6,48 +6,48 @@ import { InputLabel } from 'ts/components/ui/input_label'; import { RequiredLabel } from 'ts/components/ui/required_label'; interface IdenticonAddressInputProps { - initialAddress: string; - isRequired?: boolean; - label: string; - updateOrderAddress: (address?: string) => void; + initialAddress: string; + isRequired?: boolean; + label: string; + updateOrderAddress: (address?: string) => void; } interface IdenticonAddressInputState { - address: string; + address: string; } export class IdenticonAddressInput extends React.Component<IdenticonAddressInputProps, IdenticonAddressInputState> { - constructor(props: IdenticonAddressInputProps) { - super(props); - this.state = { - address: props.initialAddress, - }; - } - public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; - return ( - <div className="relative" style={{ width: '100%' }}> - <InputLabel text={label} /> - <div className="flex"> - <div className="col col-1 pb1 pr1" style={{ paddingTop: 13 }}> - <Identicon address={this.state.address} diameter={26} /> - </div> - <div className="col col-11 pb1 pl1" style={{ height: 65 }}> - <AddressInput - hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..." - shouldHideLabel={true} - initialAddress={this.props.initialAddress} - updateAddress={this._updateAddress.bind(this)} - /> - </div> - </div> - </div> - ); - } - private _updateAddress(address?: string): void { - this.setState({ - address, - }); - this.props.updateOrderAddress(address); - } + constructor(props: IdenticonAddressInputProps) { + super(props); + this.state = { + address: props.initialAddress, + }; + } + public render() { + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; + return ( + <div className="relative" style={{ width: '100%' }}> + <InputLabel text={label} /> + <div className="flex"> + <div className="col col-1 pb1 pr1" style={{ paddingTop: 13 }}> + <Identicon address={this.state.address} diameter={26} /> + </div> + <div className="col col-11 pb1 pl1" style={{ height: 65 }}> + <AddressInput + hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..." + shouldHideLabel={true} + initialAddress={this.props.initialAddress} + updateAddress={this._updateAddress.bind(this)} + /> + </div> + </div> + </div> + ); + } + private _updateAddress(address?: string): void { + this.setState({ + address, + }); + this.props.updateOrderAddress(address); + } } diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 0a71b2c00..63966d759 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -8,63 +8,63 @@ import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePath import { colors } from 'ts/utils/colors'; interface TokenAmountInputProps { - token: Token; - tokenState: TokenState; - label?: string; - amount?: BigNumber; - shouldShowIncompleteErrs: boolean; - shouldCheckBalance: boolean; - shouldCheckAllowance: boolean; - onChange: ValidatedBigNumberCallback; - onVisitBalancesPageClick?: () => void; + token: Token; + tokenState: TokenState; + label?: string; + amount?: BigNumber; + shouldShowIncompleteErrs: boolean; + shouldCheckBalance: boolean; + shouldCheckAllowance: boolean; + onChange: ValidatedBigNumberCallback; + onVisitBalancesPageClick?: () => void; } interface TokenAmountInputState {} export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> { - public render() { - const amount = this.props.amount - ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) - : undefined; - const hasLabel = !_.isUndefined(this.props.label); - return ( - <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}> - <BalanceBoundedInput - label={this.props.label} - amount={amount} - balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} - onChange={this._onChange.bind(this)} - validate={this._validate.bind(this)} - shouldCheckBalance={this.props.shouldCheckBalance} - shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - /> - <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> - </div> - ); - } - private _onChange(isValid: boolean, amount?: BigNumber) { - let baseUnitAmount; - if (!_.isUndefined(amount)) { - baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); - } - this.props.onChange(isValid, baseUnitAmount); - } - private _validate(amount: BigNumber): InputErrMsg { - if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { - return ( - <span> - Insufficient allowance.{' '} - <Link - to={`${WebsitePaths.Portal}/balances`} - style={{ cursor: 'pointer', color: colors.darkestGrey }} - > - Set allowance - </Link> - </span> - ); - } else { - return undefined; - } - } + public render() { + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) + : undefined; + const hasLabel = !_.isUndefined(this.props.label); + return ( + <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}> + <BalanceBoundedInput + label={this.props.label} + amount={amount} + balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} + onChange={this._onChange.bind(this)} + validate={this._validate.bind(this)} + shouldCheckBalance={this.props.shouldCheckBalance} + shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} + /> + <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> + </div> + ); + } + private _onChange(isValid: boolean, amount?: BigNumber) { + let baseUnitAmount; + if (!_.isUndefined(amount)) { + baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); + } + this.props.onChange(isValid, baseUnitAmount); + } + private _validate(amount: BigNumber): InputErrMsg { + if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { + return ( + <span> + Insufficient allowance.{' '} + <Link + to={`${WebsitePaths.Portal}/balances`} + style={{ cursor: 'pointer', color: colors.darkestGrey }} + > + Set allowance + </Link> + </span> + ); + } else { + return undefined; + } + } } diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index 3aceacb22..5df19b28c 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -12,93 +12,93 @@ import { colors } from 'ts/utils/colors'; const TOKEN_ICON_DIMENSION = 80; interface TokenInputProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - dispatcher: Dispatcher; - label: string; - side: Side; - networkId: number; - assetToken: AssetToken; - updateChosenAssetToken: (side: Side, token: AssetToken) => void; - tokenByAddress: TokenByAddress; - userAddress: string; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + dispatcher: Dispatcher; + label: string; + side: Side; + networkId: number; + assetToken: AssetToken; + updateChosenAssetToken: (side: Side, token: AssetToken) => void; + tokenByAddress: TokenByAddress; + userAddress: string; } interface TokenInputState { - isHoveringIcon: boolean; - isPickerOpen: boolean; - trackCandidateTokenIfExists?: Token; + isHoveringIcon: boolean; + isPickerOpen: boolean; + trackCandidateTokenIfExists?: Token; } export class TokenInput extends React.Component<TokenInputProps, TokenInputState> { - constructor(props: TokenInputProps) { - super(props); - this.state = { - isHoveringIcon: false, - isPickerOpen: false, - }; - } - public render() { - const token = this.props.tokenByAddress[this.props.assetToken.address]; - const iconStyles = { - cursor: 'pointer', - opacity: this.state.isHoveringIcon ? 0.5 : 1, - }; - return ( - <div className="relative"> - <div className="pb1"> - <InputLabel text={this.props.label} /> - </div> - <Paper - zDepth={1} - style={{ cursor: 'pointer' }} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - onClick={this._onAssetClicked.bind(this)} - > - <div className="mx-auto pt2" style={{ width: TOKEN_ICON_DIMENSION, ...iconStyles }}> - <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> - </div> - <div className="py1 center" style={{ color: colors.grey }}> - {token.name} - </div> - </Paper> - <AssetPicker - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - isOpen={this.state.isPickerOpen} - currentTokenAddress={this.props.assetToken.address} - onTokenChosen={this._onTokenChosen.bind(this)} - tokenByAddress={this.props.tokenByAddress} - /> - </div> - ); - } - private _onTokenChosen(tokenAddress: string) { - const assetToken: AssetToken = { - address: tokenAddress, - amount: this.props.assetToken.amount, - }; - this.props.updateChosenAssetToken(this.props.side, assetToken); - this.setState({ - isPickerOpen: false, - }); - } - private _onToggleHover(isHoveringIcon: boolean) { - this.setState({ - isHoveringIcon, - }); - } - private _onAssetClicked() { - if (this.props.blockchainErr !== BlockchainErrs.NoError) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + constructor(props: TokenInputProps) { + super(props); + this.state = { + isHoveringIcon: false, + isPickerOpen: false, + }; + } + public render() { + const token = this.props.tokenByAddress[this.props.assetToken.address]; + const iconStyles = { + cursor: 'pointer', + opacity: this.state.isHoveringIcon ? 0.5 : 1, + }; + return ( + <div className="relative"> + <div className="pb1"> + <InputLabel text={this.props.label} /> + </div> + <Paper + zDepth={1} + style={{ cursor: 'pointer' }} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + onClick={this._onAssetClicked.bind(this)} + > + <div className="mx-auto pt2" style={{ width: TOKEN_ICON_DIMENSION, ...iconStyles }}> + <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> + </div> + <div className="py1 center" style={{ color: colors.grey }}> + {token.name} + </div> + </Paper> + <AssetPicker + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + isOpen={this.state.isPickerOpen} + currentTokenAddress={this.props.assetToken.address} + onTokenChosen={this._onTokenChosen.bind(this)} + tokenByAddress={this.props.tokenByAddress} + /> + </div> + ); + } + private _onTokenChosen(tokenAddress: string) { + const assetToken: AssetToken = { + address: tokenAddress, + amount: this.props.assetToken.amount, + }; + this.props.updateChosenAssetToken(this.props.side, assetToken); + this.setState({ + isPickerOpen: false, + }); + } + private _onToggleHover(isHoveringIcon: boolean) { + this.setState({ + isHoveringIcon, + }); + } + private _onAssetClicked() { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isPickerOpen: true, - }); - } + this.setState({ + isPickerOpen: true, + }); + } } diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx index 1640a178e..1b6b32a04 100644 --- a/packages/website/ts/components/order_json.tsx +++ b/packages/website/ts/components/order_json.tsx @@ -11,172 +11,172 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface OrderJSONProps { - exchangeContractIfExists: string; - orderExpiryTimestamp: BigNumber; - orderSignatureData: SignatureData; - orderTakerAddress: string; - orderMakerAddress: string; - orderSalt: BigNumber; - orderMakerFee: BigNumber; - orderTakerFee: BigNumber; - orderFeeRecipient: string; - networkId: number; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; + exchangeContractIfExists: string; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderMakerAddress: string; + orderSalt: BigNumber; + orderMakerFee: BigNumber; + orderTakerFee: BigNumber; + orderFeeRecipient: string; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; } interface OrderJSONState { - shareLink: string; + shareLink: string; } export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { - constructor(props: OrderJSONProps) { - super(props); - this.state = { - shareLink: '', - }; - // tslint:disable-next-line:no-floating-promises - this._setShareLinkAsync(); - } - public render() { - const order = utils.generateOrder( - this.props.networkId, - this.props.exchangeContractIfExists, - this.props.sideToAssetToken, - this.props.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.orderMakerAddress, - this.props.orderMakerFee, - this.props.orderTakerFee, - this.props.orderFeeRecipient, - this.props.orderSignatureData, - this.props.tokenByAddress, - this.props.orderSalt, - ); - const orderJSON = JSON.stringify(order); - return ( - <div> - <div className="pb2"> - You have successfully generated and cryptographically signed an order! The following JSON contains - the order parameters and cryptographic signature that your counterparty will need to execute a trade - with you. - </div> - <div className="pb2 flex"> - <div className="inline-block pl1" style={{ top: 1 }}> - <CopyIcon data={orderJSON} callToAction="Copy" /> - </div> - </div> - <Paper className="center overflow-hidden"> - <TextField - id="orderJSON" - style={{ width: 710 }} - value={JSON.stringify(order, null, '\t')} - multiLine={true} - rows={2} - rowsMax={8} - underlineStyle={{ display: 'none' }} - /> - </Paper> - <div className="pt3 pb2 center"> - <div>Share your signed order!</div> - <div> - <div className="mx-auto overflow-hidden" style={{ width: 152 }}> - <TextField id={`${this.state.shareLink}-bitly`} value={this.state.shareLink} /> - </div> - </div> - <div className="mx-auto pt1 flex" style={{ width: 91 }}> - <div> - <i - style={{ cursor: 'pointer', fontSize: 29 }} - onClick={this._shareViaFacebook.bind(this)} - className="zmdi zmdi-facebook-box" - /> - </div> - <div className="pl1" style={{ position: 'relative', width: 28 }}> - <i - style={{ - cursor: 'pointer', - fontSize: 32, - position: 'absolute', - top: -2, - left: 8, - }} - onClick={this._shareViaEmailAsync.bind(this)} - className="zmdi zmdi-email" - /> - </div> - <div className="pl1"> - <i - style={{ cursor: 'pointer', fontSize: 29 }} - onClick={this._shareViaTwitterAsync.bind(this)} - className="zmdi zmdi-twitter-box" - /> - </div> - </div> - </div> - </div> - ); - } - private async _shareViaTwitterAsync() { - const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); - window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); - } - private async _shareViaFacebook() { - (window as any).FB.ui( - { - display: 'popup', - href: this.state.shareLink, - method: 'share', - }, - _.noop, - ); - } - private async _shareViaEmailAsync() { - const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); - const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. + constructor(props: OrderJSONProps) { + super(props); + this.state = { + shareLink: '', + }; + // tslint:disable-next-line:no-floating-promises + this._setShareLinkAsync(); + } + public render() { + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); + const orderJSON = JSON.stringify(order); + return ( + <div> + <div className="pb2"> + You have successfully generated and cryptographically signed an order! The following JSON contains + the order parameters and cryptographic signature that your counterparty will need to execute a trade + with you. + </div> + <div className="pb2 flex"> + <div className="inline-block pl1" style={{ top: 1 }}> + <CopyIcon data={orderJSON} callToAction="Copy" /> + </div> + </div> + <Paper className="center overflow-hidden"> + <TextField + id="orderJSON" + style={{ width: 710 }} + value={JSON.stringify(order, null, '\t')} + multiLine={true} + rows={2} + rowsMax={8} + underlineStyle={{ display: 'none' }} + /> + </Paper> + <div className="pt3 pb2 center"> + <div>Share your signed order!</div> + <div> + <div className="mx-auto overflow-hidden" style={{ width: 152 }}> + <TextField id={`${this.state.shareLink}-bitly`} value={this.state.shareLink} /> + </div> + </div> + <div className="mx-auto pt1 flex" style={{ width: 91 }}> + <div> + <i + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaFacebook.bind(this)} + className="zmdi zmdi-facebook-box" + /> + </div> + <div className="pl1" style={{ position: 'relative', width: 28 }}> + <i + style={{ + cursor: 'pointer', + fontSize: 32, + position: 'absolute', + top: -2, + left: 8, + }} + onClick={this._shareViaEmailAsync.bind(this)} + className="zmdi zmdi-email" + /> + </div> + <div className="pl1"> + <i + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaTwitterAsync.bind(this)} + className="zmdi zmdi-twitter-box" + /> + </div> + </div> + </div> + </div> + ); + } + private async _shareViaTwitterAsync() { + const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); + window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); + } + private async _shareViaFacebook() { + (window as any).FB.ui( + { + display: 'popup', + href: this.state.shareLink, + method: 'share', + }, + _.noop, + ); + } + private async _shareViaEmailAsync() { + const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); + const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. You can see and fill it here: ${this.state.shareLink}`); - const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; - window.open(mailToLink, '_blank'); - } - private async _setShareLinkAsync() { - const shareLink = await this._generateShareLinkAsync(); - this.setState({ - shareLink, - }); - } - private async _generateShareLinkAsync(): Promise<string> { - const longUrl = encodeURIComponent(this._getOrderUrl()); - const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ - configs.BITLY_ACCESS_TOKEN - }&longUrl=${longUrl}`; - const response = await fetch(bitlyRequestUrl); - const responseBody = await response.text(); - const bodyObj = JSON.parse(responseBody); - if (response.status !== 200 || bodyObj.status_code !== 200) { - // TODO: Show error message in UI - utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); - await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); - return ''; - } - return bodyObj.data.url; - } - private _getOrderUrl() { - const order = utils.generateOrder( - this.props.networkId, - this.props.exchangeContractIfExists, - this.props.sideToAssetToken, - this.props.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.orderMakerAddress, - this.props.orderMakerFee, - this.props.orderTakerFee, - this.props.orderFeeRecipient, - this.props.orderSignatureData, - this.props.tokenByAddress, - this.props.orderSalt, - ); - const orderJSONString = JSON.stringify(order); - const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; - return orderUrl; - } + const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; + window.open(mailToLink, '_blank'); + } + private async _setShareLinkAsync() { + const shareLink = await this._generateShareLinkAsync(); + this.setState({ + shareLink, + }); + } + private async _generateShareLinkAsync(): Promise<string> { + const longUrl = encodeURIComponent(this._getOrderUrl()); + const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ + configs.BITLY_ACCESS_TOKEN + }&longUrl=${longUrl}`; + const response = await fetch(bitlyRequestUrl); + const responseBody = await response.text(); + const bodyObj = JSON.parse(responseBody); + if (response.status !== 200 || bodyObj.status_code !== 200) { + // TODO: Show error message in UI + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); + return ''; + } + return bodyObj.data.url; + } + private _getOrderUrl() { + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); + const orderJSONString = JSON.stringify(order); + const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; + return orderUrl; + } } diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index e163a1fa2..e2e28e8b6 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -23,14 +23,14 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; import { - BlockchainErrs, - HashData, - Order, - ScreenWidths, - Token, - TokenByAddress, - TokenStateByAddress, - WebsitePaths, + BlockchainErrs, + HashData, + Order, + ScreenWidths, + Token, + TokenByAddress, + TokenStateByAddress, + WebsitePaths, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -42,320 +42,320 @@ const THROTTLE_TIMEOUT = 100; export interface PortalPassedProps {} export interface PortalAllProps { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - hashData: HashData; - networkId: number; - nodeVersion: string; - orderFillAmount: BigNumber; - screenWidth: ScreenWidths; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userEtherBalance: BigNumber; - userAddress: string; - shouldBlockchainErrDialogBeOpen: boolean; - userSuppliedOrderCache: Order; - location: Location; - flashMessage?: string | React.ReactNode; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + userAddress: string; + shouldBlockchainErrDialogBeOpen: boolean; + userSuppliedOrderCache: Order; + location: Location; + flashMessage?: string | React.ReactNode; } interface PortalAllState { - prevNetworkId: number; - prevNodeVersion: string; - prevUserAddress: string; - prevPathname: string; - isDisclaimerDialogOpen: boolean; - isWethNoticeDialogOpen: boolean; + prevNetworkId: number; + prevNodeVersion: string; + prevUserAddress: string; + prevPathname: string; + isDisclaimerDialogOpen: boolean; + isWethNoticeDialogOpen: boolean; } export class Portal extends React.Component<PortalAllProps, PortalAllState> { - private _blockchain: Blockchain; - private _sharedOrderIfExists: Order; - private _throttledScreenWidthUpdate: () => void; - public static hasAlreadyDismissedWethNotice() { - const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); - const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); - return hasAlreadyDismissedWethNotice; - } - constructor(props: PortalAllProps) { - super(props); - this._sharedOrderIfExists = this._getSharedOrderIfExists(); - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + private _blockchain: Blockchain; + private _sharedOrderIfExists: Order; + private _throttledScreenWidthUpdate: () => void; + public static hasAlreadyDismissedWethNotice() { + const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); + const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); + return hasAlreadyDismissedWethNotice; + } + constructor(props: PortalAllProps) { + super(props); + this._sharedOrderIfExists = this._getSharedOrderIfExists(); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); - const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); - const hasAcceptedDisclaimer = - !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); - this.state = { - prevNetworkId: this.props.networkId, - prevNodeVersion: this.props.nodeVersion, - prevUserAddress: this.props.userAddress, - prevPathname: this.props.location.pathname, - isDisclaimerDialogOpen: !hasAcceptedDisclaimer, - isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, - }; - } - public componentDidMount() { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillMount() { - this._blockchain = new Blockchain(this.props.dispatcher); - } - public componentWillUnmount() { - this._blockchain.destroy(); - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered - // the initialization process always occurs from the same base state. This helps avoid - // initialization inconsistencies (i.e While the portal was unrendered, the user might have - // become disconnected from their backing Ethereum node, changes user accounts, etc...) - this.props.dispatcher.resetState(); - } - public componentWillReceiveProps(nextProps: PortalAllProps) { - if (nextProps.networkId !== this.state.prevNetworkId) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); - this.setState({ - prevNetworkId: nextProps.networkId, - }); - } - if (nextProps.userAddress !== this.state.prevUserAddress) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); - if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { - const tokens = _.values(nextProps.tokenByAddress); - // tslint:disable-next-line:no-floating-promises - this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); - } - this.setState({ - prevUserAddress: nextProps.userAddress, - }); - } - if (nextProps.nodeVersion !== this.state.prevNodeVersion) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); - } - if (nextProps.location.pathname !== this.state.prevPathname) { - const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); - this.setState({ - prevPathname: nextProps.location.pathname, - isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, - }); - } - } - public render() { - const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( - this.props.dispatcher, - ); - const portalStyle: React.CSSProperties = { - minHeight: '100vh', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - }; - const portalMenuContainerStyle: React.CSSProperties = { - overflow: 'hidden', - backgroundColor: colors.darkestGrey, - color: colors.white, - }; - return ( - <div style={portalStyle}> - <DocumentTitle title="0x Portal DApp" /> - <TopBar - userAddress={this.props.userAddress} - blockchainIsLoaded={this.props.blockchainIsLoaded} - location={this.props.location} - /> - <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> - <Paper className="mb3 mt2"> - {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( - <div className="p3 center"> - <div className="h2 py2">Mainnet unavailable</div> - <div className="mx-auto pb2 pt2"> - <img src="/images/zrx_token.png" style={{ width: 150 }} /> - </div> - <div> - 0x portal is currently unavailable on the Ethereum mainnet. - <div>To try it out, switch to the Kovan test network (networkId: 42).</div> - <div className="py2">Check back soon!</div> - </div> - </div> - ) : ( - <div className="mx-auto flex"> - <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> - <PortalMenu menuItemStyle={{ color: colors.white }} /> - </div> - <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> - <div className="py2" style={{ backgroundColor: colors.grey50 }}> - {this.props.blockchainIsLoaded ? ( - <Switch> - <Route - path={`${WebsitePaths.Portal}/weth`} - render={this._renderEthWrapper.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/fill`} - render={this._renderFillOrder.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/balances`} - render={this._renderTokenBalances.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/trades`} - component={this._renderTradeHistory.bind(this)} - /> - <Route - path={`${WebsitePaths.Home}`} - render={this._renderGenerateOrderForm.bind(this)} - /> - </Switch> - ) : ( - <Loading /> - )} - </div> - </div> - </div> - )} - </Paper> - <BlockchainErrDialog - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - isOpen={this.props.shouldBlockchainErrDialogBeOpen} - userAddress={this.props.userAddress} - toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} - networkId={this.props.networkId} - /> - <WrappedEthSectionNoticeDialog - isOpen={this.state.isWethNoticeDialogOpen} - onToggleDialog={this._onWethNoticeAccepted.bind(this)} - /> - <PortalDisclaimerDialog - isOpen={this.state.isDisclaimerDialogOpen} - onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} - /> - <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> - </div> - <Footer /> - </div> - ); - } - private _renderEthWrapper() { - return ( - <EthWrappers - networkId={this.props.networkId} - blockchain={this._blockchain} - dispatcher={this.props.dispatcher} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} - /> - ); - } - private _renderTradeHistory() { - return ( - <TradeHistory - tokenByAddress={this.props.tokenByAddress} - userAddress={this.props.userAddress} - networkId={this.props.networkId} - /> - ); - } - private _renderTokenBalances() { - return ( - <TokenBalances - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - blockchainIsLoaded={this.props.blockchainIsLoaded} - dispatcher={this.props.dispatcher} - screenWidth={this.props.screenWidth} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} - networkId={this.props.networkId} - /> - ); - } - private _renderFillOrder(match: any, location: Location, history: History) { - const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) - ? this.props.userSuppliedOrderCache - : this._sharedOrderIfExists; - return ( - <FillOrder - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - initialOrder={initialFillOrder} - isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)} - orderFillAmount={this.props.orderFillAmount} - networkId={this.props.networkId} - userAddress={this.props.userAddress} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - dispatcher={this.props.dispatcher} - /> - ); - } - private _renderGenerateOrderForm(match: any, location: Location, history: History) { - return ( - <GenerateOrderForm - blockchain={this._blockchain} - hashData={this.props.hashData} - dispatcher={this.props.dispatcher} - /> - ); - } - private _onPortalDisclaimerAccepted() { - localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); - this.setState({ - isDisclaimerDialogOpen: false, - }); - } - private _onWethNoticeAccepted() { - localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); - this.setState({ - isWethNoticeDialogOpen: false, - }); - } - private _getSharedOrderIfExists(): Order | undefined { - const queryString = window.location.search; - if (queryString.length === 0) { - return undefined; - } - const queryParams = queryString.substring(1).split('&'); - const orderQueryParam = _.find(queryParams, queryParam => { - const queryPair = queryParam.split('='); - return queryPair[0] === 'order'; - }); - if (_.isUndefined(orderQueryParam)) { - return undefined; - } - const orderPair = orderQueryParam.split('='); - if (orderPair.length !== 2) { - return undefined; - } + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); + const hasAcceptedDisclaimer = + !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); + this.state = { + prevNetworkId: this.props.networkId, + prevNodeVersion: this.props.nodeVersion, + prevUserAddress: this.props.userAddress, + prevPathname: this.props.location.pathname, + isDisclaimerDialogOpen: !hasAcceptedDisclaimer, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }; + } + public componentDidMount() { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillMount() { + this._blockchain = new Blockchain(this.props.dispatcher); + } + public componentWillUnmount() { + this._blockchain.destroy(); + window.removeEventListener('resize', this._throttledScreenWidthUpdate); + // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered + // the initialization process always occurs from the same base state. This helps avoid + // initialization inconsistencies (i.e While the portal was unrendered, the user might have + // become disconnected from their backing Ethereum node, changes user accounts, etc...) + this.props.dispatcher.resetState(); + } + public componentWillReceiveProps(nextProps: PortalAllProps) { + if (nextProps.networkId !== this.state.prevNetworkId) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this.setState({ + prevNetworkId: nextProps.networkId, + }); + } + if (nextProps.userAddress !== this.state.prevUserAddress) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { + const tokens = _.values(nextProps.tokenByAddress); + // tslint:disable-next-line:no-floating-promises + this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + } + this.setState({ + prevUserAddress: nextProps.userAddress, + }); + } + if (nextProps.nodeVersion !== this.state.prevNodeVersion) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + if (nextProps.location.pathname !== this.state.prevPathname) { + const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + this.setState({ + prevPathname: nextProps.location.pathname, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }); + } + } + public render() { + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( + this.props.dispatcher, + ); + const portalStyle: React.CSSProperties = { + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }; + const portalMenuContainerStyle: React.CSSProperties = { + overflow: 'hidden', + backgroundColor: colors.darkestGrey, + color: colors.white, + }; + return ( + <div style={portalStyle}> + <DocumentTitle title="0x Portal DApp" /> + <TopBar + userAddress={this.props.userAddress} + blockchainIsLoaded={this.props.blockchainIsLoaded} + location={this.props.location} + /> + <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> + <Paper className="mb3 mt2"> + {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( + <div className="p3 center"> + <div className="h2 py2">Mainnet unavailable</div> + <div className="mx-auto pb2 pt2"> + <img src="/images/zrx_token.png" style={{ width: 150 }} /> + </div> + <div> + 0x portal is currently unavailable on the Ethereum mainnet. + <div>To try it out, switch to the Kovan test network (networkId: 42).</div> + <div className="py2">Check back soon!</div> + </div> + </div> + ) : ( + <div className="mx-auto flex"> + <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> + <PortalMenu menuItemStyle={{ color: colors.white }} /> + </div> + <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> + <div className="py2" style={{ backgroundColor: colors.grey50 }}> + {this.props.blockchainIsLoaded ? ( + <Switch> + <Route + path={`${WebsitePaths.Portal}/weth`} + render={this._renderEthWrapper.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/fill`} + render={this._renderFillOrder.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/balances`} + render={this._renderTokenBalances.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/trades`} + component={this._renderTradeHistory.bind(this)} + /> + <Route + path={`${WebsitePaths.Home}`} + render={this._renderGenerateOrderForm.bind(this)} + /> + </Switch> + ) : ( + <Loading /> + )} + </div> + </div> + </div> + )} + </Paper> + <BlockchainErrDialog + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + isOpen={this.props.shouldBlockchainErrDialogBeOpen} + userAddress={this.props.userAddress} + toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} + networkId={this.props.networkId} + /> + <WrappedEthSectionNoticeDialog + isOpen={this.state.isWethNoticeDialogOpen} + onToggleDialog={this._onWethNoticeAccepted.bind(this)} + /> + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} + /> + <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> + </div> + <Footer /> + </div> + ); + } + private _renderEthWrapper() { + return ( + <EthWrappers + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + userAddress={this.props.userAddress} + userEtherBalance={this.props.userEtherBalance} + /> + ); + } + private _renderTradeHistory() { + return ( + <TradeHistory + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + /> + ); + } + private _renderTokenBalances() { + return ( + <TokenBalances + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + blockchainIsLoaded={this.props.blockchainIsLoaded} + dispatcher={this.props.dispatcher} + screenWidth={this.props.screenWidth} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + userAddress={this.props.userAddress} + userEtherBalance={this.props.userEtherBalance} + networkId={this.props.networkId} + /> + ); + } + private _renderFillOrder(match: any, location: Location, history: History) { + const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) + ? this.props.userSuppliedOrderCache + : this._sharedOrderIfExists; + return ( + <FillOrder + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + initialOrder={initialFillOrder} + isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)} + orderFillAmount={this.props.orderFillAmount} + networkId={this.props.networkId} + userAddress={this.props.userAddress} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + dispatcher={this.props.dispatcher} + /> + ); + } + private _renderGenerateOrderForm(match: any, location: Location, history: History) { + return ( + <GenerateOrderForm + blockchain={this._blockchain} + hashData={this.props.hashData} + dispatcher={this.props.dispatcher} + /> + ); + } + private _onPortalDisclaimerAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); + this.setState({ + isDisclaimerDialogOpen: false, + }); + } + private _onWethNoticeAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); + this.setState({ + isWethNoticeDialogOpen: false, + }); + } + private _getSharedOrderIfExists(): Order | undefined { + const queryString = window.location.search; + if (queryString.length === 0) { + return undefined; + } + const queryParams = queryString.substring(1).split('&'); + const orderQueryParam = _.find(queryParams, queryParam => { + const queryPair = queryParam.split('='); + return queryPair[0] === 'order'; + }); + if (_.isUndefined(orderQueryParam)) { + return undefined; + } + const orderPair = orderQueryParam.split('='); + if (orderPair.length !== 2) { + return undefined; + } - const validator = new SchemaValidator(); - const order = JSON.parse(decodeURIComponent(orderPair[1])); - const validationResult = validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); - return undefined; - } - return order; - } - private _updateScreenWidth() { - const newScreenWidth = utils.getScreenWidth(); - this.props.dispatcher.updateScreenWidth(newScreenWidth); - } - private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { - this.props.dispatcher.updateBlockchainIsLoaded(false); - await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.props.dispatcher.updateBlockchainIsLoaded(true); - } + const validator = new SchemaValidator(); + const order = JSON.parse(decodeURIComponent(orderPair[1])); + const validationResult = validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); + return undefined; + } + return order; + } + private _updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { + this.props.dispatcher.updateBlockchainIsLoaded(false); + await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.props.dispatcher.updateBlockchainIsLoaded(true); + } } diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx index b025f527e..a2f9340c8 100644 --- a/packages/website/ts/components/portal_menu.tsx +++ b/packages/website/ts/components/portal_menu.tsx @@ -4,70 +4,70 @@ import { MenuItem } from 'ts/components/ui/menu_item'; import { WebsitePaths } from 'ts/types'; export interface PortalMenuProps { - menuItemStyle: React.CSSProperties; - onClick?: () => void; + menuItemStyle: React.CSSProperties; + onClick?: () => void; } interface PortalMenuState {} export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState> { - public static defaultProps: Partial<PortalMenuProps> = { - onClick: _.noop, - }; - public render() { - return ( - <div> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/fill`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/balances`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/trades`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/weth`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} - </MenuItem> - </div> - ); - } - private _renderMenuItemWithIcon(title: string, iconName: string) { - return ( - <div className="flex" style={{ fontWeight: 100 }}> - <div className="pr1 pl2"> - <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} /> - </div> - <div className="pl1">{title}</div> - </div> - ); - } + public static defaultProps: Partial<PortalMenuProps> = { + onClick: _.noop, + }; + public render() { + return ( + <div> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/fill`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/balances`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/trades`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/weth`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} + </MenuItem> + </div> + ); + } + private _renderMenuItemWithIcon(title: string, iconName: string) { + return ( + <div className="flex" style={{ fontWeight: 100 }}> + <div className="pr1 pl2"> + <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} /> + </div> + <div className="pl1">{title}</div> + </div> + ); + } } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index f97d9250b..f94ec346a 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -10,78 +10,78 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface SendButtonProps { - token: Token; - tokenState: TokenState; - dispatcher: Dispatcher; - blockchain: Blockchain; - onError: () => void; + token: Token; + tokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + onError: () => void; } interface SendButtonState { - isSendDialogVisible: boolean; - isSending: boolean; + isSendDialogVisible: boolean; + isSending: boolean; } export class SendButton extends React.Component<SendButtonProps, SendButtonState> { - public constructor(props: SendButtonProps) { - super(props); - this.state = { - isSendDialogVisible: false, - isSending: false, - }; - } - public render() { - const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; - return ( - <div> - <RaisedButton - style={{ width: '100%' }} - labelStyle={labelStyle} - disabled={this.state.isSending} - label={this.state.isSending ? 'Sending...' : 'Send'} - onClick={this._toggleSendDialog.bind(this)} - /> - <SendDialog - isOpen={this.state.isSendDialogVisible} - onComplete={this._onSendAmountSelectedAsync.bind(this)} - onCancelled={this._toggleSendDialog.bind(this)} - token={this.props.token} - tokenState={this.props.tokenState} - /> - </div> - ); - } - private _toggleSendDialog() { - this.setState({ - isSendDialogVisible: !this.state.isSendDialogVisible, - }); - } - private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { - this.setState({ - isSending: true, - }); - this._toggleSendDialog(); - const token = this.props.token; - const tokenState = this.props.tokenState; - let balance = tokenState.balance; - try { - await this.props.blockchain.transferAsync(token, recipient, value); - balance = balance.minus(value); - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } else if (!_.includes(errMsg, 'User denied transaction')) { - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.props.onError(); - await errorReporter.reportAsync(err); - } - } - this.setState({ - isSending: false, - }); - } + public constructor(props: SendButtonProps) { + super(props); + this.state = { + isSendDialogVisible: false, + isSending: false, + }; + } + public render() { + const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; + return ( + <div> + <RaisedButton + style={{ width: '100%' }} + labelStyle={labelStyle} + disabled={this.state.isSending} + label={this.state.isSending ? 'Sending...' : 'Send'} + onClick={this._toggleSendDialog.bind(this)} + /> + <SendDialog + isOpen={this.state.isSendDialogVisible} + onComplete={this._onSendAmountSelectedAsync.bind(this)} + onCancelled={this._toggleSendDialog.bind(this)} + token={this.props.token} + tokenState={this.props.tokenState} + /> + </div> + ); + } + private _toggleSendDialog() { + this.setState({ + isSendDialogVisible: !this.state.isSendDialogVisible, + }); + } + private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + this.setState({ + isSending: true, + }); + this._toggleSendDialog(); + const token = this.props.token; + const tokenState = this.props.tokenState; + let balance = tokenState.balance; + try { + await this.props.blockchain.transferAsync(token, recipient, value); + balance = balance.minus(value); + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.props.onError(); + await errorReporter.reportAsync(err); + } + } + this.setState({ + isSending: false, + }); + } } diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 480652c34..2cef413c7 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -23,16 +23,16 @@ import { TokenIcon } from 'ts/components/ui/token_icon'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - BalanceErrs, - BlockchainCallErrs, - BlockchainErrs, - EtherscanLinkSuffixes, - ScreenWidths, - Styles, - Token, - TokenByAddress, - TokenStateByAddress, - TokenVisibility, + BalanceErrs, + BlockchainCallErrs, + BlockchainErrs, + EtherscanLinkSuffixes, + ScreenWidths, + Styles, + Token, + TokenByAddress, + TokenStateByAddress, + TokenVisibility, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -53,554 +53,554 @@ const TOKEN_COL_SPAN_LG = 2; const TOKEN_COL_SPAN_SM = 1; const styles: Styles = { - bgColor: { - backgroundColor: colors.grey50, - }, + bgColor: { + backgroundColor: colors.grey50, + }, }; interface TokenBalancesProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - screenWidth: ScreenWidths; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; - networkId: number; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + networkId: number; } interface TokenBalancesState { - errorType: BalanceErrs; - isBalanceSpinnerVisible: boolean; - isDharmaDialogVisible: boolean; - isZRXSpinnerVisible: boolean; - currentZrxBalance?: BigNumber; - isTokenPickerOpen: boolean; - isAddingToken: boolean; + errorType: BalanceErrs; + isBalanceSpinnerVisible: boolean; + isDharmaDialogVisible: boolean; + isZRXSpinnerVisible: boolean; + currentZrxBalance?: BigNumber; + isTokenPickerOpen: boolean; + isAddingToken: boolean; } export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> { - public constructor(props: TokenBalancesProps) { - super(props); - this.state = { - errorType: undefined, - isBalanceSpinnerVisible: false, - isZRXSpinnerVisible: false, - isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), - isTokenPickerOpen: false, - isAddingToken: false, - }; - } - public componentWillReceiveProps(nextProps: TokenBalancesProps) { - if (nextProps.userEtherBalance !== this.props.userEtherBalance) { - if (this.state.isBalanceSpinnerVisible) { - const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); - this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); - } - this.setState({ - isBalanceSpinnerVisible: false, - }); - } - const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); - const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; - if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { - if (this.state.isZRXSpinnerVisible) { - const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); - const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); - this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); - } - this.setState({ - isZRXSpinnerVisible: false, - currentZrxBalance: undefined, - }); - } - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - const errorDialogActions = [ - <FlatButton - key="errorOkBtn" - label="Ok" - primary={true} - onTouchTap={this._onErrorDialogToggle.bind(this, false)} - />, - ]; - const dharmaDialogActions = [ - <FlatButton - key="dharmaCloseBtn" - label="Close" - primary={true} - onTouchTap={this._onDharmaDialogToggle.bind(this, false)} - />, - ]; - const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; - const dharmaButtonColumnStyle = { - paddingLeft: 3, - display: isTestNetwork ? 'table-cell' : 'none', - }; - const stubColumnStyle = { - display: isTestNetwork ? 'none' : 'table-cell', - }; - const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; - const tokenTableHeight = - allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? allTokenRowHeight : MAX_TOKEN_TABLE_HEIGHT; - const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; - const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; - const dharmaLoanExplanation = - 'If you need access to larger amounts of ether,<br> \ + public constructor(props: TokenBalancesProps) { + super(props); + this.state = { + errorType: undefined, + isBalanceSpinnerVisible: false, + isZRXSpinnerVisible: false, + isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), + isTokenPickerOpen: false, + isAddingToken: false, + }; + } + public componentWillReceiveProps(nextProps: TokenBalancesProps) { + if (nextProps.userEtherBalance !== this.props.userEtherBalance) { + if (this.state.isBalanceSpinnerVisible) { + const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); + this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); + } + this.setState({ + isBalanceSpinnerVisible: false, + }); + } + const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); + const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; + if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { + if (this.state.isZRXSpinnerVisible) { + const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); + const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); + this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); + } + this.setState({ + isZRXSpinnerVisible: false, + currentZrxBalance: undefined, + }); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const errorDialogActions = [ + <FlatButton + key="errorOkBtn" + label="Ok" + primary={true} + onTouchTap={this._onErrorDialogToggle.bind(this, false)} + />, + ]; + const dharmaDialogActions = [ + <FlatButton + key="dharmaCloseBtn" + label="Close" + primary={true} + onTouchTap={this._onDharmaDialogToggle.bind(this, false)} + />, + ]; + const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; + const dharmaButtonColumnStyle = { + paddingLeft: 3, + display: isTestNetwork ? 'table-cell' : 'none', + }; + const stubColumnStyle = { + display: isTestNetwork ? 'none' : 'table-cell', + }; + const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; + const tokenTableHeight = + allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? allTokenRowHeight : MAX_TOKEN_TABLE_HEIGHT; + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const dharmaLoanExplanation = + 'If you need access to larger amounts of ether,<br> \ you can request a loan from the Dharma Loan<br> \ network. Your loan should be funded in 5<br> \ minutes or less.'; - const allowanceExplanation = - '0x smart contracts require access to your<br> \ + const allowanceExplanation = + '0x smart contracts require access to your<br> \ token balances in order to execute trades.<br> \ Toggling sets an allowance for the<br> \ smart contract so you can start trading that token.'; - return ( - <div className="lg-px4 md-px4 sm-px1 pb2"> - <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> - <Divider /> - <div className="pt2 pb2"> - {isTestNetwork - ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ + return ( + <div className="lg-px4 md-px4 sm-px1 pb2"> + <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> + <Divider /> + <div className="pt2 pb2"> + {isTestNetwork + ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ gas costs. It might take a bit of time for the test ether to show up.' - : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ + : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ You can convert between Ether and Ether Tokens from the "Wrap ETH" tab.'} - </div> - <Table selectable={false} style={styles.bgColor}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>Currency</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( - <TableHeaderColumn style={{ paddingLeft: 3 }}> - {isSmallScreen ? 'Faucet' : 'Request from faucet'} - </TableHeaderColumn> - )} - {isTestNetwork && ( - <TableHeaderColumn style={dharmaButtonColumnStyle}> - {isSmallScreen ? 'Loan' : 'Request Dharma loan'} - <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> - </TableHeaderColumn> - )} - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - <TableRow key="ETH"> - <TableRowColumn className="py1"> - <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> - </TableRowColumn> - <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH - {this.state.isBalanceSpinnerVisible && ( - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - )} - </TableRowColumn> - <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( - <TableRowColumn style={{ paddingLeft: 3 }}> - <LifeCycleRaisedButton - labelReady="Request" - labelLoading="Sending..." - labelComplete="Sent!" - onClickAsyncFn={this._faucetRequestAsync.bind(this, true)} - /> - </TableRowColumn> - )} - {isTestNetwork && ( - <TableRowColumn style={dharmaButtonColumnStyle}> - <RaisedButton - label="Request" - style={{ width: '100%' }} - onTouchTap={this._onDharmaDialogToggle.bind(this)} - /> - </TableRowColumn> - )} - </TableRow> - </TableBody> - </Table> - <div className="clearfix" style={{ paddingBottom: 1 }}> - <div className="col col-10"> - <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> - </div> - <div className="col col-1 pt3 align-right"> - <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> - <ContentAdd /> - </FloatingActionButton> - </div> - <div className="col col-1 pt3 align-right"> - <FloatingActionButton mini={true} zDepth={0} onClick={this._onRemoveTokenClicked.bind(this)}> - <ContentRemove /> - </FloatingActionButton> - </div> - </div> - <Divider /> - <div className="pt2 pb2"> - {isTestNetwork - ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." - : "Set trading permissions for a token you'd like to start trading."} - </div> - <Table selectable={false} bodyStyle={{ height: tokenTableHeight }} style={styles.bgColor}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn colSpan={tokenColSpan}>Token</TableHeaderColumn> - <TableHeaderColumn style={{ paddingLeft: 3 }}>Balance</TableHeaderColumn> - <TableHeaderColumn> - <div className="inline-block">Allowance</div> - <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> - </TableHeaderColumn> - <TableHeaderColumn>Action</TableHeaderColumn> - {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody> - </Table> - <Dialog - title="Oh oh" - titleStyle={{ fontWeight: 100 }} - actions={errorDialogActions} - open={!_.isUndefined(this.state.errorType)} - onRequestClose={this._onErrorDialogToggle.bind(this, false)} - > - {this._renderErrorDialogBody()} - </Dialog> - <Dialog - title="Request Dharma Loan" - titleStyle={{ fontWeight: 100, backgroundColor: colors.white }} - bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }} - actionsContainerStyle={{ backgroundColor: colors.white }} - autoScrollBodyContent={true} - actions={dharmaDialogActions} - open={this.state.isDharmaDialogVisible} - > - {this._renderDharmaLoanFrame()} - </Dialog> - <AssetPicker - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - isOpen={this.state.isTokenPickerOpen} - currentTokenAddress={''} - onTokenChosen={this._onAssetTokenPicked.bind(this)} - tokenByAddress={this.props.tokenByAddress} - tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED} - /> - </div> - ); - } - private _renderTokenTableRows() { - if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { - return ''; - } - const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; - const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; - const actionPaddingX = isSmallScreen ? 2 : 24; - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); - const trackedTokensStartingWithEtherToken = trackedTokens.sort( - firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) - .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) - .thenBy('address'), - ); - const tableRows = _.map( - trackedTokensStartingWithEtherToken, - this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX), - ); - return tableRows; - } - private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { - const tokenState = this.props.tokenStateByAddress[token.address]; - const tokenLink = utils.getEtherScanLinkIfExists( - token.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const isMintable = - _.includes(configs.SYMBOLS_OF_MINTABLE_TOKENS, token.symbol) && - this.props.networkId !== constants.NETWORK_ID_MAINNET; - return ( - <TableRow key={token.address} style={{ height: TOKEN_TABLE_ROW_HEIGHT }}> - <TableRowColumn colSpan={tokenColSpan}> - {_.isUndefined(tokenLink) ? ( - this._renderTokenName(token) - ) : ( - <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> - {this._renderTokenName(token)} - </a> - )} - </TableRowColumn> - <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> - {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} - {this.state.isZRXSpinnerVisible && - token.symbol === ZRX_TOKEN_SYMBOL && ( - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - )} - </TableRowColumn> - <TableRowColumn> - <AllowanceToggle - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - token={token} - tokenState={tokenState} - onErrorOccurred={this._onErrorOccurred.bind(this)} - userAddress={this.props.userAddress} - /> - </TableRowColumn> - <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> - {isMintable && ( - <LifeCycleRaisedButton - labelReady="Mint" - labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>} - labelComplete="Minted!" - onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)} - /> - )} - {token.symbol === ZRX_TOKEN_SYMBOL && - this.props.networkId === constants.NETWORK_ID_TESTNET && ( - <LifeCycleRaisedButton - labelReady="Request" - labelLoading="Sending..." - labelComplete="Sent!" - onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} - /> - )} - </TableRowColumn> - {this.props.screenWidth !== ScreenWidths.Sm && ( - <TableRowColumn - style={{ - paddingLeft: actionPaddingX, - paddingRight: actionPaddingX, - }} - > - <SendButton - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - token={token} - tokenState={tokenState} - onError={this._onSendFailed.bind(this)} - /> - </TableRowColumn> - )} - </TableRow> - ); - } - private _onAssetTokenPicked(tokenAddress: string) { - if (_.isEmpty(tokenAddress)) { - this.setState({ - isTokenPickerOpen: false, - }); - return; - } - const token = this.props.tokenByAddress[tokenAddress]; - const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); - if (!this.state.isAddingToken && !isDefaultTrackedToken) { - if (token.isRegistered) { - // Remove the token from tracked tokens - const newToken = { - ...token, - isTracked: false, - }; - this.props.dispatcher.updateTokenByAddress([newToken]); - } else { - this.props.dispatcher.removeTokenToTokenByAddress(token); - } - this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); - trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); - } else if (isDefaultTrackedToken) { - this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); - } - this.setState({ - isTokenPickerOpen: false, - }); - } - private _onSendFailed() { - this.setState({ - errorType: BalanceErrs.sendFailed, - }); - } - private _renderAmount(amount: BigNumber, decimals: number) { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - return unitAmount.toNumber().toFixed(PRECISION); - } - private _renderTokenName(token: Token) { - const tooltipId = `tooltip-${token.address}`; - return ( - <div className="flex"> - <TokenIcon token={token} diameter={ICON_DIMENSION} /> - <div data-tip={true} data-for={tooltipId} className="mt2 ml2 sm-hide xs-hide"> - {token.name} - </div> - <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip> - </div> - ); - } - private _renderErrorDialogBody() { - switch (this.state.errorType) { - case BalanceErrs.incorrectNetworkForFaucet: - return ( - <div> - Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet - (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} - {constants.TESTNET_NAME} testnet and try requesting ether again. - </div> - ); + </div> + <Table selectable={false} style={styles.bgColor}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>Currency</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableHeaderColumn style={{ paddingLeft: 3 }}> + {isSmallScreen ? 'Faucet' : 'Request from faucet'} + </TableHeaderColumn> + )} + {isTestNetwork && ( + <TableHeaderColumn style={dharmaButtonColumnStyle}> + {isSmallScreen ? 'Loan' : 'Request Dharma loan'} + <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> + </TableHeaderColumn> + )} + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + <TableRow key="ETH"> + <TableRowColumn className="py1"> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> + </TableRowColumn> + <TableRowColumn> + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {this.state.isBalanceSpinnerVisible && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} + </TableRowColumn> + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableRowColumn style={{ paddingLeft: 3 }}> + <LifeCycleRaisedButton + labelReady="Request" + labelLoading="Sending..." + labelComplete="Sent!" + onClickAsyncFn={this._faucetRequestAsync.bind(this, true)} + /> + </TableRowColumn> + )} + {isTestNetwork && ( + <TableRowColumn style={dharmaButtonColumnStyle}> + <RaisedButton + label="Request" + style={{ width: '100%' }} + onTouchTap={this._onDharmaDialogToggle.bind(this)} + /> + </TableRowColumn> + )} + </TableRow> + </TableBody> + </Table> + <div className="clearfix" style={{ paddingBottom: 1 }}> + <div className="col col-10"> + <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> + </div> + <div className="col col-1 pt3 align-right"> + <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> + <ContentAdd /> + </FloatingActionButton> + </div> + <div className="col col-1 pt3 align-right"> + <FloatingActionButton mini={true} zDepth={0} onClick={this._onRemoveTokenClicked.bind(this)}> + <ContentRemove /> + </FloatingActionButton> + </div> + </div> + <Divider /> + <div className="pt2 pb2"> + {isTestNetwork + ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." + : "Set trading permissions for a token you'd like to start trading."} + </div> + <Table selectable={false} bodyStyle={{ height: tokenTableHeight }} style={styles.bgColor}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn colSpan={tokenColSpan}>Token</TableHeaderColumn> + <TableHeaderColumn style={{ paddingLeft: 3 }}>Balance</TableHeaderColumn> + <TableHeaderColumn> + <div className="inline-block">Allowance</div> + <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> + </TableHeaderColumn> + <TableHeaderColumn>Action</TableHeaderColumn> + {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody> + </Table> + <Dialog + title="Oh oh" + titleStyle={{ fontWeight: 100 }} + actions={errorDialogActions} + open={!_.isUndefined(this.state.errorType)} + onRequestClose={this._onErrorDialogToggle.bind(this, false)} + > + {this._renderErrorDialogBody()} + </Dialog> + <Dialog + title="Request Dharma Loan" + titleStyle={{ fontWeight: 100, backgroundColor: colors.white }} + bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }} + actionsContainerStyle={{ backgroundColor: colors.white }} + autoScrollBodyContent={true} + actions={dharmaDialogActions} + open={this.state.isDharmaDialogVisible} + > + {this._renderDharmaLoanFrame()} + </Dialog> + <AssetPicker + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + isOpen={this.state.isTokenPickerOpen} + currentTokenAddress={''} + onTokenChosen={this._onAssetTokenPicked.bind(this)} + tokenByAddress={this.props.tokenByAddress} + tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED} + /> + </div> + ); + } + private _renderTokenTableRows() { + if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { + return ''; + } + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const actionPaddingX = isSmallScreen ? 2 : 24; + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + const trackedTokensStartingWithEtherToken = trackedTokens.sort( + firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) + .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) + .thenBy('address'), + ); + const tableRows = _.map( + trackedTokensStartingWithEtherToken, + this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX), + ); + return tableRows; + } + private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + const tokenState = this.props.tokenStateByAddress[token.address]; + const tokenLink = utils.getEtherScanLinkIfExists( + token.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const isMintable = + _.includes(configs.SYMBOLS_OF_MINTABLE_TOKENS, token.symbol) && + this.props.networkId !== constants.NETWORK_ID_MAINNET; + return ( + <TableRow key={token.address} style={{ height: TOKEN_TABLE_ROW_HEIGHT }}> + <TableRowColumn colSpan={tokenColSpan}> + {_.isUndefined(tokenLink) ? ( + this._renderTokenName(token) + ) : ( + <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> + {this._renderTokenName(token)} + </a> + )} + </TableRowColumn> + <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> + {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && + token.symbol === ZRX_TOKEN_SYMBOL && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} + </TableRowColumn> + <TableRowColumn> + <AllowanceToggle + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + token={token} + tokenState={tokenState} + onErrorOccurred={this._onErrorOccurred.bind(this)} + userAddress={this.props.userAddress} + /> + </TableRowColumn> + <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> + {isMintable && ( + <LifeCycleRaisedButton + labelReady="Mint" + labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>} + labelComplete="Minted!" + onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)} + /> + )} + {token.symbol === ZRX_TOKEN_SYMBOL && + this.props.networkId === constants.NETWORK_ID_TESTNET && ( + <LifeCycleRaisedButton + labelReady="Request" + labelLoading="Sending..." + labelComplete="Sent!" + onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} + /> + )} + </TableRowColumn> + {this.props.screenWidth !== ScreenWidths.Sm && ( + <TableRowColumn + style={{ + paddingLeft: actionPaddingX, + paddingRight: actionPaddingX, + }} + > + <SendButton + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + token={token} + tokenState={tokenState} + onError={this._onSendFailed.bind(this)} + /> + </TableRowColumn> + )} + </TableRow> + ); + } + private _onAssetTokenPicked(tokenAddress: string) { + if (_.isEmpty(tokenAddress)) { + this.setState({ + isTokenPickerOpen: false, + }); + return; + } + const token = this.props.tokenByAddress[tokenAddress]; + const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); + if (!this.state.isAddingToken && !isDefaultTrackedToken) { + if (token.isRegistered) { + // Remove the token from tracked tokens + const newToken = { + ...token, + isTracked: false, + }; + this.props.dispatcher.updateTokenByAddress([newToken]); + } else { + this.props.dispatcher.removeTokenToTokenByAddress(token); + } + this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); + trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); + } else if (isDefaultTrackedToken) { + this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); + } + this.setState({ + isTokenPickerOpen: false, + }); + } + private _onSendFailed() { + this.setState({ + errorType: BalanceErrs.sendFailed, + }); + } + private _renderAmount(amount: BigNumber, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return unitAmount.toNumber().toFixed(PRECISION); + } + private _renderTokenName(token: Token) { + const tooltipId = `tooltip-${token.address}`; + return ( + <div className="flex"> + <TokenIcon token={token} diameter={ICON_DIMENSION} /> + <div data-tip={true} data-for={tooltipId} className="mt2 ml2 sm-hide xs-hide"> + {token.name} + </div> + <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip> + </div> + ); + } + private _renderErrorDialogBody() { + switch (this.state.errorType) { + case BalanceErrs.incorrectNetworkForFaucet: + return ( + <div> + Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet + (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} + {constants.TESTNET_NAME} testnet and try requesting ether again. + </div> + ); - case BalanceErrs.faucetRequestFailed: - return ( - <div> - An unexpected error occurred while trying to request test Ether from our faucet. Please refresh - the page and try again. - </div> - ); + case BalanceErrs.faucetRequestFailed: + return ( + <div> + An unexpected error occurred while trying to request test Ether from our faucet. Please refresh + the page and try again. + </div> + ); - case BalanceErrs.faucetQueueIsFull: - return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>; + case BalanceErrs.faucetQueueIsFull: + return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>; - case BalanceErrs.mintingFailed: - return <div>Minting your test tokens failed unexpectedly. Please refresh the page and try again.</div>; + case BalanceErrs.mintingFailed: + return <div>Minting your test tokens failed unexpectedly. Please refresh the page and try again.</div>; - case BalanceErrs.allowanceSettingFailed: - return ( - <div> - An unexpected error occurred while trying to set your test token allowance. Please refresh the - page and try again. - </div> - ); + case BalanceErrs.allowanceSettingFailed: + return ( + <div> + An unexpected error occurred while trying to set your test token allowance. Please refresh the + page and try again. + </div> + ); - case undefined: - return null; // No error to show + case undefined: + return null; // No error to show - default: - throw utils.spawnSwitchErr('errorType', this.state.errorType); - } - } - private _renderDharmaLoanFrame() { - if (utils.isUserOnMobile()) { - return ( - <h4 style={{ textAlign: 'center' }}> - We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your - desktop browser. - </h4> - ); - } else { - return ( - <DharmaLoanFrame - partner="0x" - env={utils.getCurrentEnvironment()} - screenWidth={this.props.screenWidth} - /> - ); - } - } - private _onErrorOccurred(errorType: BalanceErrs) { - this.setState({ - errorType, - }); - } - private async _onMintTestTokensAsync(token: Token): Promise<boolean> { - try { - await this.props.blockchain.mintTestTokensAsync(token); - const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); - this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); - return true; - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } - if (_.includes(errMsg, 'User denied transaction')) { - return false; - } - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.setState({ - errorType: BalanceErrs.mintingFailed, - }); - await errorReporter.reportAsync(err); - return false; - } - } - private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { - if (this.props.userAddress === '') { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } + default: + throw utils.spawnSwitchErr('errorType', this.state.errorType); + } + } + private _renderDharmaLoanFrame() { + if (utils.isUserOnMobile()) { + return ( + <h4 style={{ textAlign: 'center' }}> + We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your + desktop browser. + </h4> + ); + } else { + return ( + <DharmaLoanFrame + partner="0x" + env={utils.getCurrentEnvironment()} + screenWidth={this.props.screenWidth} + /> + ); + } + } + private _onErrorOccurred(errorType: BalanceErrs) { + this.setState({ + errorType, + }); + } + private async _onMintTestTokensAsync(token: Token): Promise<boolean> { + try { + await this.props.blockchain.mintTestTokensAsync(token); + const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); + return true; + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.setState({ + errorType: BalanceErrs.mintingFailed, + }); + await errorReporter.reportAsync(err); + return false; + } + } + private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } - // If on another network other then the testnet our faucet serves test ether - // from, we must show user an error message - if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { - this.setState({ - errorType: BalanceErrs.incorrectNetworkForFaucet, - }); - return false; - } + // If on another network other then the testnet our faucet serves test ether + // from, we must show user an error message + if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { + this.setState({ + errorType: BalanceErrs.incorrectNetworkForFaucet, + }); + return false; + } - await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); + await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); - const segment = isEtherRequest ? 'ether' : 'zrx'; - const response = await fetch(`${constants.URL_ETHER_FAUCET}/${segment}/${this.props.userAddress}`); - const responseBody = await response.text(); - if (response.status !== constants.SUCCESS_STATUS) { - utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); - const errorType = - response.status === constants.UNAVAILABLE_STATUS - ? BalanceErrs.faucetQueueIsFull - : BalanceErrs.faucetRequestFailed; - this.setState({ - errorType, - }); - await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); - return false; - } + const segment = isEtherRequest ? 'ether' : 'zrx'; + const response = await fetch(`${constants.URL_ETHER_FAUCET}/${segment}/${this.props.userAddress}`); + const responseBody = await response.text(); + if (response.status !== constants.SUCCESS_STATUS) { + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + const errorType = + response.status === constants.UNAVAILABLE_STATUS + ? BalanceErrs.faucetQueueIsFull + : BalanceErrs.faucetRequestFailed; + this.setState({ + errorType, + }); + await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); + return false; + } - if (isEtherRequest) { - this.setState({ - isBalanceSpinnerVisible: true, - }); - } else { - const tokens = _.values(this.props.tokenByAddress); - const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); - const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; - this.setState({ - isZRXSpinnerVisible: true, - currentZrxBalance: zrxTokenState.balance, - }); - // tslint:disable-next-line:no-floating-promises - this.props.blockchain.pollTokenBalanceAsync(zrxToken); - } - return true; - } - private _onErrorDialogToggle(isOpen: boolean) { - this.setState({ - errorType: undefined, - }); - } - private _onDharmaDialogToggle() { - this.setState({ - isDharmaDialogVisible: !this.state.isDharmaDialogVisible, - }); - } - private _onAddTokenClicked() { - this.setState({ - isTokenPickerOpen: true, - isAddingToken: true, - }); - } - private _onRemoveTokenClicked() { - this.setState({ - isTokenPickerOpen: true, - isAddingToken: false, - }); - } + if (isEtherRequest) { + this.setState({ + isBalanceSpinnerVisible: true, + }); + } else { + const tokens = _.values(this.props.tokenByAddress); + const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); + const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; + this.setState({ + isZRXSpinnerVisible: true, + currentZrxBalance: zrxTokenState.balance, + }); + // tslint:disable-next-line:no-floating-promises + this.props.blockchain.pollTokenBalanceAsync(zrxToken); + } + return true; + } + private _onErrorDialogToggle(isOpen: boolean) { + this.setState({ + errorType: undefined, + }); + } + private _onDharmaDialogToggle() { + this.setState({ + isDharmaDialogVisible: !this.state.isDharmaDialogVisible, + }); + } + private _onAddTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: true, + }); + } + private _onRemoveTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: false, + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx index 1f111cb07..11d3e7cc2 100644 --- a/packages/website/ts/components/top_bar.tsx +++ b/packages/website/ts/components/top_bar.tsx @@ -15,333 +15,333 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface TopBarProps { - userAddress?: string; - blockchainIsLoaded: boolean; - location: Location; - docsVersion?: string; - availableDocVersions?: string[]; - menu?: DocsMenu; - menuSubsectionsBySection?: MenuSubsectionsBySection; - shouldFullWidth?: boolean; - docsInfo?: DocsInfo; - style?: React.CSSProperties; - isNightVersion?: boolean; + userAddress?: string; + blockchainIsLoaded: boolean; + location: Location; + docsVersion?: string; + availableDocVersions?: string[]; + menu?: DocsMenu; + menuSubsectionsBySection?: MenuSubsectionsBySection; + shouldFullWidth?: boolean; + docsInfo?: DocsInfo; + style?: React.CSSProperties; + isNightVersion?: boolean; } interface TopBarState { - isDrawerOpen: boolean; + isDrawerOpen: boolean; } const styles: Styles = { - address: { - marginRight: 12, - overflow: 'hidden', - paddingTop: 4, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - width: 70, - }, - topBar: { - backgroundcolor: colors.white, - height: 59, - width: '100%', - position: 'relative', - top: 0, - zIndex: 1100, - paddingBottom: 1, - }, - bottomBar: { - boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', - }, - menuItem: { - fontSize: 14, - color: colors.darkestGrey, - paddingTop: 6, - paddingBottom: 6, - marginTop: 17, - cursor: 'pointer', - fontWeight: 400, - }, + address: { + marginRight: 12, + overflow: 'hidden', + paddingTop: 4, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 70, + }, + topBar: { + backgroundcolor: colors.white, + height: 59, + width: '100%', + position: 'relative', + top: 0, + zIndex: 1100, + paddingBottom: 1, + }, + bottomBar: { + boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', + }, + menuItem: { + fontSize: 14, + color: colors.darkestGrey, + paddingTop: 6, + paddingBottom: 6, + marginTop: 17, + cursor: 'pointer', + fontWeight: 400, + }, }; export class TopBar extends React.Component<TopBarProps, TopBarState> { - public static defaultProps: Partial<TopBarProps> = { - shouldFullWidth: false, - style: {}, - isNightVersion: false, - }; - constructor(props: TopBarProps) { - super(props); - this.state = { - isDrawerOpen: false, - }; - } - public render() { - const isNightVersion = this.props.isNightVersion; - const isFullWidthPage = this.props.shouldFullWidth; - const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; - const developerSectionMenuItems = [ - <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> - </Link>, - <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> - </Link>, - <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> - </Link>, - <a - key="subMenuItem-standard-relayer-api" - target="_blank" - className="text-decoration-none" - href={constants.URL_STANDARD_RELAYER_API_GITHUB} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> - </a>, - <a - key="subMenuItem-github" - target="_blank" - className="text-decoration-none" - href={constants.URL_GITHUB_ORG} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> - </a>, - <a - key="subMenuItem-whitePaper" - target="_blank" - className="text-decoration-none" - href={`${WebsitePaths.Whitepaper}`} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> - </a>, - ]; - const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; - const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; - const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; - const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; - const menuIconStyle = { - fontSize: 25, - color: isNightVersion ? 'white' : 'black', - cursor: 'pointer', - paddingTop: 16, - }; - return ( - <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> - <div className={parentClassNames}> - <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> - <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> - <img src={logoUrl} height="30" /> - </Link> - </div> - <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> - <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> - {!this._isViewingPortal() && ( - <div className={menuClasses}> - <div className="flex justify-between"> - <DropDownMenuItem - title="Developers" - subMenuItems={developerSectionMenuItems} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="Wiki" - path={`${WebsitePaths.Wiki}`} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="About" - path={`${WebsitePaths.About}`} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="Portal DApp" - path={`${WebsitePaths.Portal}`} - isPrimary={true} - style={styles.menuItem} - className={`${isFullWidthPage && 'md-hide'}`} - isNightVersion={isNightVersion} - /> - </div> - </div> - )} - {this.props.blockchainIsLoaded && - !_.isEmpty(this.props.userAddress) && ( - <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> - )} - <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> - <div style={menuIconStyle}> - <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> - </div> - </div> - </div> - {this._renderDrawer()} - </div> - ); - } - private _renderDrawer() { - return ( - <Drawer - open={this.state.isDrawerOpen} - docked={false} - openSecondary={true} - onRequestChange={this._onMenuButtonClick.bind(this)} - > - {this._renderPortalMenu()} - {this._renderDocsMenu()} - {this._renderWiki()} - <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> - Website - </div> - <Link to={WebsitePaths.Home} className="text-decoration-none"> - <MenuItem className="py2">Home</MenuItem> - </Link> - <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> - <MenuItem className="py2">Wiki</MenuItem> - </Link> - {!this._isViewing0xjsDocs() && ( - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <MenuItem className="py2">0x.js Docs</MenuItem> - </Link> - )} - {!this._isViewingConnectDocs() && ( - <Link to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem className="py2">0x Connect Docs</MenuItem> - </Link> - )} - {!this._isViewingSmartContractsDocs() && ( - <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem className="py2">Smart Contract Docs</MenuItem> - </Link> - )} - {!this._isViewingPortal() && ( - <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> - <MenuItem className="py2">Portal DApp</MenuItem> - </Link> - )} - <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> - <MenuItem className="py2">Whitepaper</MenuItem> - </a> - <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> - <MenuItem className="py2">About</MenuItem> - </Link> - <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> - <MenuItem className="py2">Blog</MenuItem> - </a> - <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> - <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> - FAQ - </MenuItem> - </Link> - </Drawer> - ); - } - private _renderDocsMenu(): React.ReactNode { - if ( - (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || - _.isUndefined(this.props.menu) - ) { - return undefined; - } + public static defaultProps: Partial<TopBarProps> = { + shouldFullWidth: false, + style: {}, + isNightVersion: false, + }; + constructor(props: TopBarProps) { + super(props); + this.state = { + isDrawerOpen: false, + }; + } + public render() { + const isNightVersion = this.props.isNightVersion; + const isFullWidthPage = this.props.shouldFullWidth; + const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const developerSectionMenuItems = [ + <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> + </Link>, + <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> + </Link>, + <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> + </Link>, + <a + key="subMenuItem-standard-relayer-api" + target="_blank" + className="text-decoration-none" + href={constants.URL_STANDARD_RELAYER_API_GITHUB} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> + </a>, + <a + key="subMenuItem-github" + target="_blank" + className="text-decoration-none" + href={constants.URL_GITHUB_ORG} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> + </a>, + <a + key="subMenuItem-whitePaper" + target="_blank" + className="text-decoration-none" + href={`${WebsitePaths.Whitepaper}`} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> + </a>, + ]; + const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; + const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; + const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; + const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; + const menuIconStyle = { + fontSize: 25, + color: isNightVersion ? 'white' : 'black', + cursor: 'pointer', + paddingTop: 16, + }; + return ( + <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> + <div className={parentClassNames}> + <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> + <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> + <img src={logoUrl} height="30" /> + </Link> + </div> + <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> + <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> + {!this._isViewingPortal() && ( + <div className={menuClasses}> + <div className="flex justify-between"> + <DropDownMenuItem + title="Developers" + subMenuItems={developerSectionMenuItems} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="Wiki" + path={`${WebsitePaths.Wiki}`} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="About" + path={`${WebsitePaths.About}`} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="Portal DApp" + path={`${WebsitePaths.Portal}`} + isPrimary={true} + style={styles.menuItem} + className={`${isFullWidthPage && 'md-hide'}`} + isNightVersion={isNightVersion} + /> + </div> + </div> + )} + {this.props.blockchainIsLoaded && + !_.isEmpty(this.props.userAddress) && ( + <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> + )} + <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div style={menuIconStyle}> + <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> + </div> + </div> + </div> + {this._renderDrawer()} + </div> + ); + } + private _renderDrawer() { + return ( + <Drawer + open={this.state.isDrawerOpen} + docked={false} + openSecondary={true} + onRequestChange={this._onMenuButtonClick.bind(this)} + > + {this._renderPortalMenu()} + {this._renderDocsMenu()} + {this._renderWiki()} + <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> + Website + </div> + <Link to={WebsitePaths.Home} className="text-decoration-none"> + <MenuItem className="py2">Home</MenuItem> + </Link> + <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> + <MenuItem className="py2">Wiki</MenuItem> + </Link> + {!this._isViewing0xjsDocs() && ( + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <MenuItem className="py2">0x.js Docs</MenuItem> + </Link> + )} + {!this._isViewingConnectDocs() && ( + <Link to={WebsitePaths.Connect} className="text-decoration-none"> + <MenuItem className="py2">0x Connect Docs</MenuItem> + </Link> + )} + {!this._isViewingSmartContractsDocs() && ( + <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> + <MenuItem className="py2">Smart Contract Docs</MenuItem> + </Link> + )} + {!this._isViewingPortal() && ( + <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> + <MenuItem className="py2">Portal DApp</MenuItem> + </Link> + )} + <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> + <MenuItem className="py2">Whitepaper</MenuItem> + </a> + <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> + <MenuItem className="py2">About</MenuItem> + </Link> + <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> + <MenuItem className="py2">Blog</MenuItem> + </a> + <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> + <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> + FAQ + </MenuItem> + </Link> + </Drawer> + ); + } + private _renderDocsMenu(): React.ReactNode { + if ( + (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || + _.isUndefined(this.props.menu) + ) { + return undefined; + } - const sectionTitle = `${this.props.docsInfo.displayName} Docs`; - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - {sectionTitle} - </div> - <NestedSidebarMenu - topLevelMenu={this.props.menu} - menuSubsectionsBySection={this.props.menuSubsectionsBySection} - shouldDisplaySectionHeaders={false} - onMenuItemClick={this._onMenuButtonClick.bind(this)} - selectedVersion={this.props.docsVersion} - docPath={this.props.docsInfo.websitePath} - versions={this.props.availableDocVersions} - /> - </div> - ); - } - private _renderWiki(): React.ReactNode { - if (!this._isViewingWiki()) { - return undefined; - } + const sectionTitle = `${this.props.docsInfo.displayName} Docs`; + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + {sectionTitle} + </div> + <NestedSidebarMenu + topLevelMenu={this.props.menu} + menuSubsectionsBySection={this.props.menuSubsectionsBySection} + shouldDisplaySectionHeaders={false} + onMenuItemClick={this._onMenuButtonClick.bind(this)} + selectedVersion={this.props.docsVersion} + docPath={this.props.docsInfo.websitePath} + versions={this.props.availableDocVersions} + /> + </div> + ); + } + private _renderWiki(): React.ReactNode { + if (!this._isViewingWiki()) { + return undefined; + } - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - 0x Protocol Wiki - </div> - <NestedSidebarMenu - topLevelMenu={this.props.menuSubsectionsBySection} - menuSubsectionsBySection={this.props.menuSubsectionsBySection} - shouldDisplaySectionHeaders={false} - onMenuItemClick={this._onMenuButtonClick.bind(this)} - /> - </div> - ); - } - private _renderPortalMenu(): React.ReactNode { - if (!this._isViewingPortal()) { - return undefined; - } + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + 0x Protocol Wiki + </div> + <NestedSidebarMenu + topLevelMenu={this.props.menuSubsectionsBySection} + menuSubsectionsBySection={this.props.menuSubsectionsBySection} + shouldDisplaySectionHeaders={false} + onMenuItemClick={this._onMenuButtonClick.bind(this)} + /> + </div> + ); + } + private _renderPortalMenu(): React.ReactNode { + if (!this._isViewingPortal()) { + return undefined; + } - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - Portal DApp - </div> - <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> - </div> - ); - } - private _renderUser() { - const userAddress = this.props.userAddress; - const identiconDiameter = 26; - return ( - <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> - <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> - {!_.isEmpty(userAddress) ? userAddress : ''} - </div> - <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> - <div> - <Identicon address={userAddress} diameter={identiconDiameter} /> - </div> - </div> - ); - } - private _onMenuButtonClick() { - this.setState({ - isDrawerOpen: !this.state.isDrawerOpen, - }); - } - private _isViewingPortal() { - return _.includes(this.props.location.pathname, WebsitePaths.Portal); - } - private _isViewingFAQ() { - return _.includes(this.props.location.pathname, WebsitePaths.FAQ); - } - private _isViewing0xjsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); - } - private _isViewingConnectDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.Connect); - } - private _isViewingSmartContractsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); - } - private _isViewingWiki() { - return _.includes(this.props.location.pathname, WebsitePaths.Wiki); - } - private _shouldDisplayBottomBar() { - return ( - this._isViewingWiki() || - this._isViewing0xjsDocs() || - this._isViewingFAQ() || - this._isViewingSmartContractsDocs() || - this._isViewingConnectDocs() - ); - } + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + Portal DApp + </div> + <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> + </div> + ); + } + private _renderUser() { + const userAddress = this.props.userAddress; + const identiconDiameter = 26; + return ( + <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> + <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> + {!_.isEmpty(userAddress) ? userAddress : ''} + </div> + <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> + <div> + <Identicon address={userAddress} diameter={identiconDiameter} /> + </div> + </div> + ); + } + private _onMenuButtonClick() { + this.setState({ + isDrawerOpen: !this.state.isDrawerOpen, + }); + } + private _isViewingPortal() { + return _.includes(this.props.location.pathname, WebsitePaths.Portal); + } + private _isViewingFAQ() { + return _.includes(this.props.location.pathname, WebsitePaths.FAQ); + } + private _isViewing0xjsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); + } + private _isViewingConnectDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Connect); + } + private _isViewingSmartContractsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); + } + private _isViewingWiki() { + return _.includes(this.props.location.pathname, WebsitePaths.Wiki); + } + private _shouldDisplayBottomBar() { + return ( + this._isViewingWiki() || + this._isViewing0xjsDocs() || + this._isViewingFAQ() || + this._isViewingSmartContractsDocs() || + this._isViewingConnectDocs() + ); + } } diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx index 0138740ba..96ee86142 100644 --- a/packages/website/ts/components/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar_menu_item.tsx @@ -4,49 +4,49 @@ import { Link } from 'react-router-dom'; import { colors } from 'ts/utils/colors'; const DEFAULT_STYLE = { - color: colors.darkestGrey, + color: colors.darkestGrey, }; interface TopBarMenuItemProps { - title: string; - path?: string; - isPrimary?: boolean; - style?: React.CSSProperties; - className?: string; - isNightVersion?: boolean; + title: string; + path?: string; + isPrimary?: boolean; + style?: React.CSSProperties; + className?: string; + isNightVersion?: boolean; } interface TopBarMenuItemState {} export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarMenuItemState> { - public static defaultProps: Partial<TopBarMenuItemProps> = { - isPrimary: false, - style: DEFAULT_STYLE, - className: '', - isNightVersion: false, - }; - public render() { - const primaryStyles = this.props.isPrimary - ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - width: 77, - } - : {}; - const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; - const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; - return ( - <div - className={`center ${this.props.className}`} - style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} - > - <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> - {this.props.title} - </Link> - </div> - ); - } + public static defaultProps: Partial<TopBarMenuItemProps> = { + isPrimary: false, + style: DEFAULT_STYLE, + className: '', + isNightVersion: false, + }; + public render() { + const primaryStyles = this.props.isPrimary + ? { + borderRadius: 4, + border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, + marginTop: 15, + paddingLeft: 9, + paddingRight: 9, + width: 77, + } + : {}; + const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; + const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; + return ( + <div + className={`center ${this.props.className}`} + style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} + > + <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> + {this.props.title} + </Link> + </div> + ); + } } diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx index 1887bd11c..76971aefa 100644 --- a/packages/website/ts/components/track_token_confirmation.tsx +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -6,56 +6,56 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TrackTokenConfirmationProps { - tokens: Token[]; - tokenByAddress: TokenByAddress; - networkId: number; - isAddingTokenToTracked: boolean; + tokens: Token[]; + tokenByAddress: TokenByAddress; + networkId: number; + isAddingTokenToTracked: boolean; } interface TrackTokenConfirmationState {} export class TrackTokenConfirmation extends React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> { - public render() { - const isMultipleTokens = this.props.tokens.length > 1; - const allTokens = _.values(this.props.tokenByAddress); - return ( - <div style={{ color: colors.grey700 }}> - {this.props.isAddingTokenToTracked ? ( - <div className="py4 my4 center"> - <span className="pr1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - <span>Adding token{isMultipleTokens && 's'}...</span> - </div> - ) : ( - <div> - <div>You do not currently track the following token{isMultipleTokens && 's'}:</div> - <div className="py2 clearfix mx-auto center" style={{ width: 355 }}> - {_.map(this.props.tokens, (token: Token) => ( - <div - key={`token-profile-${token.name}`} - className={`col col-${isMultipleTokens ? '6' : '12'} px2`} - > - <Party - label={token.name} - address={token.address} - networkId={this.props.networkId} - alternativeImage={token.iconUrl} - isInTokenRegistry={token.isRegistered} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)} - /> - </div> - ))} - </div> - <div> - Tracking a token adds it to the balances section of 0x Portal and allows you to - generate/fill orders involving the token - {isMultipleTokens && 's'}. Would you like to start tracking{' '} - {isMultipleTokens ? 'these' : 'this'} token? - </div> - </div> - )} - </div> - ); - } + public render() { + const isMultipleTokens = this.props.tokens.length > 1; + const allTokens = _.values(this.props.tokenByAddress); + return ( + <div style={{ color: colors.grey700 }}> + {this.props.isAddingTokenToTracked ? ( + <div className="py4 my4 center"> + <span className="pr1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + <span>Adding token{isMultipleTokens && 's'}...</span> + </div> + ) : ( + <div> + <div>You do not currently track the following token{isMultipleTokens && 's'}:</div> + <div className="py2 clearfix mx-auto center" style={{ width: 355 }}> + {_.map(this.props.tokens, (token: Token) => ( + <div + key={`token-profile-${token.name}`} + className={`col col-${isMultipleTokens ? '6' : '12'} px2`} + > + <Party + label={token.name} + address={token.address} + networkId={this.props.networkId} + alternativeImage={token.iconUrl} + isInTokenRegistry={token.isRegistered} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)} + /> + </div> + ))} + </div> + <div> + Tracking a token adds it to the balances section of 0x Portal and allows you to + generate/fill orders involving the token + {isMultipleTokens && 's'}. Would you like to start tracking{' '} + {isMultipleTokens ? 'these' : 'this'} token? + </div> + </div> + )} + </div> + ); + } } diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx index 3963135f7..635358627 100644 --- a/packages/website/ts/components/trade_history/trade_history.tsx +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -10,106 +10,106 @@ import { utils } from 'ts/utils/utils'; const FILL_POLLING_INTERVAL = 1000; interface TradeHistoryProps { - tokenByAddress: TokenByAddress; - userAddress: string; - networkId: number; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; } interface TradeHistoryState { - sortedFills: Fill[]; + sortedFills: Fill[]; } export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> { - private _fillPollingIntervalId: number; - public constructor(props: TradeHistoryProps) { - super(props); - const sortedFills = this._getSortedFills(); - this.state = { - sortedFills, - }; - } - public componentWillMount() { - this._startPollingForFills(); - } - public componentWillUnmount() { - this._stopPollingForFills(); - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div className="lg-px4 md-px4 sm-px2"> - <h3>Trade history</h3> - <Divider /> - <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> - {this._renderTrades()} - </div> - </div> - ); - } - private _renderTrades() { - const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); - if (numNonCustomFills === 0) { - return this._renderEmptyNotice(); - } + private _fillPollingIntervalId: number; + public constructor(props: TradeHistoryProps) { + super(props); + const sortedFills = this._getSortedFills(); + this.state = { + sortedFills, + }; + } + public componentWillMount() { + this._startPollingForFills(); + } + public componentWillUnmount() { + this._stopPollingForFills(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div className="lg-px4 md-px4 sm-px2"> + <h3>Trade history</h3> + <Divider /> + <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> + {this._renderTrades()} + </div> + </div> + ); + } + private _renderTrades() { + const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); + if (numNonCustomFills === 0) { + return this._renderEmptyNotice(); + } - return _.map(this.state.sortedFills, (fill, index) => { - return ( - <TradeHistoryItem - key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`} - fill={fill} - tokenByAddress={this.props.tokenByAddress} - userAddress={this.props.userAddress} - networkId={this.props.networkId} - /> - ); - }); - } - private _renderEmptyNotice() { - return ( - <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> - No filled orders yet. - </Paper> - ); - } - private _numFillsWithoutCustomERC20Tokens() { - let numNonCustomFills = 0; - const tokens = _.values(this.props.tokenByAddress); - _.each(this.state.sortedFills, fill => { - const takerToken = _.find(tokens, token => { - return token.address === fill.takerToken; - }); - const makerToken = _.find(tokens, token => { - return token.address === fill.makerToken; - }); - // For now we don't show history items for orders using custom ERC20 - // tokens the client does not know how to display. - // TODO: Try to retrieve the name/symbol of an unknown token in order to display it - // Be sure to remove similar logic in trade_history_item.tsx - if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { - numNonCustomFills += 1; - } - }); - return numNonCustomFills; - } - private _startPollingForFills() { - this._fillPollingIntervalId = window.setInterval(() => { - const sortedFills = this._getSortedFills(); - if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { - this.setState({ - sortedFills, - }); - } - }, FILL_POLLING_INTERVAL); - } - private _stopPollingForFills() { - clearInterval(this._fillPollingIntervalId); - } - private _getSortedFills() { - const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); - const fills = _.values(fillsByHash); - const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); - return sortedFills; - } + return _.map(this.state.sortedFills, (fill, index) => { + return ( + <TradeHistoryItem + key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`} + fill={fill} + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + /> + ); + }); + } + private _renderEmptyNotice() { + return ( + <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> + No filled orders yet. + </Paper> + ); + } + private _numFillsWithoutCustomERC20Tokens() { + let numNonCustomFills = 0; + const tokens = _.values(this.props.tokenByAddress); + _.each(this.state.sortedFills, fill => { + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history_item.tsx + if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { + numNonCustomFills += 1; + } + }); + return numNonCustomFills; + } + private _startPollingForFills() { + this._fillPollingIntervalId = window.setInterval(() => { + const sortedFills = this._getSortedFills(); + if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { + this.setState({ + sortedFills, + }); + } + }, FILL_POLLING_INTERVAL); + } + private _stopPollingForFills() { + clearInterval(this._fillPollingIntervalId); + } + private _getSortedFills() { + const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); + const fills = _.values(fillsByHash); + const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); + return sortedFills; + } } diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 118bde3b9..7e42e64e6 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -14,157 +14,157 @@ const PRECISION = 5; const IDENTICON_DIAMETER = 40; interface TradeHistoryItemProps { - fill: Fill; - tokenByAddress: TokenByAddress; - userAddress: string; - networkId: number; + fill: Fill; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; } interface TradeHistoryItemState {} export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, TradeHistoryItemState> { - public render() { - const fill = this.props.fill; - const tokens = _.values(this.props.tokenByAddress); - const takerToken = _.find(tokens, token => { - return token.address === fill.takerToken; - }); - const makerToken = _.find(tokens, token => { - return token.address === fill.makerToken; - }); - // For now we don't show history items for orders using custom ERC20 - // tokens the client does not know how to display. - // TODO: Try to retrieve the name/symbol of an unknown token in order to display it - // Be sure to remove similar logic in trade_history.tsx - if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { - return null; - } + public render() { + const fill = this.props.fill; + const tokens = _.values(this.props.tokenByAddress); + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history.tsx + if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { + return null; + } - const amountColStyle: React.CSSProperties = { - fontWeight: 100, - display: 'inline-block', - }; - const amountColClassNames = - 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ + const amountColStyle: React.CSSProperties = { + fontWeight: 100, + display: 'inline-block', + }; + const amountColClassNames = + 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ lg-right-align md-right-align sm-center'; - return ( - <Paper className="py1" style={{ margin: '3px 3px 15px 3px' }}> - <div className="clearfix"> - <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">{this._renderDate()}</div> - <div - className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3" - style={{ fontSize: 12, fontWeight: 100 }} - > - <div className="flex sm-mx-auto xs-mx-auto" style={{ paddingTop: 4, width: 224 }}> - <Party - label="Maker" - address={fill.maker} - identiconDiameter={IDENTICON_DIAMETER} - networkId={this.props.networkId} - /> - <i style={{ fontSize: 30 }} className="zmdi zmdi-swap py3" /> - <Party - label="Taker" - address={fill.taker} - identiconDiameter={IDENTICON_DIAMETER} - networkId={this.props.networkId} - /> - </div> - </div> - <div className={amountColClassNames} style={amountColStyle}> - {this._renderAmounts(makerToken, takerToken)} - </div> - <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center"> - <div className="pt1 lg-right md-right sm-mx-auto" style={{ width: 13 }}> - <EtherScanIcon - addressOrTxHash={fill.transactionHash} - networkId={this.props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx} - /> - </div> - </div> - </div> - </Paper> - ); - } - private _renderAmounts(makerToken: Token, takerToken: Token) { - const fill = this.props.fill; - const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); - const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); - let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); - const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); + return ( + <Paper className="py1" style={{ margin: '3px 3px 15px 3px' }}> + <div className="clearfix"> + <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">{this._renderDate()}</div> + <div + className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3" + style={{ fontSize: 12, fontWeight: 100 }} + > + <div className="flex sm-mx-auto xs-mx-auto" style={{ paddingTop: 4, width: 224 }}> + <Party + label="Maker" + address={fill.maker} + identiconDiameter={IDENTICON_DIAMETER} + networkId={this.props.networkId} + /> + <i style={{ fontSize: 30 }} className="zmdi zmdi-swap py3" /> + <Party + label="Taker" + address={fill.taker} + identiconDiameter={IDENTICON_DIAMETER} + networkId={this.props.networkId} + /> + </div> + </div> + <div className={amountColClassNames} style={amountColStyle}> + {this._renderAmounts(makerToken, takerToken)} + </div> + <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center"> + <div className="pt1 lg-right md-right sm-mx-auto" style={{ width: 13 }}> + <EtherScanIcon + addressOrTxHash={fill.transactionHash} + networkId={this.props.networkId} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx} + /> + </div> + </div> + </div> + </Paper> + ); + } + private _renderAmounts(makerToken: Token, takerToken: Token) { + const fill = this.props.fill; + const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); + const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); + let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); + const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); - let receiveAmount; - let receiveToken; - let givenAmount; - let givenToken; - if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { - receiveAmount = new BigNumber(0); - givenAmount = new BigNumber(0); - receiveToken = makerToken; - givenToken = takerToken; - } else if (this.props.userAddress === fill.maker) { - receiveAmount = fill.filledTakerTokenAmount; - givenAmount = fillMakerTokenAmount; - receiveToken = takerToken; - givenToken = makerToken; - exchangeRate = new BigNumber(1).div(exchangeRate); - } else if (this.props.userAddress === fill.taker) { - receiveAmount = fillMakerTokenAmount; - givenAmount = fill.filledTakerTokenAmount; - receiveToken = makerToken; - givenToken = takerToken; - } else { - // This condition should never be hit - throw new Error("Found Fill that wasn't performed by this user"); - } + let receiveAmount; + let receiveToken; + let givenAmount; + let givenToken; + if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { + receiveAmount = new BigNumber(0); + givenAmount = new BigNumber(0); + receiveToken = makerToken; + givenToken = takerToken; + } else if (this.props.userAddress === fill.maker) { + receiveAmount = fill.filledTakerTokenAmount; + givenAmount = fillMakerTokenAmount; + receiveToken = takerToken; + givenToken = makerToken; + exchangeRate = new BigNumber(1).div(exchangeRate); + } else if (this.props.userAddress === fill.taker) { + receiveAmount = fillMakerTokenAmount; + givenAmount = fill.filledTakerTokenAmount; + receiveToken = makerToken; + givenToken = takerToken; + } else { + // This condition should never be hit + throw new Error("Found Fill that wasn't performed by this user"); + } - return ( - <div> - <div style={{ color: colors.green400, fontSize: 16 }}> - <span>+ </span> - {this._renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} - </div> - <div className="pb1 inline-block" style={{ color: colors.red200, fontSize: 16 }}> - <span>- </span> - {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} - </div> - <div style={{ color: colors.grey400, fontSize: 14 }}> - {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} - </div> - </div> - ); - } - private _renderDate() { - const blockMoment = moment.unix(this.props.fill.blockTimestamp); - if (!blockMoment.isValid()) { - return null; - } + return ( + <div> + <div style={{ color: colors.green400, fontSize: 16 }}> + <span>+ </span> + {this._renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} + </div> + <div className="pb1 inline-block" style={{ color: colors.red200, fontSize: 16 }}> + <span>- </span> + {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} + </div> + <div style={{ color: colors.grey400, fontSize: 14 }}> + {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} + </div> + </div> + ); + } + private _renderDate() { + const blockMoment = moment.unix(this.props.fill.blockTimestamp); + if (!blockMoment.isValid()) { + return null; + } - const dayOfMonth = blockMoment.format('D'); - const monthAbreviation = blockMoment.format('MMM'); - const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); - const dateTooltipId = `${this.props.fill.transactionHash}-date`; + const dayOfMonth = blockMoment.format('D'); + const monthAbreviation = blockMoment.format('MMM'); + const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); + const dateTooltipId = `${this.props.fill.transactionHash}-date`; - return ( - <div data-tip={true} data-for={dateTooltipId}> - <div className="center pt1" style={{ fontSize: 13 }}> - {monthAbreviation} - </div> - <div className="center" style={{ fontSize: 24, fontWeight: 100 }}> - {dayOfMonth} - </div> - <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip> - </div> - ); - } - private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - return ( - <span> - {unitAmount.toFixed(PRECISION)} {symbol} - </span> - ); - } + return ( + <div data-tip={true} data-for={dateTooltipId}> + <div className="center pt1" style={{ fontSize: 13 }}> + {monthAbreviation} + </div> + <div className="center" style={{ fontSize: 24, fontWeight: 100 }}> + {dayOfMonth} + </div> + <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip> + </div> + ); + } + private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return ( + <span> + {unitAmount.toFixed(PRECISION)} {symbol} + </span> + ); + } } diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx index 91ef8f76e..54881b499 100644 --- a/packages/website/ts/components/ui/alert.tsx +++ b/packages/website/ts/components/ui/alert.tsx @@ -3,23 +3,23 @@ import { AlertTypes } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface AlertProps { - type: AlertTypes; - message: string | React.ReactNode; + type: AlertTypes; + message: string | React.ReactNode; } export function Alert(props: AlertProps) { - const isAlert = props.type === AlertTypes.ERROR; - const errMsgStyles = { - background: isAlert ? colors.red200 : colors.lightestGreen, - color: colors.white, - marginTop: 10, - padding: 4, - paddingLeft: 8, - }; + const isAlert = props.type === AlertTypes.ERROR; + const errMsgStyles = { + background: isAlert ? colors.red200 : colors.lightestGreen, + color: colors.white, + marginTop: 10, + padding: 4, + paddingLeft: 8, + }; - return ( - <div className="rounded center" style={errMsgStyles}> - {props.message} - </div> - ); + return ( + <div className="rounded center" style={errMsgStyles}> + {props.message} + </div> + ); } diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx index 1d2d81af6..7f7ea006e 100644 --- a/packages/website/ts/components/ui/badge.tsx +++ b/packages/website/ts/components/ui/badge.tsx @@ -3,55 +3,55 @@ import * as React from 'react'; import { Styles } from 'ts/types'; const styles: Styles = { - badge: { - width: 50, - fontSize: 11, - height: 10, - borderRadius: 5, - marginTop: 25, - lineHeight: 0.9, - fontFamily: 'Roboto Mono', - marginLeft: 3, - marginRight: 3, - }, + badge: { + width: 50, + fontSize: 11, + height: 10, + borderRadius: 5, + marginTop: 25, + lineHeight: 0.9, + fontFamily: 'Roboto Mono', + marginLeft: 3, + marginRight: 3, + }, }; interface BadgeProps { - title: string; - backgroundColor: string; + title: string; + backgroundColor: string; } interface BadgeState { - isHovering: boolean; + isHovering: boolean; } export class Badge extends React.Component<BadgeProps, BadgeState> { - constructor(props: BadgeProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const badgeStyle = { - ...styles.badge, - backgroundColor: this.props.backgroundColor, - opacity: this.state.isHovering ? 0.7 : 1, - }; - return ( - <div - className="p1 center" - style={badgeStyle} - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - > - {this.props.title} - </div> - ); - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + constructor(props: BadgeProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const badgeStyle = { + ...styles.badge, + backgroundColor: this.props.backgroundColor, + opacity: this.state.isHovering ? 0.7 : 1, + }; + return ( + <div + className="p1 center" + style={badgeStyle} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + {this.props.title} + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index 72ab77e1f..df55e0922 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -6,74 +6,74 @@ import ReactTooltip = require('react-tooltip'); import { colors } from 'ts/utils/colors'; interface CopyIconProps { - data: string; - callToAction?: string; + data: string; + callToAction?: string; } interface CopyIconState { - isHovering: boolean; + isHovering: boolean; } export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { - private _copyTooltipTimeoutId: number; - private _copyable: HTMLInputElement; - constructor(props: CopyIconProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public componentDidUpdate() { - // Remove tooltip if hover away - if (!this.state.isHovering && this._copyTooltipTimeoutId) { - clearInterval(this._copyTooltipTimeoutId); - this._hideTooltip(); - } - } - public render() { - return ( - <div className="inline-block"> - <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> - <div - className="inline flex" - style={{ cursor: 'pointer', color: colors.amber600 }} - ref={this._setRefToProperty.bind(this)} - data-tip={true} - data-for="copy" - data-event="click" - data-iscapture={true} // This let's the click event continue to propogate - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - > - <div> - <i style={{ fontSize: 15 }} className="zmdi zmdi-copy" /> - </div> - {this.props.callToAction && <div className="pl1">{this.props.callToAction}</div>} - </div> - </CopyToClipboard> - <ReactTooltip id="copy">Copied!</ReactTooltip> - </div> - ); - } - private _setRefToProperty(el: HTMLInputElement) { - this._copyable = el; - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } - private _onCopy() { - if (this._copyTooltipTimeoutId) { - clearInterval(this._copyTooltipTimeoutId); - } + private _copyTooltipTimeoutId: number; + private _copyable: HTMLInputElement; + constructor(props: CopyIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public componentDidUpdate() { + // Remove tooltip if hover away + if (!this.state.isHovering && this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); + this._hideTooltip(); + } + } + public render() { + return ( + <div className="inline-block"> + <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> + <div + className="inline flex" + style={{ cursor: 'pointer', color: colors.amber600 }} + ref={this._setRefToProperty.bind(this)} + data-tip={true} + data-for="copy" + data-event="click" + data-iscapture={true} // This let's the click event continue to propogate + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + <div> + <i style={{ fontSize: 15 }} className="zmdi zmdi-copy" /> + </div> + {this.props.callToAction && <div className="pl1">{this.props.callToAction}</div>} + </div> + </CopyToClipboard> + <ReactTooltip id="copy">Copied!</ReactTooltip> + </div> + ); + } + private _setRefToProperty(el: HTMLInputElement) { + this._copyable = el; + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } + private _onCopy() { + if (this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); + } - const tooltipLifespanMs = 1000; - this._copyTooltipTimeoutId = window.setTimeout(() => { - this._hideTooltip(); - }, tooltipLifespanMs); - } - private _hideTooltip() { - ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); - } + const tooltipLifespanMs = 1000; + this._copyTooltipTimeoutId = window.setTimeout(() => { + this._hideTooltip(); + }, tooltipLifespanMs); + } + private _hideTooltip() { + ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); + } } diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx index 64f88f318..a578fb4f9 100644 --- a/packages/website/ts/components/ui/drop_down_menu_item.tsx +++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx @@ -6,99 +6,99 @@ import { colors } from 'ts/utils/colors'; const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; const DEFAULT_STYLE = { - fontSize: 14, + fontSize: 14, }; interface DropDownMenuItemProps { - title: string; - subMenuItems: React.ReactNode[]; - style?: React.CSSProperties; - menuItemStyle?: React.CSSProperties; - isNightVersion?: boolean; + title: string; + subMenuItems: React.ReactNode[]; + style?: React.CSSProperties; + menuItemStyle?: React.CSSProperties; + isNightVersion?: boolean; } interface DropDownMenuItemState { - isDropDownOpen: boolean; - anchorEl?: HTMLInputElement; + isDropDownOpen: boolean; + anchorEl?: HTMLInputElement; } export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> { - public static defaultProps: Partial<DropDownMenuItemProps> = { - style: DEFAULT_STYLE, - menuItemStyle: DEFAULT_STYLE, - isNightVersion: false, - }; - private _isHovering: boolean; - private _popoverCloseCheckIntervalId: number; - constructor(props: DropDownMenuItemProps) { - super(props); - this.state = { - isDropDownOpen: false, - }; - } - public componentDidMount() { - this._popoverCloseCheckIntervalId = window.setInterval(() => { - this._checkIfShouldClosePopover(); - }, CHECK_CLOSE_POPOVER_INTERVAL_MS); - } - public componentWillUnmount() { - window.clearInterval(this._popoverCloseCheckIntervalId); - } - public render() { - const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; - return ( - <div - style={{ ...this.props.style, color: colorStyle }} - onMouseEnter={this._onHover.bind(this)} - onMouseLeave={this._onHoverOff.bind(this)} - > - <div className="flex relative"> - <div style={{ paddingRight: 10 }}>{this.props.title}</div> - <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> - <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> - </div> - </div> - <Popover - open={this.state.isDropDownOpen} - anchorEl={this.state.anchorEl} - anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} - targetOrigin={{ horizontal: 'middle', vertical: 'top' }} - onRequestClose={this._closePopover.bind(this)} - useLayerForClickAway={false} - > - <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> - <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> - </div> - </Popover> - </div> - ); - } - private _onHover(event: React.FormEvent<HTMLInputElement>) { - this._isHovering = true; - this._checkIfShouldOpenPopover(event); - } - private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { - if (this.state.isDropDownOpen) { - return; // noop - } + public static defaultProps: Partial<DropDownMenuItemProps> = { + style: DEFAULT_STYLE, + menuItemStyle: DEFAULT_STYLE, + isNightVersion: false, + }; + private _isHovering: boolean; + private _popoverCloseCheckIntervalId: number; + constructor(props: DropDownMenuItemProps) { + super(props); + this.state = { + isDropDownOpen: false, + }; + } + public componentDidMount() { + this._popoverCloseCheckIntervalId = window.setInterval(() => { + this._checkIfShouldClosePopover(); + }, CHECK_CLOSE_POPOVER_INTERVAL_MS); + } + public componentWillUnmount() { + window.clearInterval(this._popoverCloseCheckIntervalId); + } + public render() { + const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; + return ( + <div + style={{ ...this.props.style, color: colorStyle }} + onMouseEnter={this._onHover.bind(this)} + onMouseLeave={this._onHoverOff.bind(this)} + > + <div className="flex relative"> + <div style={{ paddingRight: 10 }}>{this.props.title}</div> + <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> + <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> + </div> + </div> + <Popover + open={this.state.isDropDownOpen} + anchorEl={this.state.anchorEl} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} + onRequestClose={this._closePopover.bind(this)} + useLayerForClickAway={false} + > + <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> + <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> + </div> + </Popover> + </div> + ); + } + private _onHover(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = true; + this._checkIfShouldOpenPopover(event); + } + private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { + if (this.state.isDropDownOpen) { + return; // noop + } - this.setState({ - isDropDownOpen: true, - anchorEl: event.currentTarget, - }); - } - private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { - this._isHovering = false; - } - private _checkIfShouldClosePopover() { - if (!this.state.isDropDownOpen || this._isHovering) { - return; // noop - } - this._closePopover(); - } - private _closePopover() { - this.setState({ - isDropDownOpen: false, - }); - } + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = false; + } + private _checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this._isHovering) { + return; // noop + } + this._closePopover(); + } + private _closePopover() { + this.setState({ + isDropDownOpen: false, + }); + } } diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx index ba51135be..b75d97e39 100644 --- a/packages/website/ts/components/ui/ethereum_address.tsx +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -5,26 +5,26 @@ import { EtherscanLinkSuffixes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface EthereumAddressProps { - address: string; - networkId: number; + address: string; + networkId: number; } export const EthereumAddress = (props: EthereumAddressProps) => { - const tooltipId = `${props.address}-ethereum-address`; - const truncatedAddress = utils.getAddressBeginAndEnd(props.address); - return ( - <div> - <div className="inline" style={{ fontSize: 13 }} data-tip={true} data-for={tooltipId}> - {truncatedAddress} - </div> - <div className="pl1 inline"> - <EtherScanIcon - addressOrTxHash={props.address} - networkId={props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.Address} - /> - </div> - <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip> - </div> - ); + const tooltipId = `${props.address}-ethereum-address`; + const truncatedAddress = utils.getAddressBeginAndEnd(props.address); + return ( + <div> + <div className="inline" style={{ fontSize: 13 }} data-tip={true} data-for={tooltipId}> + {truncatedAddress} + </div> + <div className="pl1 inline"> + <EtherScanIcon + addressOrTxHash={props.address} + networkId={props.networkId} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Address} + /> + </div> + <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip> + </div> + ); }; diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index 5b224c3e1..3b17bd0fa 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -6,36 +6,36 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { - addressOrTxHash: string; - etherscanLinkSuffixes: EtherscanLinkSuffixes; - networkId: number; + addressOrTxHash: string; + etherscanLinkSuffixes: EtherscanLinkSuffixes; + networkId: number; } export const EtherScanIcon = (props: EtherScanIconProps) => { - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - props.addressOrTxHash, - props.networkId, - EtherscanLinkSuffixes.Address, - ); - const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; - return ( - <div className="inline"> - {!_.isUndefined(etherscanLinkIfExists) ? ( - <a href={etherscanLinkIfExists} target="_blank"> - {renderIcon()} - </a> - ) : ( - <div className="inline" data-tip={true} data-for={transactionTooltipId}> - {renderIcon()} - <ReactTooltip id={transactionTooltipId}> - Your network (id: {props.networkId}) is not supported by Etherscan - </ReactTooltip> - </div> - )} - </div> - ); + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + props.addressOrTxHash, + props.networkId, + EtherscanLinkSuffixes.Address, + ); + const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; + return ( + <div className="inline"> + {!_.isUndefined(etherscanLinkIfExists) ? ( + <a href={etherscanLinkIfExists} target="_blank"> + {renderIcon()} + </a> + ) : ( + <div className="inline" data-tip={true} data-for={transactionTooltipId}> + {renderIcon()} + <ReactTooltip id={transactionTooltipId}> + Your network (id: {props.networkId}) is not supported by Etherscan + </ReactTooltip> + </div> + )} + </div> + ); }; function renderIcon() { - return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; + return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; } diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx index 6d321bb46..f3d9410f6 100644 --- a/packages/website/ts/components/ui/fake_text_field.tsx +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -3,32 +3,32 @@ import { InputLabel } from 'ts/components/ui/input_label'; import { Styles } from 'ts/types'; const styles: Styles = { - hr: { - borderBottom: '1px solid rgb(224, 224, 224)', - borderLeft: 'none rgb(224, 224, 224)', - borderRight: 'none rgb(224, 224, 224)', - borderTop: 'none rgb(224, 224, 224)', - bottom: 6, - boxSizing: 'content-box', - margin: 0, - position: 'absolute', - width: '100%', - }, + hr: { + borderBottom: '1px solid rgb(224, 224, 224)', + borderLeft: 'none rgb(224, 224, 224)', + borderRight: 'none rgb(224, 224, 224)', + borderTop: 'none rgb(224, 224, 224)', + bottom: 6, + boxSizing: 'content-box', + margin: 0, + position: 'absolute', + width: '100%', + }, }; interface FakeTextFieldProps { - label?: React.ReactNode | string; - children?: any; + label?: React.ReactNode | string; + children?: any; } export function FakeTextField(props: FakeTextFieldProps) { - return ( - <div className="relative"> - {props.label !== '' && <InputLabel text={props.label} />} - <div className="pb2" style={{ height: 23 }}> - {props.children} - </div> - <hr style={styles.hr} /> - </div> - ); + return ( + <div className="relative"> + {props.label !== '' && <InputLabel text={props.label} />} + <div className="pb2" style={{ height: 23 }}> + {props.children} + </div> + <hr style={styles.hr} /> + </div> + ); } diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx index 57a66d21f..2cb1fc764 100644 --- a/packages/website/ts/components/ui/flash_message.tsx +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -6,35 +6,35 @@ import { Dispatcher } from 'ts/redux/dispatcher'; const SHOW_DURATION_MS = 4000; interface FlashMessageProps { - dispatcher: Dispatcher; - flashMessage?: string | React.ReactNode; - showDurationMs?: number; - bodyStyle?: React.CSSProperties; + dispatcher: Dispatcher; + flashMessage?: string | React.ReactNode; + showDurationMs?: number; + bodyStyle?: React.CSSProperties; } interface FlashMessageState {} export class FlashMessage extends React.Component<FlashMessageProps, FlashMessageState> { - public static defaultProps: Partial<FlashMessageProps> = { - showDurationMs: SHOW_DURATION_MS, - bodyStyle: {}, - }; - public render() { - if (!_.isUndefined(this.props.flashMessage)) { - return ( - <Snackbar - open={true} - message={this.props.flashMessage} - autoHideDuration={this.props.showDurationMs} - onRequestClose={this._onClose.bind(this)} - bodyStyle={this.props.bodyStyle} - /> - ); - } else { - return null; - } - } - private _onClose() { - this.props.dispatcher.hideFlashMessage(); - } + public static defaultProps: Partial<FlashMessageProps> = { + showDurationMs: SHOW_DURATION_MS, + bodyStyle: {}, + }; + public render() { + if (!_.isUndefined(this.props.flashMessage)) { + return ( + <Snackbar + open={true} + message={this.props.flashMessage} + autoHideDuration={this.props.showDurationMs} + onRequestClose={this._onClose.bind(this)} + bodyStyle={this.props.bodyStyle} + /> + ); + } else { + return null; + } + } + private _onClose() { + this.props.dispatcher.hideFlashMessage(); + } } diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx index c946a70cb..d827eebb9 100644 --- a/packages/website/ts/components/ui/help_tooltip.tsx +++ b/packages/website/ts/components/ui/help_tooltip.tsx @@ -2,21 +2,21 @@ import * as React from 'react'; import ReactTooltip = require('react-tooltip'); interface HelpTooltipProps { - style?: React.CSSProperties; - explanation: React.ReactNode; + style?: React.CSSProperties; + explanation: React.ReactNode; } export const HelpTooltip = (props: HelpTooltipProps) => { - return ( - <div - style={{ ...props.style }} - className="inline-block" - data-tip={props.explanation} - data-for="helpTooltip" - data-multiline={true} - > - <i style={{ fontSize: 16 }} className="zmdi zmdi-help" /> - <ReactTooltip id="helpTooltip" /> - </div> - ); + return ( + <div + style={{ ...props.style }} + className="inline-block" + data-tip={props.explanation} + data-for="helpTooltip" + data-multiline={true} + > + <i style={{ fontSize: 16 }} className="zmdi zmdi-help" /> + <ReactTooltip id="helpTooltip" /> + </div> + ); }; diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx index bad7a657d..bad6c2a78 100644 --- a/packages/website/ts/components/ui/identicon.tsx +++ b/packages/website/ts/components/ui/identicon.tsx @@ -4,45 +4,45 @@ import * as React from 'react'; import { constants } from 'ts/utils/constants'; interface IdenticonProps { - address: string; - diameter: number; - style?: React.CSSProperties; + address: string; + diameter: number; + style?: React.CSSProperties; } interface IdenticonState {} export class Identicon extends React.Component<IdenticonProps, IdenticonState> { - public static defaultProps: Partial<IdenticonProps> = { - style: {}, - }; - public render() { - let address = this.props.address; - if (_.isEmpty(address)) { - address = constants.NULL_ADDRESS; - } - const diameter = this.props.diameter; - const icon = blockies({ - seed: address.toLowerCase(), - }); - return ( - <div - className="circle mx-auto relative transitionFix" - style={{ - width: diameter, - height: diameter, - overflow: 'hidden', - ...this.props.style, - }} - > - <img - src={icon.toDataURL()} - style={{ - width: diameter, - height: diameter, - imageRendering: 'pixelated', - }} - /> - </div> - ); - } + public static defaultProps: Partial<IdenticonProps> = { + style: {}, + }; + public render() { + let address = this.props.address; + if (_.isEmpty(address)) { + address = constants.NULL_ADDRESS; + } + const diameter = this.props.diameter; + const icon = blockies({ + seed: address.toLowerCase(), + }); + return ( + <div + className="circle mx-auto relative transitionFix" + style={{ + width: diameter, + height: diameter, + overflow: 'hidden', + ...this.props.style, + }} + > + <img + src={icon.toDataURL()} + style={{ + width: diameter, + height: diameter, + imageRendering: 'pixelated', + }} + /> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx index 1cbda6692..e2009ad20 100644 --- a/packages/website/ts/components/ui/input_label.tsx +++ b/packages/website/ts/components/ui/input_label.tsx @@ -2,24 +2,24 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface InputLabelProps { - text: string | Element | React.ReactNode; + text: string | Element | React.ReactNode; } const styles = { - label: { - color: colors.grey, - fontSize: 12, - pointerEvents: 'none', - textAlign: 'left', - transform: 'scale(0.75) translate(0px, -28px)', - transformOrigin: 'left top 0px', - transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', - userSelect: 'none', - width: 240, - zIndex: 1, - }, + label: { + color: colors.grey, + fontSize: 12, + pointerEvents: 'none', + textAlign: 'left', + transform: 'scale(0.75) translate(0px, -28px)', + transformOrigin: 'left top 0px', + transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', + userSelect: 'none', + width: 240, + zIndex: 1, + }, }; export const InputLabel = (props: InputLabelProps) => { - return <label style={styles.label}>{props.text}</label>; + return <label style={styles.label}>{props.text}</label>; }; diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index fd23912f1..8ff856a75 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -7,97 +7,97 @@ import { utils } from 'ts/utils/utils'; const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; enum ButtonState { - READY, - LOADING, - COMPLETE, + READY, + LOADING, + COMPLETE, } interface LifeCycleRaisedButtonProps { - isHidden?: boolean; - isDisabled?: boolean; - isPrimary?: boolean; - labelReady: React.ReactNode | string; - labelLoading: React.ReactNode | string; - labelComplete: React.ReactNode | string; - onClickAsyncFn: () => Promise<boolean>; - backgroundColor?: string; - labelColor?: string; + isHidden?: boolean; + isDisabled?: boolean; + isPrimary?: boolean; + labelReady: React.ReactNode | string; + labelLoading: React.ReactNode | string; + labelComplete: React.ReactNode | string; + onClickAsyncFn: () => Promise<boolean>; + backgroundColor?: string; + labelColor?: string; } interface LifeCycleRaisedButtonState { - buttonState: ButtonState; + buttonState: ButtonState; } export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> { - public static defaultProps: Partial<LifeCycleRaisedButtonProps> = { - isDisabled: false, - backgroundColor: colors.white, - labelColor: colors.darkGrey, - }; - private _buttonTimeoutId: number; - private _didUnmount: boolean; - constructor(props: LifeCycleRaisedButtonProps) { - super(props); - this.state = { - buttonState: ButtonState.READY, - }; - } - public componentWillUnmount() { - clearTimeout(this._buttonTimeoutId); - this._didUnmount = true; - } - public render() { - if (this.props.isHidden) { - return <span />; - } + public static defaultProps: Partial<LifeCycleRaisedButtonProps> = { + isDisabled: false, + backgroundColor: colors.white, + labelColor: colors.darkGrey, + }; + private _buttonTimeoutId: number; + private _didUnmount: boolean; + constructor(props: LifeCycleRaisedButtonProps) { + super(props); + this.state = { + buttonState: ButtonState.READY, + }; + } + public componentWillUnmount() { + clearTimeout(this._buttonTimeoutId); + this._didUnmount = true; + } + public render() { + if (this.props.isHidden) { + return <span />; + } - let label; - switch (this.state.buttonState) { - case ButtonState.READY: - label = this.props.labelReady; - break; - case ButtonState.LOADING: - label = this.props.labelLoading; - break; - case ButtonState.COMPLETE: - label = this.props.labelComplete; - break; - default: - throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); - } - return ( - <RaisedButton - primary={this.props.isPrimary} - label={label} - style={{ width: '100%' }} - backgroundColor={this.props.backgroundColor} - labelColor={this.props.labelColor} - onTouchTap={this.onClickAsync.bind(this)} - disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY} - /> - ); - } - public async onClickAsync() { - this.setState({ - buttonState: ButtonState.LOADING, - }); - const didSucceed = await this.props.onClickAsyncFn(); - if (this._didUnmount) { - return; // noop since unmount called before async callback returned. - } - if (didSucceed) { - this.setState({ - buttonState: ButtonState.COMPLETE, - }); - this._buttonTimeoutId = window.setTimeout(() => { - this.setState({ - buttonState: ButtonState.READY, - }); - }, COMPLETE_STATE_SHOW_LENGTH_MS); - } else { - this.setState({ - buttonState: ButtonState.READY, - }); - } - } + let label; + switch (this.state.buttonState) { + case ButtonState.READY: + label = this.props.labelReady; + break; + case ButtonState.LOADING: + label = this.props.labelLoading; + break; + case ButtonState.COMPLETE: + label = this.props.labelComplete; + break; + default: + throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); + } + return ( + <RaisedButton + primary={this.props.isPrimary} + label={label} + style={{ width: '100%' }} + backgroundColor={this.props.backgroundColor} + labelColor={this.props.labelColor} + onTouchTap={this.onClickAsync.bind(this)} + disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY} + /> + ); + } + public async onClickAsync() { + this.setState({ + buttonState: ButtonState.LOADING, + }); + const didSucceed = await this.props.onClickAsyncFn(); + if (this._didUnmount) { + return; // noop since unmount called before async callback returned. + } + if (didSucceed) { + this.setState({ + buttonState: ButtonState.COMPLETE, + }); + this._buttonTimeoutId = window.setTimeout(() => { + this.setState({ + buttonState: ButtonState.READY, + }); + }, COMPLETE_STATE_SHOW_LENGTH_MS); + } else { + this.setState({ + buttonState: ButtonState.READY, + }); + } + } } diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx index e9bfe3316..aa319e9e9 100644 --- a/packages/website/ts/components/ui/loading.tsx +++ b/packages/website/ts/components/ui/loading.tsx @@ -10,30 +10,30 @@ interface LoadingProps {} interface LoadingState {} export class Loading extends React.Component<LoadingProps, LoadingState> { - public render() { - return ( - <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> - <Paper className="mx-auto" style={{ maxWidth: 400 }}> - {utils.isUserOnMobile() ? ( - <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> - ) : ( - <div style={{ pointerEvents: 'none' }}> - <Video - autoPlay={true} - loop={true} - muted={true} - controls={[]} - poster="/images/loading_poster.png" - > - <source src="/videos/0xAnimation.mp4" type="video/mp4" /> - </Video> - </div> - )} - <div className="center pt2" style={{ paddingBottom: 11 }}> - Connecting to the blockchain... - </div> - </Paper> - </div> - ); - } + public render() { + return ( + <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> + <Paper className="mx-auto" style={{ maxWidth: 400 }}> + {utils.isUserOnMobile() ? ( + <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> + ) : ( + <div style={{ pointerEvents: 'none' }}> + <Video + autoPlay={true} + loop={true} + muted={true} + controls={[]} + poster="/images/loading_poster.png" + > + <source src="/videos/0xAnimation.mp4" type="video/mp4" /> + </Video> + </div> + )} + <div className="center pt2" style={{ paddingBottom: 11 }}> + Connecting to the blockchain... + </div> + </Paper> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx index 956b5eae8..3482f436c 100644 --- a/packages/website/ts/components/ui/menu_item.tsx +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -3,49 +3,49 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; interface MenuItemProps { - to: string; - style?: React.CSSProperties; - onClick?: () => void; - className?: string; + to: string; + style?: React.CSSProperties; + onClick?: () => void; + className?: string; } interface MenuItemState { - isHovering: boolean; + isHovering: boolean; } export class MenuItem extends React.Component<MenuItemProps, MenuItemState> { - public static defaultProps: Partial<MenuItemProps> = { - onClick: _.noop, - className: '', - }; - public constructor(props: MenuItemProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const menuItemStyles = { - cursor: 'pointer', - opacity: this.state.isHovering ? 0.5 : 1, - }; - return ( - <Link to={this.props.to} style={{ textDecoration: 'none', ...this.props.style }}> - <div - onClick={this.props.onClick.bind(this)} - className={`mx-auto ${this.props.className}`} - style={menuItemStyles} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - > - {this.props.children} - </div> - </Link> - ); - } - private _onToggleHover(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + public static defaultProps: Partial<MenuItemProps> = { + onClick: _.noop, + className: '', + }; + public constructor(props: MenuItemProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const menuItemStyles = { + cursor: 'pointer', + opacity: this.state.isHovering ? 0.5 : 1, + }; + return ( + <Link to={this.props.to} style={{ textDecoration: 'none', ...this.props.style }}> + <div + onClick={this.props.onClick.bind(this)} + className={`mx-auto ${this.props.className}`} + style={menuItemStyles} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + > + {this.props.children} + </div> + </Link> + ); + } + private _onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index ef3c7b425..ca2577b61 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -11,129 +11,129 @@ const IMAGE_DIMENSION = 100; const IDENTICON_DIAMETER = 95; interface PartyProps { - label: string; - address: string; - networkId: number; - alternativeImage?: string; - identiconDiameter?: number; - identiconStyle?: React.CSSProperties; - isInTokenRegistry?: boolean; - hasUniqueNameAndSymbol?: boolean; + label: string; + address: string; + networkId: number; + alternativeImage?: string; + identiconDiameter?: number; + identiconStyle?: React.CSSProperties; + isInTokenRegistry?: boolean; + hasUniqueNameAndSymbol?: boolean; } interface PartyState {} export class Party extends React.Component<PartyProps, PartyState> { - public static defaultProps: Partial<PartyProps> = { - identiconStyle: {}, - identiconDiameter: IDENTICON_DIAMETER, - }; - public render() { - const label = this.props.label; - const address = this.props.address; - const identiconDiameter = this.props.identiconDiameter; - const emptyIdenticonStyles = { - width: identiconDiameter, - height: identiconDiameter, - backgroundColor: 'lightgray', - marginTop: 13, - marginBottom: 10, - }; - const tokenImageStyle = { - width: IMAGE_DIMENSION, - height: IMAGE_DIMENSION, - }; - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - this.props.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const isRegistered = this.props.isInTokenRegistry; - const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; - const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; - return ( - <div style={{ overflow: 'hidden' }}> - <div className="pb1 center">{label}</div> - {_.isEmpty(address) ? ( - <div className="circle mx-auto" style={emptyIdenticonStyles} /> - ) : ( - <a href={etherscanLinkIfExists} target="_blank"> - {isRegistered && !_.isUndefined(this.props.alternativeImage) ? ( - <img style={tokenImageStyle} src={this.props.alternativeImage} /> - ) : ( - <div className="mx-auto" style={{ height: identiconDiameter, width: identiconDiameter }}> - <Identicon - address={this.props.address} - diameter={identiconDiameter} - style={this.props.identiconStyle} - /> - </div> - )} - </a> - )} - <div className="mx-auto center pt1"> - <div style={{ height: 25 }}> - <EthereumAddress address={address} networkId={this.props.networkId} /> - </div> - {!_.isUndefined(this.props.isInTokenRegistry) && ( - <div> - <div - data-tip={true} - data-for={registeredTooltipId} - className="mx-auto" - style={{ fontSize: 13, width: 127 }} - > - <span - style={{ - color: isRegistered ? colors.brightGreen : colors.red500, - }} - > - <i - className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`} - /> - </span>{' '} - <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span> - <ReactTooltip id={registeredTooltipId}> - {isRegistered ? ( - <div> - This token address was found in the token registry<br /> - smart contract and is therefore believed to be a<br /> - legitimate token. - </div> - ) : ( - <div> - This token is not included in the token registry<br /> - smart contract. We cannot guarantee the legitimacy<br /> - of this token. Make sure to verify its address on Etherscan. - </div> - )} - </ReactTooltip> - </div> - </div> - )} - {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && - !this.props.hasUniqueNameAndSymbol && ( - <div> - <div - data-tip={true} - data-for={uniqueNameAndSymbolTooltipId} - className="mx-auto" - style={{ fontSize: 13, width: 127 }} - > - <span style={{ color: colors.red500 }}> - <i className="zmdi zmdi-alert-octagon" /> - </span>{' '} - <span>Suspicious token</span> - <ReactTooltip id={uniqueNameAndSymbolTooltipId}> - This token shares it's name, symbol or both with<br /> - a token in the 0x Token Registry but it has a different<br /> - smart contract address. This is most likely a scam token! - </ReactTooltip> - </div> - </div> - )} - </div> - </div> - ); - } + public static defaultProps: Partial<PartyProps> = { + identiconStyle: {}, + identiconDiameter: IDENTICON_DIAMETER, + }; + public render() { + const label = this.props.label; + const address = this.props.address; + const identiconDiameter = this.props.identiconDiameter; + const emptyIdenticonStyles = { + width: identiconDiameter, + height: identiconDiameter, + backgroundColor: 'lightgray', + marginTop: 13, + marginBottom: 10, + }; + const tokenImageStyle = { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + }; + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + this.props.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const isRegistered = this.props.isInTokenRegistry; + const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; + const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; + return ( + <div style={{ overflow: 'hidden' }}> + <div className="pb1 center">{label}</div> + {_.isEmpty(address) ? ( + <div className="circle mx-auto" style={emptyIdenticonStyles} /> + ) : ( + <a href={etherscanLinkIfExists} target="_blank"> + {isRegistered && !_.isUndefined(this.props.alternativeImage) ? ( + <img style={tokenImageStyle} src={this.props.alternativeImage} /> + ) : ( + <div className="mx-auto" style={{ height: identiconDiameter, width: identiconDiameter }}> + <Identicon + address={this.props.address} + diameter={identiconDiameter} + style={this.props.identiconStyle} + /> + </div> + )} + </a> + )} + <div className="mx-auto center pt1"> + <div style={{ height: 25 }}> + <EthereumAddress address={address} networkId={this.props.networkId} /> + </div> + {!_.isUndefined(this.props.isInTokenRegistry) && ( + <div> + <div + data-tip={true} + data-for={registeredTooltipId} + className="mx-auto" + style={{ fontSize: 13, width: 127 }} + > + <span + style={{ + color: isRegistered ? colors.brightGreen : colors.red500, + }} + > + <i + className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`} + /> + </span>{' '} + <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span> + <ReactTooltip id={registeredTooltipId}> + {isRegistered ? ( + <div> + This token address was found in the token registry<br /> + smart contract and is therefore believed to be a<br /> + legitimate token. + </div> + ) : ( + <div> + This token is not included in the token registry<br /> + smart contract. We cannot guarantee the legitimacy<br /> + of this token. Make sure to verify its address on Etherscan. + </div> + )} + </ReactTooltip> + </div> + </div> + )} + {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && + !this.props.hasUniqueNameAndSymbol && ( + <div> + <div + data-tip={true} + data-for={uniqueNameAndSymbolTooltipId} + className="mx-auto" + style={{ fontSize: 13, width: 127 }} + > + <span style={{ color: colors.red500 }}> + <i className="zmdi zmdi-alert-octagon" /> + </span>{' '} + <span>Suspicious token</span> + <ReactTooltip id={uniqueNameAndSymbolTooltipId}> + This token shares it's name, symbol or both with<br /> + a token in the 0x Token Registry but it has a different<br /> + smart contract address. This is most likely a scam token! + </ReactTooltip> + </div> + </div> + )} + </div> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx index 638683427..a5e7a22ce 100644 --- a/packages/website/ts/components/ui/required_label.tsx +++ b/packages/website/ts/components/ui/required_label.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface RequiredLabelProps { - label: string | React.ReactNode; + label: string | React.ReactNode; } export const RequiredLabel = (props: RequiredLabelProps) => { - return ( - <span> - {props.label} - <span style={{ color: colors.red600 }}>*</span> - </span> - ); + return ( + <span> + {props.label} + <span style={{ color: colors.red600 }}>*</span> + </span> + ); }; diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx index 9f1fd5a13..81744196d 100644 --- a/packages/website/ts/components/ui/simple_loading.tsx +++ b/packages/website/ts/components/ui/simple_loading.tsx @@ -2,16 +2,16 @@ import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; export interface SimpleLoadingProps { - message: string; + message: string; } export const SimpleLoading = (props: SimpleLoadingProps) => { - return ( - <div className="mx-auto pt3" style={{ maxWidth: 400, height: 409 }}> - <div className="relative" style={{ top: '50%', transform: 'translateY(-50%)', height: 95 }}> - <CircularProgress /> - <div className="pt3 pb3">{props.message}</div> - </div> - </div> - ); + return ( + <div className="mx-auto pt3" style={{ maxWidth: 400, height: 409 }}> + <div className="relative" style={{ top: '50%', transform: 'translateY(-50%)', height: 95 }}> + <CircularProgress /> + <div className="pt3 pb3">{props.message}</div> + </div> + </div> + ); }; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index 99e3450de..c41592287 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -3,40 +3,40 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface SwapIconProps { - swapTokensFn: () => void; + swapTokensFn: () => void; } interface SwapIconState { - isHovering: boolean; + isHovering: boolean; } export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> { - public constructor(props: SwapIconProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const swapStyles = { - color: this.state.isHovering ? colors.amber600 : colors.amber800, - fontSize: 50, - }; - return ( - <div - className="mx-auto pt4" - style={{ cursor: 'pointer', height: 50, width: 37.5 }} - onClick={this.props.swapTokensFn} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - > - <i style={swapStyles} className="zmdi zmdi-swap" /> - </div> - ); - } - private _onToggleHover(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + public constructor(props: SwapIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const swapStyles = { + color: this.state.isHovering ? colors.amber600 : colors.amber800, + fontSize: 50, + }; + return ( + <div + className="mx-auto pt4" + style={{ cursor: 'pointer', height: 50, width: 37.5 }} + onClick={this.props.swapTokensFn} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + > + <i style={swapStyles} className="zmdi zmdi-swap" /> + </div> + ); + } + private _onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx index a729821ce..ff57a96de 100644 --- a/packages/website/ts/components/ui/token_icon.tsx +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -4,24 +4,24 @@ import { Identicon } from 'ts/components/ui/identicon'; import { Token } from 'ts/types'; interface TokenIconProps { - token: Token; - diameter: number; + token: Token; + diameter: number; } interface TokenIconState {} export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> { - public render() { - const token = this.props.token; - const diameter = this.props.diameter; - return ( - <div> - {token.isRegistered && !_.isUndefined(token.iconUrl) ? ( - <img style={{ width: diameter, height: diameter }} src={token.iconUrl} /> - ) : ( - <Identicon address={token.address} diameter={diameter} /> - )} - </div> - ); - } + public render() { + const token = this.props.token; + const diameter = this.props.diameter; + return ( + <div> + {token.isRegistered && !_.isUndefined(token.iconUrl) ? ( + <img style={{ width: diameter, height: diameter }} src={token.iconUrl} /> + ) : ( + <Identicon address={token.address} diameter={diameter} /> + )} + </div> + ); + } } diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx index a9779ac62..092954086 100644 --- a/packages/website/ts/components/visual_order.tsx +++ b/packages/website/ts/components/visual_order.tsx @@ -8,69 +8,69 @@ import { utils } from 'ts/utils/utils'; const PRECISION = 5; interface VisualOrderProps { - orderTakerAddress: string; - orderMakerAddress: string; - makerAssetToken: AssetToken; - takerAssetToken: AssetToken; - makerToken: Token; - takerToken: Token; - networkId: number; - tokenByAddress: TokenByAddress; - isMakerTokenAddressInRegistry: boolean; - isTakerTokenAddressInRegistry: boolean; + orderTakerAddress: string; + orderMakerAddress: string; + makerAssetToken: AssetToken; + takerAssetToken: AssetToken; + makerToken: Token; + takerToken: Token; + networkId: number; + tokenByAddress: TokenByAddress; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; } interface VisualOrderState {} export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderState> { - public render() { - const allTokens = _.values(this.props.tokenByAddress); - const makerImage = this.props.makerToken.iconUrl; - const takerImage = this.props.takerToken.iconUrl; - return ( - <div> - <div className="clearfix"> - <div className="col col-5 center"> - <Party - label="Send" - address={this.props.takerToken.address} - alternativeImage={takerImage} - networkId={this.props.networkId} - isInTokenRegistry={this.props.isTakerTokenAddressInRegistry} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)} - /> - </div> - <div className="col col-2 center pt1"> - <div className="pb1"> - {this._renderAmount(this.props.takerAssetToken, this.props.takerToken)} - </div> - <div className="lg-p2 md-p2 sm-p1"> - <img src="/images/trade_arrows.png" style={{ width: 47 }} /> - </div> - <div className="pt1"> - {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)} - </div> - </div> - <div className="col col-5 center"> - <Party - label="Receive" - address={this.props.makerToken.address} - alternativeImage={makerImage} - networkId={this.props.networkId} - isInTokenRegistry={this.props.isMakerTokenAddressInRegistry} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)} - /> - </div> - </div> - </div> - ); - } - private _renderAmount(assetToken: AssetToken, token: Token) { - const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); - return ( - <div style={{ fontSize: 13 }}> - {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} - </div> - ); - } + public render() { + const allTokens = _.values(this.props.tokenByAddress); + const makerImage = this.props.makerToken.iconUrl; + const takerImage = this.props.takerToken.iconUrl; + return ( + <div> + <div className="clearfix"> + <div className="col col-5 center"> + <Party + label="Send" + address={this.props.takerToken.address} + alternativeImage={takerImage} + networkId={this.props.networkId} + isInTokenRegistry={this.props.isTakerTokenAddressInRegistry} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)} + /> + </div> + <div className="col col-2 center pt1"> + <div className="pb1"> + {this._renderAmount(this.props.takerAssetToken, this.props.takerToken)} + </div> + <div className="lg-p2 md-p2 sm-p1"> + <img src="/images/trade_arrows.png" style={{ width: 47 }} /> + </div> + <div className="pt1"> + {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)} + </div> + </div> + <div className="col col-5 center"> + <Party + label="Receive" + address={this.props.makerToken.address} + alternativeImage={makerImage} + networkId={this.props.networkId} + isInTokenRegistry={this.props.isMakerTokenAddressInRegistry} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)} + /> + </div> + </div> + </div> + ); + } + private _renderAmount(assetToken: AssetToken, token: Token) { + const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); + return ( + <div style={{ fontSize: 13 }}> + {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} + </div> + ); + } } diff --git a/packages/website/ts/containers/connect_documentation.tsx b/packages/website/ts/containers/connect_documentation.tsx index 2f5326d57..3e02a7d05 100644 --- a/packages/website/ts/containers/connect_documentation.tsx +++ b/packages/website/ts/containers/connect_documentation.tsx @@ -16,81 +16,81 @@ const InstallationMarkdown = require('md/docs/connect/installation'); /* tslint:enable:no-var-requires */ const connectDocSections = { - introduction: 'introduction', - installation: 'installation', - httpClient: 'httpClient', - webSocketOrderbookChannel: 'webSocketOrderbookChannel', - types: constants.TYPES_SECTION_NAME, + introduction: 'introduction', + installation: 'installation', + httpClient: 'httpClient', + webSocketOrderbookChannel: 'webSocketOrderbookChannel', + types: constants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { - displayName: '0x Connect', - subPackageName: 'connect', - packageUrl: 'https://github.com/0xProject/0x.js', - websitePath: WebsitePaths.Connect, - docsJsonRoot: 'https://s3.amazonaws.com/connect-docs-jsons', - menu: { - introduction: [connectDocSections.introduction], - install: [connectDocSections.installation], - httpClient: [connectDocSections.httpClient], - webSocketOrderbookChannel: [connectDocSections.webSocketOrderbookChannel], - types: [connectDocSections.types], - }, - sectionNameToMarkdown: { - [connectDocSections.introduction]: IntroMarkdown, - [connectDocSections.installation]: InstallationMarkdown, - }, - // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is - // currently no way to extract the re-exported types from index.ts via TypeDoc :( - publicTypes: [ - 'Client', - 'FeesRequest', - 'FeesResponse', - 'OrderbookChannel', - 'OrderbookChannelHandler', - 'OrderbookChannelSubscriptionOpts', - 'OrderbookRequest', - 'OrderbookResponse', - 'OrdersRequest', - 'TokenPairsItem', - 'TokenPairsRequest', - 'TokenTradeInfo', - 'Order', - 'SignedOrder', - 'ECSignature', - ], - sectionNameToModulePath: { - [connectDocSections.httpClient]: ['"src/http_client"'], - [connectDocSections.webSocketOrderbookChannel]: ['"src/ws_orderbook_channel"'], - [connectDocSections.types]: ['"src/types"'], - }, - menuSubsectionToVersionWhenIntroduced: {}, - sections: connectDocSections, - visibleConstructors: [connectDocSections.httpClient, connectDocSections.webSocketOrderbookChannel], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), + displayName: '0x Connect', + subPackageName: 'connect', + packageUrl: 'https://github.com/0xProject/0x.js', + websitePath: WebsitePaths.Connect, + docsJsonRoot: 'https://s3.amazonaws.com/connect-docs-jsons', + menu: { + introduction: [connectDocSections.introduction], + install: [connectDocSections.installation], + httpClient: [connectDocSections.httpClient], + webSocketOrderbookChannel: [connectDocSections.webSocketOrderbookChannel], + types: [connectDocSections.types], + }, + sectionNameToMarkdown: { + [connectDocSections.introduction]: IntroMarkdown, + [connectDocSections.installation]: InstallationMarkdown, + }, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'Client', + 'FeesRequest', + 'FeesResponse', + 'OrderbookChannel', + 'OrderbookChannelHandler', + 'OrderbookChannelSubscriptionOpts', + 'OrderbookRequest', + 'OrderbookResponse', + 'OrdersRequest', + 'TokenPairsItem', + 'TokenPairsRequest', + 'TokenTradeInfo', + 'Order', + 'SignedOrder', + 'ECSignature', + ], + sectionNameToModulePath: { + [connectDocSections.httpClient]: ['"src/http_client"'], + [connectDocSections.webSocketOrderbookChannel]: ['"src/ws_orderbook_channel"'], + [connectDocSections.types]: ['"src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: connectDocSections, + visibleConstructors: [connectDocSections.httpClient, connectDocSections.webSocketOrderbookChannel], + convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + docsInfo, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx index c2b781557..3fd31087f 100644 --- a/packages/website/ts/containers/generate_order_form.tsx +++ b/packages/website/ts/containers/generate_order_form.tsx @@ -7,48 +7,48 @@ import { GenerateOrderForm as GenerateOrderFormComponent } from 'ts/components/g import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; import { - BlockchainErrs, - HashData, - SideToAssetToken, - SignatureData, - TokenByAddress, - TokenStateByAddress, + BlockchainErrs, + HashData, + SideToAssetToken, + SignatureData, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; interface GenerateOrderFormProps { - blockchain: Blockchain; - hashData: HashData; - dispatcher: Dispatcher; + blockchain: Blockchain; + hashData: HashData; + dispatcher: Dispatcher; } interface ConnectedState { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - orderExpiryTimestamp: BigNumber; - orderSignatureData: SignatureData; - userAddress: string; - orderTakerAddress: string; - orderSalt: BigNumber; - networkId: number; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + userAddress: string; + orderTakerAddress: string; + orderSalt: BigNumber; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; } const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ - blockchainErr: state.blockchainErr, - blockchainIsLoaded: state.blockchainIsLoaded, - orderExpiryTimestamp: state.orderExpiryTimestamp, - orderSignatureData: state.orderSignatureData, - orderTakerAddress: state.orderTakerAddress, - orderSalt: state.orderSalt, - networkId: state.networkId, - sideToAssetToken: state.sideToAssetToken, - tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, - userAddress: state.userAddress, + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderSignatureData: state.orderSignatureData, + orderTakerAddress: state.orderTakerAddress, + orderSalt: state.orderSalt, + networkId: state.networkId, + sideToAssetToken: state.sideToAssetToken, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, }); export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> = connect(mapStateToProps)( - GenerateOrderFormComponent, + GenerateOrderFormComponent, ); diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx index bc7aa213c..f0247935b 100644 --- a/packages/website/ts/containers/portal.tsx +++ b/packages/website/ts/containers/portal.tsx @@ -10,72 +10,72 @@ import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, To import { constants } from 'ts/utils/constants'; interface ConnectedState { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - hashData: HashData; - networkId: number; - nodeVersion: string; - orderFillAmount: BigNumber; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userEtherBalance: BigNumber; - screenWidth: ScreenWidths; - shouldBlockchainErrDialogBeOpen: boolean; - userAddress: string; - userSuppliedOrderCache: Order; - flashMessage?: string | React.ReactNode; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + userAddress: string; + userSuppliedOrderCache: Order; + flashMessage?: string | React.ReactNode; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => { - const receiveAssetToken = state.sideToAssetToken[Side.Receive]; - const depositAssetToken = state.sideToAssetToken[Side.Deposit]; - const receiveAddress = !_.isUndefined(receiveAssetToken.address) - ? receiveAssetToken.address - : constants.NULL_ADDRESS; - const depositAddress = !_.isUndefined(depositAssetToken.address) - ? depositAssetToken.address - : constants.NULL_ADDRESS; - const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0); - const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0); - const hashData = { - depositAmount, - depositTokenContractAddr: depositAddress, - feeRecipientAddress: constants.NULL_ADDRESS, - makerFee: constants.MAKER_FEE, - orderExpiryTimestamp: state.orderExpiryTimestamp, - orderMakerAddress: state.userAddress, - orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, - receiveAmount, - receiveTokenContractAddr: receiveAddress, - takerFee: constants.TAKER_FEE, - orderSalt: state.orderSalt, - }; - return { - blockchainErr: state.blockchainErr, - blockchainIsLoaded: state.blockchainIsLoaded, - networkId: state.networkId, - nodeVersion: state.nodeVersion, - orderFillAmount: state.orderFillAmount, - hashData, - screenWidth: state.screenWidth, - shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, - tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, - userAddress: state.userAddress, - userEtherBalance: state.userEtherBalance, - userSuppliedOrderCache: state.userSuppliedOrderCache, - flashMessage: state.flashMessage, - }; + const receiveAssetToken = state.sideToAssetToken[Side.Receive]; + const depositAssetToken = state.sideToAssetToken[Side.Deposit]; + const receiveAddress = !_.isUndefined(receiveAssetToken.address) + ? receiveAssetToken.address + : constants.NULL_ADDRESS; + const depositAddress = !_.isUndefined(depositAssetToken.address) + ? depositAssetToken.address + : constants.NULL_ADDRESS; + const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0); + const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0); + const hashData = { + depositAmount, + depositTokenContractAddr: depositAddress, + feeRecipientAddress: constants.NULL_ADDRESS, + makerFee: constants.MAKER_FEE, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderMakerAddress: state.userAddress, + orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, + receiveAmount, + receiveTokenContractAddr: receiveAddress, + takerFee: constants.TAKER_FEE, + orderSalt: state.orderSalt, + }; + return { + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + networkId: state.networkId, + nodeVersion: state.nodeVersion, + orderFillAmount: state.orderFillAmount, + hashData, + screenWidth: state.screenWidth, + shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, + userEtherBalance: state.userEtherBalance, + userSuppliedOrderCache: state.userSuppliedOrderCache, + flashMessage: state.flashMessage, + }; }; const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Portal: React.ComponentClass<PortalComponentAllProps> = connect(mapStateToProps, mapDispatchToProps)( - PortalComponent, + PortalComponent, ); diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx index addfd1af9..8be33b546 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.tsx +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -14,49 +14,49 @@ const IntroMarkdown = require('md/docs/smart_contracts/introduction'); /* tslint:enable:no-var-requires */ const docsInfoConfig: DocsInfoConfig = { - displayName: '0x Smart Contracts', - packageUrl: 'https://github.com/0xProject/contracts', - websitePath: WebsitePaths.SmartContracts, - docsJsonRoot: 'https://s3.amazonaws.com/smart-contracts-docs-json', - menu: { - introduction: [Sections.Introduction], - contracts: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], - }, - sectionNameToMarkdown: { - [Sections.Introduction]: IntroMarkdown, - }, - sections: { - Introduction: Sections.Introduction, - Exchange: Sections.Exchange, - TokenTransferProxy: Sections.TokenTransferProxy, - TokenRegistry: Sections.TokenRegistry, - ZRXToken: Sections.ZRXToken, - }, - visibleConstructors: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], - convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils), + displayName: '0x Smart Contracts', + packageUrl: 'https://github.com/0xProject/contracts', + websitePath: WebsitePaths.SmartContracts, + docsJsonRoot: 'https://s3.amazonaws.com/smart-contracts-docs-json', + menu: { + introduction: [Sections.Introduction], + contracts: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], + }, + sectionNameToMarkdown: { + [Sections.Introduction]: IntroMarkdown, + }, + sections: { + Introduction: Sections.Introduction, + Exchange: Sections.Exchange, + TokenTransferProxy: Sections.TokenTransferProxy, + TokenRegistry: Sections.TokenRegistry, + ZRXToken: Sections.ZRXToken, + }, + visibleConstructors: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], + convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; + docsVersion: string; + availableDocVersions: string[]; } interface ConnectedDispatch { - dispatcher: Dispatcher; - docsInfo: DocsInfo; + dispatcher: Dispatcher; + docsInfo: DocsInfo; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), - docsInfo, + dispatcher: new Dispatcher(dispatch), + docsInfo, }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx index d416cbc12..8ae6a7b73 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.tsx +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -19,152 +19,152 @@ const versioningMarkdown = require('md/docs/0xjs/versioning'); /* tslint:enable:no-var-requires */ const zeroExJsDocSections = { - introduction: 'introduction', - installation: 'installation', - testrpc: 'testrpc', - async: 'async', - errors: 'errors', - versioning: 'versioning', - zeroEx: 'zeroEx', - exchange: 'exchange', - token: 'token', - tokenRegistry: 'tokenRegistry', - etherToken: 'etherToken', - proxy: 'proxy', - orderWatcher: 'orderWatcher', - types: constants.TYPES_SECTION_NAME, + introduction: 'introduction', + installation: 'installation', + testrpc: 'testrpc', + async: 'async', + errors: 'errors', + versioning: 'versioning', + zeroEx: 'zeroEx', + exchange: 'exchange', + token: 'token', + tokenRegistry: 'tokenRegistry', + etherToken: 'etherToken', + proxy: 'proxy', + orderWatcher: 'orderWatcher', + types: constants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { - displayName: '0x.js', - packageUrl: 'https://github.com/0xProject/0x.js', - subPackageName: '0x.js', - websitePath: WebsitePaths.ZeroExJs, - docsJsonRoot: 'https://s3.amazonaws.com/0xjs-docs-jsons', - menu: { - introduction: [zeroExJsDocSections.introduction], - install: [zeroExJsDocSections.installation], - topics: [zeroExJsDocSections.async, zeroExJsDocSections.errors, zeroExJsDocSections.versioning], - zeroEx: [zeroExJsDocSections.zeroEx], - contracts: [ - zeroExJsDocSections.exchange, - zeroExJsDocSections.token, - zeroExJsDocSections.tokenRegistry, - zeroExJsDocSections.etherToken, - zeroExJsDocSections.proxy, - ], - orderWatcher: [zeroExJsDocSections.orderWatcher], - types: [zeroExJsDocSections.types], - }, - sectionNameToMarkdown: { - [zeroExJsDocSections.introduction]: IntroMarkdown, - [zeroExJsDocSections.installation]: InstallationMarkdown, - [zeroExJsDocSections.async]: AsyncMarkdown, - [zeroExJsDocSections.errors]: ErrorsMarkdown, - [zeroExJsDocSections.versioning]: versioningMarkdown, - }, - // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is - // currently no way to extract the re-exported types from index.ts via TypeDoc :( - publicTypes: [ - 'Order', - 'SignedOrder', - 'ECSignature', - 'ZeroExError', - 'EventCallback', - 'EventCallbackAsync', - 'EventCallbackSync', - 'ExchangeContractErrs', - 'ContractEvent', - 'Token', - 'ExchangeEvents', - 'IndexedFilterValues', - 'SubscriptionOpts', - 'BlockRange', - 'BlockParam', - 'OrderFillOrKillRequest', - 'OrderCancellationRequest', - 'OrderFillRequest', - 'ContractEventEmitter', - 'Web3Provider', - 'ContractEventArgs', - 'LogCancelArgs', - 'LogFillArgs', - 'LogErrorContractEventArgs', - 'LogFillContractEventArgs', - 'LogCancelContractEventArgs', - 'EtherTokenContractEventArgs', - 'WithdrawalContractEventArgs', - 'DepositContractEventArgs', - 'TokenEvents', - 'ExchangeContractEventArgs', - 'TransferContractEventArgs', - 'ApprovalContractEventArgs', - 'TokenContractEventArgs', - 'ZeroExConfig', - 'TransactionReceiptWithDecodedLogs', - 'LogWithDecodedArgs', - 'EtherTokenEvents', - 'BlockParamLiteral', - 'DecodedLogArgs', - 'MethodOpts', - 'ValidateOrderFillableOpts', - 'OrderTransactionOpts', - 'TransactionOpts', - 'ContractEventArg', - 'LogEvent', - 'LogEntry', - 'DecodedLogEvent', - 'EventWatcherCallback', - 'OnOrderStateChangeCallback', - 'OrderStateValid', - 'OrderStateInvalid', - 'OrderState', - 'FilterObject', - ], - sectionNameToModulePath: { - [zeroExJsDocSections.zeroEx]: ['"src/0x"'], - [zeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], - [zeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], - [zeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], - [zeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], - [zeroExJsDocSections.proxy]: [ - '"src/contract_wrappers/proxy_wrapper"', - '"src/contract_wrappers/token_transfer_proxy_wrapper"', - ], - [zeroExJsDocSections.orderWatcher]: ['"src/order_watcher/order_state_watcher"'], - [zeroExJsDocSections.types]: ['"src/types"'], - }, - menuSubsectionToVersionWhenIntroduced: { - [zeroExJsDocSections.etherToken]: '0.7.1', - [zeroExJsDocSections.proxy]: '0.8.0', - [zeroExJsDocSections.orderWatcher]: '0.27.1', - }, - sections: zeroExJsDocSections, - visibleConstructors: [zeroExJsDocSections.zeroEx], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), + displayName: '0x.js', + packageUrl: 'https://github.com/0xProject/0x.js', + subPackageName: '0x.js', + websitePath: WebsitePaths.ZeroExJs, + docsJsonRoot: 'https://s3.amazonaws.com/0xjs-docs-jsons', + menu: { + introduction: [zeroExJsDocSections.introduction], + install: [zeroExJsDocSections.installation], + topics: [zeroExJsDocSections.async, zeroExJsDocSections.errors, zeroExJsDocSections.versioning], + zeroEx: [zeroExJsDocSections.zeroEx], + contracts: [ + zeroExJsDocSections.exchange, + zeroExJsDocSections.token, + zeroExJsDocSections.tokenRegistry, + zeroExJsDocSections.etherToken, + zeroExJsDocSections.proxy, + ], + orderWatcher: [zeroExJsDocSections.orderWatcher], + types: [zeroExJsDocSections.types], + }, + sectionNameToMarkdown: { + [zeroExJsDocSections.introduction]: IntroMarkdown, + [zeroExJsDocSections.installation]: InstallationMarkdown, + [zeroExJsDocSections.async]: AsyncMarkdown, + [zeroExJsDocSections.errors]: ErrorsMarkdown, + [zeroExJsDocSections.versioning]: versioningMarkdown, + }, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'Order', + 'SignedOrder', + 'ECSignature', + 'ZeroExError', + 'EventCallback', + 'EventCallbackAsync', + 'EventCallbackSync', + 'ExchangeContractErrs', + 'ContractEvent', + 'Token', + 'ExchangeEvents', + 'IndexedFilterValues', + 'SubscriptionOpts', + 'BlockRange', + 'BlockParam', + 'OrderFillOrKillRequest', + 'OrderCancellationRequest', + 'OrderFillRequest', + 'ContractEventEmitter', + 'Web3Provider', + 'ContractEventArgs', + 'LogCancelArgs', + 'LogFillArgs', + 'LogErrorContractEventArgs', + 'LogFillContractEventArgs', + 'LogCancelContractEventArgs', + 'EtherTokenContractEventArgs', + 'WithdrawalContractEventArgs', + 'DepositContractEventArgs', + 'TokenEvents', + 'ExchangeContractEventArgs', + 'TransferContractEventArgs', + 'ApprovalContractEventArgs', + 'TokenContractEventArgs', + 'ZeroExConfig', + 'TransactionReceiptWithDecodedLogs', + 'LogWithDecodedArgs', + 'EtherTokenEvents', + 'BlockParamLiteral', + 'DecodedLogArgs', + 'MethodOpts', + 'ValidateOrderFillableOpts', + 'OrderTransactionOpts', + 'TransactionOpts', + 'ContractEventArg', + 'LogEvent', + 'LogEntry', + 'DecodedLogEvent', + 'EventWatcherCallback', + 'OnOrderStateChangeCallback', + 'OrderStateValid', + 'OrderStateInvalid', + 'OrderState', + 'FilterObject', + ], + sectionNameToModulePath: { + [zeroExJsDocSections.zeroEx]: ['"src/0x"'], + [zeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], + [zeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], + [zeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], + [zeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], + [zeroExJsDocSections.proxy]: [ + '"src/contract_wrappers/proxy_wrapper"', + '"src/contract_wrappers/token_transfer_proxy_wrapper"', + ], + [zeroExJsDocSections.orderWatcher]: ['"src/order_watcher/order_state_watcher"'], + [zeroExJsDocSections.types]: ['"src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: { + [zeroExJsDocSections.etherToken]: '0.7.1', + [zeroExJsDocSections.proxy]: '0.8.0', + [zeroExJsDocSections.orderWatcher]: '0.27.1', + }, + sections: zeroExJsDocSections, + visibleConstructors: [zeroExJsDocSections.zeroEx], + convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + docsInfo, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 736b23d9d..383e5cbe0 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -14,10 +14,10 @@ declare module 'ledgerco'; declare module 'ethereumjs-tx'; declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } // tslint:disable:max-classes-per-file @@ -25,137 +25,137 @@ declare module '*.json' { // find-version declarations declare function findVersions(version: string): string[]; declare module 'find-versions' { - export = findVersions; + export = findVersions; } // compare-version declarations declare function compareVersions(firstVersion: string, secondVersion: string): number; declare module 'compare-versions' { - export = compareVersions; + export = compareVersions; } // semver-sort declarations declare module 'semver-sort' { - const desc: (versions: string[]) => string[]; + const desc: (versions: string[]) => string[]; } // xml-js declarations declare interface XML2JSONOpts { - compact?: boolean; - spaces?: number; + compact?: boolean; + spaces?: number; } declare module 'xml-js' { - const xml2json: (xml: string, opts: XML2JSONOpts) => string; + const xml2json: (xml: string, opts: XML2JSONOpts) => string; } // This will be defined by default in TS 2.4 // Source: https://github.com/Microsoft/TypeScript/issues/12364 interface System { - import<T>(module: string): Promise<T>; + import<T>(module: string): Promise<T>; } declare var System: System; // jsonschema declarations // Source: https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts declare interface Schema { - id?: string; - $schema?: string; - title?: string; - description?: string; - multipleOf?: number; - maximum?: number; - exclusiveMaximum?: boolean; - minimum?: number; - exclusiveMinimum?: boolean; - maxLength?: number; - minLength?: number; - pattern?: string; - additionalItems?: boolean | Schema; - items?: Schema | Schema[]; - maxItems?: number; - minItems?: number; - uniqueItems?: boolean; - maxProperties?: number; - minProperties?: number; - required?: string[]; - additionalProperties?: boolean | Schema; - definitions?: { - [name: string]: Schema; - }; - properties?: { - [name: string]: Schema; - }; - patternProperties?: { - [name: string]: Schema; - }; - dependencies?: { - [name: string]: Schema | string[]; - }; - enum?: any[]; - type?: string | string[]; - allOf?: Schema[]; - anyOf?: Schema[]; - oneOf?: Schema[]; - not?: Schema; - // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts - // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 - // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files - // As soon as it gets merged we should be good to use types from 'jsonschema' package - $ref?: string; + id?: string; + $schema?: string; + title?: string; + description?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalItems?: boolean | Schema; + items?: Schema | Schema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + additionalProperties?: boolean | Schema; + definitions?: { + [name: string]: Schema; + }; + properties?: { + [name: string]: Schema; + }; + patternProperties?: { + [name: string]: Schema; + }; + dependencies?: { + [name: string]: Schema | string[]; + }; + enum?: any[]; + type?: string | string[]; + allOf?: Schema[]; + anyOf?: Schema[]; + oneOf?: Schema[]; + not?: Schema; + // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts + // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 + // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files + // As soon as it gets merged we should be good to use types from 'jsonschema' package + $ref?: string; } // blockies declarations declare interface BlockiesIcon { - toDataURL(): string; + toDataURL(): string; } declare interface BlockiesConfig { - seed: string; + seed: string; } declare function blockies(config: BlockiesConfig): BlockiesIcon; declare module 'blockies' { - export = blockies; + export = blockies; } // is-mobile declarations declare function isMobile(): boolean; declare module 'is-mobile' { - export = isMobile; + export = isMobile; } // web3-provider-engine declarations declare class Subprovider {} declare module 'web3-provider-engine/subproviders/subprovider' { - export = Subprovider; + export = Subprovider; } declare module 'web3-provider-engine/subproviders/rpc' { - import * as Web3 from 'web3'; - class RpcSubprovider { - constructor(options: { rpcUrl: string }); - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, data?: any) => void, - ): void; - } - export = RpcSubprovider; + import * as Web3 from 'web3'; + class RpcSubprovider { + constructor(options: { rpcUrl: string }); + public handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, + ): void; + } + export = RpcSubprovider; } declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; + class Web3ProviderEngine { + public on(event: string, handler: () => void): void; + public send(payload: any): void; + public sendAsync(payload: any, callback: (error: any, response: any) => void): void; + public addProvider(provider: any): void; + public start(): void; + public stop(): void; + } + export = Web3ProviderEngine; } declare interface Artifact { - abi: any; - networks: { - [networkId: number]: { - address: string; - }; - }; + abi: any; + networks: { + [networkId: number]: { + address: string; + }; + }; } diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index df45c80bd..ffb551561 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -32,44 +32,44 @@ import 'less/all.less'; // At the same time webpack statically parses for System.import() to determine bundle chunk split points // so each lazy import needs it's own `System.import()` declaration. const LazyPortal = createLazyComponent('Portal', async () => - System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), + System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), ); const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'), + System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'), ); const LazySmartContractsDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "smartContractDocs" */ 'ts/containers/smart_contracts_documentation'), + System.import<any>(/* webpackChunkName: "smartContractDocs" */ 'ts/containers/smart_contracts_documentation'), ); const LazyConnectDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "connectDocs" */ 'ts/containers/connect_documentation'), + System.import<any>(/* webpackChunkName: "connectDocs" */ 'ts/containers/connect_documentation'), ); const store: ReduxStore<State> = createStore(reducer); render( - <Router> - <div> - <MuiThemeProvider muiTheme={muiTheme}> - <Provider store={store}> - <div> - <Switch> - <Route exact={true} path="/" component={Landing as any} /> - <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> - <Route path={`${WebsitePaths.Portal}`} component={LazyPortal} /> - <Route path={`${WebsitePaths.FAQ}`} component={FAQ as any} /> - <Route path={`${WebsitePaths.About}`} component={About as any} /> - <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} /> - <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> - <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> - <Route - path={`${WebsitePaths.SmartContracts}/:version?`} - component={LazySmartContractsDocumentation} - /> - <Route component={NotFound as any} /> - </Switch> - </div> - </Provider> - </MuiThemeProvider> - </div> - </Router>, - document.getElementById('app'), + <Router> + <div> + <MuiThemeProvider muiTheme={muiTheme}> + <Provider store={store}> + <div> + <Switch> + <Route exact={true} path="/" component={Landing as any} /> + <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> + <Route path={`${WebsitePaths.Portal}`} component={LazyPortal} /> + <Route path={`${WebsitePaths.FAQ}`} component={FAQ as any} /> + <Route path={`${WebsitePaths.About}`} component={About as any} /> + <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} /> + <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> + <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> + <Route + path={`${WebsitePaths.SmartContracts}/:version?`} + component={LazySmartContractsDocumentation} + /> + <Route component={NotFound as any} /> + </Switch> + </div> + </Provider> + </MuiThemeProvider> + </div> + </Router>, + document.getElementById('app'), ); diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx index 5efebb667..48800c2dd 100644 --- a/packages/website/ts/lazy_component.tsx +++ b/packages/website/ts/lazy_component.tsx @@ -2,12 +2,12 @@ import * as _ from 'lodash'; import * as React from 'react'; interface LazyComponentProps { - reactComponentPromise: Promise<React.ComponentClass<any>>; - reactComponentProps: any; + reactComponentPromise: Promise<React.ComponentClass<any>>; + reactComponentProps: any; } interface LazyComponentState { - component?: React.ComponentClass<any>; + component?: React.ComponentClass<any>; } /** @@ -15,33 +15,33 @@ interface LazyComponentState { * Source: https://reacttraining.com/react-router/web/guides/code-splitting */ export class LazyComponent extends React.Component<LazyComponentProps, LazyComponentState> { - constructor(props: LazyComponentProps) { - super(props); - this.state = { - component: undefined, - }; - } - public componentWillMount() { - // tslint:disable-next-line:no-floating-promises - this._loadComponentFireAndForgetAsync(this.props); - } - public componentWillReceiveProps(nextProps: LazyComponentProps) { - if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { - // tslint:disable-next-line:no-floating-promises - this._loadComponentFireAndForgetAsync(nextProps); - } - } - public render() { - return _.isUndefined(this.state.component) - ? null - : React.createElement(this.state.component, this.props.reactComponentProps); - } - private async _loadComponentFireAndForgetAsync(props: LazyComponentProps) { - const component = await props.reactComponentPromise; - this.setState({ - component, - }); - } + constructor(props: LazyComponentProps) { + super(props); + this.state = { + component: undefined, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._loadComponentFireAndForgetAsync(this.props); + } + public componentWillReceiveProps(nextProps: LazyComponentProps) { + if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { + // tslint:disable-next-line:no-floating-promises + this._loadComponentFireAndForgetAsync(nextProps); + } + } + public render() { + return _.isUndefined(this.state.component) + ? null + : React.createElement(this.state.component, this.props.reactComponentProps); + } + private async _loadComponentFireAndForgetAsync(props: LazyComponentProps) { + const component = await props.reactComponentPromise; + this.setState({ + component, + }); + } } /** @@ -52,15 +52,15 @@ export class LazyComponent extends React.Component<LazyComponentProps, LazyCompo * @example `const LazyPortal = createLazyComponent('Portal', () => System.import<any>('ts/containers/portal'));`` */ export const createLazyComponent = (componentName: string, lazyImport: () => Promise<any>) => { - return (props: any) => { - const reactComponentPromise = (async (): Promise<React.ComponentClass<any>> => { - const mod = await lazyImport(); - const component = mod[componentName]; - if (_.isUndefined(component)) { - throw new Error(`Did not find exported component: ${componentName}`); - } - return component; - })(); - return <LazyComponent reactComponentPromise={reactComponentPromise} reactComponentProps={props} />; - }; + return (props: any) => { + const reactComponentPromise = (async (): Promise<React.ComponentClass<any>> => { + const mod = await lazyImport(); + const component = mod[componentName]; + if (_.isUndefined(component)) { + throw new Error(`Did not find exported component: ${componentName}`); + } + return component; + })(); + return <LazyComponent reactComponentPromise={reactComponentPromise} reactComponentProps={props} />; + }; }; diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts index 4e5b77a0a..d94e6877b 100644 --- a/packages/website/ts/local_storage/local_storage.ts +++ b/packages/website/ts/local_storage/local_storage.ts @@ -1,35 +1,35 @@ import * as _ from 'lodash'; export const localStorage = { - doesExist() { - return !!window.localStorage; - }, - getItemIfExists(key: string): string { - if (!this.doesExist) { - return undefined; - } - const item = window.localStorage.getItem(key); - if (_.isNull(item) || item === 'undefined') { - return ''; - } - return item; - }, - setItem(key: string, value: string) { - if (!this.doesExist || _.isUndefined(value)) { - return; - } - window.localStorage.setItem(key, value); - }, - removeItem(key: string) { - if (!this.doesExist) { - return; - } - window.localStorage.removeItem(key); - }, - getAllKeys(): string[] { - if (!this.doesExist) { - return []; - } - return _.keys(window.localStorage); - }, + doesExist() { + return !!window.localStorage; + }, + getItemIfExists(key: string): string { + if (!this.doesExist) { + return undefined; + } + const item = window.localStorage.getItem(key); + if (_.isNull(item) || item === 'undefined') { + return ''; + } + return item; + }, + setItem(key: string, value: string) { + if (!this.doesExist || _.isUndefined(value)) { + return; + } + window.localStorage.setItem(key, value); + }, + removeItem(key: string) { + if (!this.doesExist) { + return; + } + window.localStorage.removeItem(key); + }, + getAllKeys(): string[] { + if (!this.doesExist) { + return []; + } + return _.keys(window.localStorage); + }, }; diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts index 2c62084e0..7733e8436 100644 --- a/packages/website/ts/local_storage/tracked_token_storage.ts +++ b/packages/website/ts/local_storage/tracked_token_storage.ts @@ -7,61 +7,61 @@ const TRACKED_TOKENS_KEY = 'trackedTokens'; const TRACKED_TOKENS_CLEAR_KEY = 'lastClearTrackedTokensDate'; export const trackedTokenStorage = { - // Clear trackedTokens localStorage if we've updated the config variable in an update - // that introduced a backward incompatible change requiring the tracked tokens to be re-set - clearIfRequired(): void { - const lastClearFillDate = localStorage.getItemIfExists(TRACKED_TOKENS_CLEAR_KEY); - if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE) { - localStorage.removeItem(TRACKED_TOKENS_KEY); - } - localStorage.setItem(TRACKED_TOKENS_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE); - }, - addTrackedTokenToUser(userAddress: string, networkId: number, token: Token): void { - const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); - let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - if (_.isUndefined(trackedTokensByNetworkId)) { - trackedTokensByNetworkId = {}; - } - const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) - ? trackedTokensByNetworkId[networkId] - : []; - trackedTokens.push(token); - trackedTokensByNetworkId[networkId] = trackedTokens; - trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; - const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); - localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); - }, - getTrackedTokensByUserAddress(): TrackedTokensByUserAddress { - const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); - if (_.isEmpty(trackedTokensJSONString)) { - return {}; - } - const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); - return trackedTokensByUserAddress; - }, - getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { - const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); - if (_.isEmpty(trackedTokensJSONString)) { - return undefined; - } - const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); - const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - if (_.isUndefined(trackedTokensByNetworkId)) { - return undefined; - } - const trackedTokens = trackedTokensByNetworkId[networkId]; - return trackedTokens; - }, - removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void { - const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); - const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - const trackedTokens = trackedTokensByNetworkId[networkId]; - const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { - return token.address !== tokenAddress; - }); - trackedTokensByNetworkId[networkId] = remainingTrackedTokens; - trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; - const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); - localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); - }, + // Clear trackedTokens localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the tracked tokens to be re-set + clearIfRequired(): void { + const lastClearFillDate = localStorage.getItemIfExists(TRACKED_TOKENS_CLEAR_KEY); + if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE) { + localStorage.removeItem(TRACKED_TOKENS_KEY); + } + localStorage.setItem(TRACKED_TOKENS_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE); + }, + addTrackedTokenToUser(userAddress: string, networkId: number, token: Token): void { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + trackedTokensByNetworkId = {}; + } + const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) + ? trackedTokensByNetworkId[networkId] + : []; + trackedTokens.push(token); + trackedTokensByNetworkId[networkId] = trackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, + getTrackedTokensByUserAddress(): TrackedTokensByUserAddress { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return {}; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + return trackedTokensByUserAddress; + }, + getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return undefined; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + return undefined; + } + const trackedTokens = trackedTokensByNetworkId[networkId]; + return trackedTokens; + }, + removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + const trackedTokens = trackedTokensByNetworkId[networkId]; + const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { + return token.address !== tokenAddress; + }); + trackedTokensByNetworkId[networkId] = remainingTrackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, }; diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index b20244b29..df731236e 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -11,84 +11,84 @@ const FILLS_LATEST_BLOCK = 'fillsLatestBlock'; const FILL_CLEAR_KEY = 'lastClearFillDate'; export const tradeHistoryStorage = { - // Clear all fill related localStorage if we've updated the config variable in an update - // that introduced a backward incompatible change requiring the user to re-fetch the fills from - // the blockchain - clearIfRequired() { - const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); - if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE) { - const localStorageKeys = localStorage.getAllKeys(); - _.each(localStorageKeys, key => { - if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { - localStorage.removeItem(key); - } - }); - } - localStorage.setItem(FILL_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE); - }, - addFillToUser(userAddress: string, networkId: number, fill: Fill) { - const fillsByHash = this.getUserFillsByHash(userAddress, networkId); - const fillHash = this._getFillHash(fill); - const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); - if (doesFillExist) { - return; // noop - } - fillsByHash[fillHash] = fill; - const userFillsJSONString = JSON.stringify(fillsByHash); - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - localStorage.setItem(userFillsKey, userFillsJSONString); - }, - removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { - const fillsByHash = this.getUserFillsByHash(userAddress, networkId); - const fillHash = this._getFillHash(fill); - const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); - if (!doesFillExist) { - return; // noop - } - delete fillsByHash[fillHash]; - const userFillsJSONString = JSON.stringify(fillsByHash); - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - localStorage.setItem(userFillsKey, userFillsJSONString); - }, - getUserFillsByHash(userAddress: string, networkId: number): { [fillHash: string]: Fill } { - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); - if (_.isEmpty(userFillsJSONString)) { - return {}; - } - const userFillsByHash = JSON.parse(userFillsJSONString); - _.each(userFillsByHash, (fill, hash) => { - fill.paidMakerFee = new BigNumber(fill.paidMakerFee); - fill.paidTakerFee = new BigNumber(fill.paidTakerFee); - fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); - fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); - }); - return userFillsByHash; - }, - getFillsLatestBlock(userAddress: string, networkId: number): number { - const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); - const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); - if (_.isEmpty(blockNumberStr)) { - return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; - } - const blockNumber = _.parseInt(blockNumberStr); - return blockNumber; - }, - setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { - const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); - localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); - }, - _getUserFillsKey(userAddress: string, networkId: number) { - const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; - return userFillsKey; - }, - _getFillsLatestBlockKey(userAddress: string, networkId: number) { - const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; - return userFillsLatestBlockKey; - }, - _getFillHash(fill: Fill): string { - const fillJSON = JSON.stringify(fill); - const fillHash = ethUtil.sha256(fillJSON); - return fillHash.toString('hex'); - }, + // Clear all fill related localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the user to re-fetch the fills from + // the blockchain + clearIfRequired() { + const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); + if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE) { + const localStorageKeys = localStorage.getAllKeys(); + _.each(localStorageKeys, key => { + if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { + localStorage.removeItem(key); + } + }); + } + localStorage.setItem(FILL_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE); + }, + addFillToUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (doesFillExist) { + return; // noop + } + fillsByHash[fillHash] = fill; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (!doesFillExist) { + return; // noop + } + delete fillsByHash[fillHash]; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + getUserFillsByHash(userAddress: string, networkId: number): { [fillHash: string]: Fill } { + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); + if (_.isEmpty(userFillsJSONString)) { + return {}; + } + const userFillsByHash = JSON.parse(userFillsJSONString); + _.each(userFillsByHash, (fill, hash) => { + fill.paidMakerFee = new BigNumber(fill.paidMakerFee); + fill.paidTakerFee = new BigNumber(fill.paidTakerFee); + fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); + fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); + }); + return userFillsByHash; + }, + getFillsLatestBlock(userAddress: string, networkId: number): number { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); + if (_.isEmpty(blockNumberStr)) { + return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; + } + const blockNumber = _.parseInt(blockNumberStr); + return blockNumber; + }, + setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); + }, + _getUserFillsKey(userAddress: string, networkId: number) { + const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; + return userFillsKey; + }, + _getFillsLatestBlockKey(userAddress: string, networkId: number) { + const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; + return userFillsLatestBlockKey; + }, + _getFillHash(fill: Fill): string { + const fillJSON = JSON.stringify(fill); + const fillHash = ethUtil.sha256(fillJSON); + return fillHash.toString('hex'); + }, }; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 411456427..c929673f5 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -10,239 +10,239 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const teamRow1: ProfileInfo[] = [ - { - name: 'Will Warren', - title: 'Co-founder & CEO', - description: `Smart contract R&D. Previously applied physics at Los Alamos \ + { + name: 'Will Warren', + title: 'Co-founder & CEO', + description: `Smart contract R&D. Previously applied physics at Los Alamos \ Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`, - image: '/images/team/will.jpg', - linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', - github: 'https://github.com/willwarren89', - medium: 'https://medium.com/@willwarren89', - }, - { - name: 'Amir Bandeali', - title: 'Co-founder & CTO', - description: `Smart contract R&D. Previously fixed income trader at DRW. \ + image: '/images/team/will.jpg', + linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', + github: 'https://github.com/willwarren89', + medium: 'https://medium.com/@willwarren89', + }, + { + name: 'Amir Bandeali', + title: 'Co-founder & CTO', + description: `Smart contract R&D. Previously fixed income trader at DRW. \ Finance at University of Illinois, Urbana-Champaign.`, - image: '/images/team/amir.jpeg', - linkedIn: 'https://www.linkedin.com/in/abandeali1/', - github: 'https://github.com/abandeali1', - medium: 'https://medium.com/@abandeali1', - }, - { - name: 'Fabio Berger', - title: 'Senior Engineer', - description: `Full-stack blockchain engineer. Previously software engineer \ + image: '/images/team/amir.jpeg', + linkedIn: 'https://www.linkedin.com/in/abandeali1/', + github: 'https://github.com/abandeali1', + medium: 'https://medium.com/@abandeali1', + }, + { + name: 'Fabio Berger', + title: 'Senior Engineer', + description: `Full-stack blockchain engineer. Previously software engineer \ at Airtable and founder of WealthLift. Computer science at Duke.`, - image: '/images/team/fabio.jpg', - linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', - github: 'https://github.com/fabioberger', - medium: 'https://medium.com/@fabioberger', - }, + image: '/images/team/fabio.jpg', + linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', + github: 'https://github.com/fabioberger', + medium: 'https://medium.com/@fabioberger', + }, ]; const teamRow2: ProfileInfo[] = [ - { - name: 'Alex Xu', - title: 'Director of Operations', - description: `Strategy and operations. Previously digital marketing at Google \ + { + name: 'Alex Xu', + title: 'Director of Operations', + description: `Strategy and operations. Previously digital marketing at Google \ and vendor management at Amazon. Economics at UC San Diego.`, - image: '/images/team/alex.jpg', - linkedIn: 'https://www.linkedin.com/in/alex-xu/', - github: '', - medium: 'https://medium.com/@aqxu', - }, - { - name: 'Leonid Logvinov', - title: 'Engineer', - description: `Full-stack blockchain engineer. Previously blockchain engineer \ + image: '/images/team/alex.jpg', + linkedIn: 'https://www.linkedin.com/in/alex-xu/', + github: '', + medium: 'https://medium.com/@aqxu', + }, + { + name: 'Leonid Logvinov', + title: 'Engineer', + description: `Full-stack blockchain engineer. Previously blockchain engineer \ at Neufund. Computer science at University of Warsaw.`, - image: '/images/team/leonid.png', - linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', - github: 'https://github.com/LogvinovLeon', - medium: 'https://medium.com/@Logvinov', - }, - { - name: 'Ben Burns', - title: 'Designer', - description: `Product, motion, and graphic designer. Previously designer \ + image: '/images/team/leonid.png', + linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', + github: 'https://github.com/LogvinovLeon', + medium: 'https://medium.com/@Logvinov', + }, + { + name: 'Ben Burns', + title: 'Designer', + description: `Product, motion, and graphic designer. Previously designer \ at Airtable and Apple. Digital Design at University of Cincinnati.`, - image: '/images/team/ben.jpg', - linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', - github: '', - medium: '', - }, + image: '/images/team/ben.jpg', + linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', + github: '', + medium: '', + }, ]; const teamRow3: ProfileInfo[] = [ - { - name: 'Brandon Millman', - title: 'Senior Engineer', - description: `Full-stack engineer. Previously senior software engineer at \ + { + name: 'Brandon Millman', + title: 'Senior Engineer', + description: `Full-stack engineer. Previously senior software engineer at \ Twitter. Electrical and Computer Engineering at Duke.`, - image: '/images/team/brandon.png', - linkedIn: 'https://www.linkedin.com/in/brandon-millman-b093a022/', - github: 'https://github.com/BMillman19', - medium: 'https://medium.com/@bchillman', - }, - { - name: 'Tom Schmidt', - title: 'Product Manager', - description: `Previously engineering at Apple, product management at Facebook and Instagram. Computer Science at Stanford.`, - image: '/images/team/tom.jpg', - linkedIn: 'https://www.linkedin.com/in/tomhschmidt/', - github: 'https://github.com/tomhschmidt', - medium: '', - }, - { - name: 'Jacob Evans', - title: 'Blockchain Engineer', - description: `Previously software engineer at Qantas and RSA Security.`, - image: '/images/team/jacob.jpg', - linkedIn: 'https://www.linkedin.com/in/dekzter/', - github: 'https://github.com/dekz', - medium: '', - }, + image: '/images/team/brandon.png', + linkedIn: 'https://www.linkedin.com/in/brandon-millman-b093a022/', + github: 'https://github.com/BMillman19', + medium: 'https://medium.com/@bchillman', + }, + { + name: 'Tom Schmidt', + title: 'Product Manager', + description: `Previously engineering at Apple, product management at Facebook and Instagram. Computer Science at Stanford.`, + image: '/images/team/tom.jpg', + linkedIn: 'https://www.linkedin.com/in/tomhschmidt/', + github: 'https://github.com/tomhschmidt', + medium: '', + }, + { + name: 'Jacob Evans', + title: 'Blockchain Engineer', + description: `Previously software engineer at Qantas and RSA Security.`, + image: '/images/team/jacob.jpg', + linkedIn: 'https://www.linkedin.com/in/dekzter/', + github: 'https://github.com/dekz', + medium: '', + }, ]; const advisors: ProfileInfo[] = [ - { - name: 'Fred Ehrsam', - description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', - image: '/images/advisors/fred.jpg', - linkedIn: 'https://www.linkedin.com/in/fredehrsam/', - medium: 'https://medium.com/@FEhrsam', - twitter: 'https://twitter.com/FEhrsam', - }, - { - name: 'Olaf Carlson-Wee', - image: '/images/advisors/olaf.png', - description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', - linkedIn: 'https://www.linkedin.com/in/olafcw/', - angellist: 'https://angel.co/olafcw', - }, - { - name: 'Joey Krug', - description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, - image: '/images/advisors/joey.jpg', - linkedIn: 'https://www.linkedin.com/in/joeykrug/', - github: 'https://github.com/joeykrug', - angellist: 'https://angel.co/joeykrug', - }, - { - name: 'Linda Xie', - description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', - image: '/images/advisors/linda.jpg', - linkedIn: 'https://www.linkedin.com/in/lindaxie/', - medium: 'https://medium.com/@linda.xie', - twitter: 'https://twitter.com/ljxie', - }, + { + name: 'Fred Ehrsam', + description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', + image: '/images/advisors/fred.jpg', + linkedIn: 'https://www.linkedin.com/in/fredehrsam/', + medium: 'https://medium.com/@FEhrsam', + twitter: 'https://twitter.com/FEhrsam', + }, + { + name: 'Olaf Carlson-Wee', + image: '/images/advisors/olaf.png', + description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', + linkedIn: 'https://www.linkedin.com/in/olafcw/', + angellist: 'https://angel.co/olafcw', + }, + { + name: 'Joey Krug', + description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, + image: '/images/advisors/joey.jpg', + linkedIn: 'https://www.linkedin.com/in/joeykrug/', + github: 'https://github.com/joeykrug', + angellist: 'https://angel.co/joeykrug', + }, + { + name: 'Linda Xie', + description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', + image: '/images/advisors/linda.jpg', + linkedIn: 'https://www.linkedin.com/in/lindaxie/', + medium: 'https://medium.com/@linda.xie', + twitter: 'https://twitter.com/ljxie', + }, ]; export interface AboutProps { - source: string; - location: Location; + source: string; + location: Location; } interface AboutState {} const styles: Styles = { - header: { - fontFamily: 'Roboto Mono', - fontSize: 36, - color: 'black', - paddingTop: 110, - }, - weAreHiring: { - fontSize: 30, - color: colors.darkestGrey, - fontFamily: 'Roboto Mono', - letterSpacing: 7.5, - }, + header: { + fontFamily: 'Roboto Mono', + fontSize: 36, + color: 'black', + paddingTop: 110, + }, + weAreHiring: { + fontSize: 30, + color: colors.darkestGrey, + fontFamily: 'Roboto Mono', + letterSpacing: 7.5, + }, }; export class About extends React.Component<AboutProps, AboutState> { - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div style={{ backgroundColor: colors.lightestGrey }}> - <DocumentTitle title="0x About Us" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - style={{ backgroundColor: colors.lightestGrey }} - /> - <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> - <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> - <div style={styles.header}>About us:</div> - <div - className="pt3" - style={{ - fontSize: 17, - color: colors.darkestGrey, - lineHeight: 1.5, - }} - > - Our team is a diverse and globally distributed group with backgrounds in engineering, - research, business and design. We are passionate about decentralized technology and its - potential to act as an equalizing force in the world. - </div> - </div> - <div className="pt3 md-px4 lg-px0"> - <div className="clearfix pb3">{this._renderProfiles(teamRow1)}</div> - <div className="clearfix">{this._renderProfiles(teamRow2)}</div> - <div className="clearfix">{this._renderProfiles(teamRow3)}</div> - </div> - <div className="pt3 pb2"> - <div - className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3" - style={{ - color: colors.grey, - fontSize: 24, - fontFamily: 'Roboto Mono', - }} - > - Advisors: - </div> - <div className="clearfix">{this._renderProfiles(advisors)}</div> - </div> - <div className="mx-auto py4 sm-px3" style={{ maxWidth: 308 }}> - <div className="pb2" style={styles.weAreHiring}> - WE'RE HIRING - </div> - <div - className="pb4 mb4" - style={{ - fontSize: 16, - color: colors.darkestGrey, - lineHeight: 1.5, - letterSpacing: '0.5px', - }} - > - We are seeking outstanding candidates to{' '} - <a href={constants.URL_ANGELLIST} target="_blank" style={{ color: 'black' }}> - join our team - </a> - . We value passion, diversity and unique perspectives. - </div> - </div> - </div> - <Footer /> - </div> - ); - } - private _renderProfiles(profiles: ProfileInfo[]) { - const numIndiv = profiles.length; - const colSize = utils.getColSize(numIndiv); - return _.map(profiles, profile => { - return ( - <div key={`profile-${profile.name}`}> - <Profile colSize={colSize} profileInfo={profile} /> - </div> - ); - }); - } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div style={{ backgroundColor: colors.lightestGrey }}> + <DocumentTitle title="0x About Us" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + style={{ backgroundColor: colors.lightestGrey }} + /> + <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> + <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> + <div style={styles.header}>About us:</div> + <div + className="pt3" + style={{ + fontSize: 17, + color: colors.darkestGrey, + lineHeight: 1.5, + }} + > + Our team is a diverse and globally distributed group with backgrounds in engineering, + research, business and design. We are passionate about decentralized technology and its + potential to act as an equalizing force in the world. + </div> + </div> + <div className="pt3 md-px4 lg-px0"> + <div className="clearfix pb3">{this._renderProfiles(teamRow1)}</div> + <div className="clearfix">{this._renderProfiles(teamRow2)}</div> + <div className="clearfix">{this._renderProfiles(teamRow3)}</div> + </div> + <div className="pt3 pb2"> + <div + className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3" + style={{ + color: colors.grey, + fontSize: 24, + fontFamily: 'Roboto Mono', + }} + > + Advisors: + </div> + <div className="clearfix">{this._renderProfiles(advisors)}</div> + </div> + <div className="mx-auto py4 sm-px3" style={{ maxWidth: 308 }}> + <div className="pb2" style={styles.weAreHiring}> + WE'RE HIRING + </div> + <div + className="pb4 mb4" + style={{ + fontSize: 16, + color: colors.darkestGrey, + lineHeight: 1.5, + letterSpacing: '0.5px', + }} + > + We are seeking outstanding candidates to{' '} + <a href={constants.URL_ANGELLIST} target="_blank" style={{ color: 'black' }}> + join our team + </a> + . We value passion, diversity and unique perspectives. + </div> + </div> + </div> + <Footer /> + </div> + ); + } + private _renderProfiles(profiles: ProfileInfo[]) { + const numIndiv = profiles.length; + const colSize = utils.getColSize(numIndiv); + return _.map(profiles, profile => { + return ( + <div key={`profile-${profile.name}`}> + <Profile colSize={colSize} profileInfo={profile} /> + </div> + ); + }); + } } diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index f1830851f..18b4e0d5a 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -5,75 +5,75 @@ import { colors } from 'ts/utils/colors'; const IMAGE_DIMENSION = 149; const styles: Styles = { - subheader: { - textTransform: 'uppercase', - fontSize: 32, - margin: 0, - }, - imageContainer: { - width: IMAGE_DIMENSION, - height: IMAGE_DIMENSION, - boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', - }, + subheader: { + textTransform: 'uppercase', + fontSize: 32, + margin: 0, + }, + imageContainer: { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', + }, }; interface ProfileProps { - colSize: number; - profileInfo: ProfileInfo; + colSize: number; + profileInfo: ProfileInfo; } export function Profile(props: ProfileProps) { - return ( - <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> - <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> - <div className="circle overflow-hidden mx-auto" style={styles.imageContainer}> - <img width={IMAGE_DIMENSION} src={props.profileInfo.image} /> - </div> - <div className="center" style={{ fontSize: 18, fontWeight: 'bold', paddingTop: 20 }}> - {props.profileInfo.name} - </div> - {!_.isUndefined(props.profileInfo.title) && ( - <div - className="pt1 center" - style={{ - fontSize: 14, - fontFamily: 'Roboto Mono', - color: colors.darkGrey, - }} - > - {props.profileInfo.title.toUpperCase()} - </div> - )} - <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> - {props.profileInfo.description} - </div> - <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> - {renderSocialMediaIcons(props.profileInfo)} - </div> - </div> - </div> - ); + return ( + <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> + <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> + <div className="circle overflow-hidden mx-auto" style={styles.imageContainer}> + <img width={IMAGE_DIMENSION} src={props.profileInfo.image} /> + </div> + <div className="center" style={{ fontSize: 18, fontWeight: 'bold', paddingTop: 20 }}> + {props.profileInfo.name} + </div> + {!_.isUndefined(props.profileInfo.title) && ( + <div + className="pt1 center" + style={{ + fontSize: 14, + fontFamily: 'Roboto Mono', + color: colors.darkGrey, + }} + > + {props.profileInfo.title.toUpperCase()} + </div> + )} + <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> + {props.profileInfo.description} + </div> + <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> + {renderSocialMediaIcons(props.profileInfo)} + </div> + </div> + </div> + ); } function renderSocialMediaIcons(profileInfo: ProfileInfo) { - const icons = [ - renderSocialMediaIcon('zmdi-github-box', profileInfo.github), - renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), - renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), - ]; - return icons; + const icons = [ + renderSocialMediaIcon('zmdi-github-box', profileInfo.github), + renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), + renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), + ]; + return icons; } function renderSocialMediaIcon(iconName: string, url: string) { - if (_.isEmpty(url)) { - return null; - } + if (_.isEmpty(url)) { + return null; + } - return ( - <div key={url} className="pr1"> - <a href={url} style={{ color: 'inherit' }} target="_blank" className="text-decoration-none"> - <i className={`zmdi ${iconName}`} style={{ ...styles.socalIcon }} /> - </a> - </div> - ); + return ( + <div key={url} className="pr1"> + <a href={url} style={{ color: 'inherit' }} target="_blank" className="text-decoration-none"> + <i className={`zmdi ${iconName}`} style={{ ...styles.socalIcon }} /> + </a> + </div> + ); } diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx index 6be39f586..23cfd96bd 100644 --- a/packages/website/ts/pages/documentation/comment.tsx +++ b/packages/website/ts/pages/documentation/comment.tsx @@ -4,20 +4,20 @@ import * as ReactMarkdown from 'react-markdown'; import { MarkdownCodeBlock } from 'ts/pages/shared/markdown_code_block'; interface CommentProps { - comment: string; - className?: string; + comment: string; + className?: string; } const defaultProps = { - className: '', + className: '', }; export const Comment: React.SFC<CommentProps> = (props: CommentProps) => { - return ( - <div className={`${props.className} comment`}> - <ReactMarkdown source={props.comment} renderers={{ CodeBlock: MarkdownCodeBlock }} /> - </div> - ); + return ( + <div className={`${props.className} comment`}> + <ReactMarkdown source={props.comment} renderers={{ CodeBlock: MarkdownCodeBlock }} /> + </div> + ); }; Comment.defaultProps = defaultProps; diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx index 0006b8d7e..8d50a2f52 100644 --- a/packages/website/ts/pages/documentation/custom_enum.tsx +++ b/packages/website/ts/pages/documentation/custom_enum.tsx @@ -6,27 +6,27 @@ import { utils } from 'ts/utils/utils'; const STRING_ENUM_CODE_PREFIX = ' strEnum('; interface CustomEnumProps { - type: CustomType; + type: CustomType; } // This component renders custom string enums that was a work-around for versions of // TypeScript <2.4.0 that did not support them natively. We keep it around to support // older versions of 0x.js <0.9.0 export function CustomEnum(props: CustomEnumProps) { - const type = props.type; - if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { - utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); - return null; - } - // Remove the prefix and postfix, leaving only the strEnum values without quotes. - const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); - return ( - <span> - {`{`} - {'\t'} - {enumValues} - <br /> - {`}`} - </span> - ); + const type = props.type; + if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { + utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); + return null; + } + // Remove the prefix and postfix, leaving only the strEnum values without quotes. + const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); + return ( + <span> + {`{`} + {'\t'} + {enumValues} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/docs_info.ts b/packages/website/ts/pages/documentation/docs_info.ts index e1080f3a0..4b1ec122a 100644 --- a/packages/website/ts/pages/documentation/docs_info.ts +++ b/packages/website/ts/pages/documentation/docs_info.ts @@ -1,111 +1,111 @@ import compareVersions = require('compare-versions'); import * as _ from 'lodash'; import { - DocAgnosticFormat, - DocsInfoConfig, - DocsMenu, - DoxityDocObj, - MenuSubsectionsBySection, - SectionsMap, - TypeDocNode, + DocAgnosticFormat, + DocsInfoConfig, + DocsMenu, + DoxityDocObj, + MenuSubsectionsBySection, + SectionsMap, + TypeDocNode, } from 'ts/types'; export class DocsInfo { - public displayName: string; - public packageUrl: string; - public subPackageName?: string; - public websitePath: string; - public docsJsonRoot: string; - public menu: DocsMenu; - public sections: SectionsMap; - public sectionNameToMarkdown: { [sectionName: string]: string }; - private _docsInfo: DocsInfoConfig; - constructor(config: DocsInfoConfig) { - this.displayName = config.displayName; - this.packageUrl = config.packageUrl; - this.subPackageName = config.subPackageName; - this.websitePath = config.websitePath; - this.docsJsonRoot = config.docsJsonRoot; - this.sections = config.sections; - this.sectionNameToMarkdown = config.sectionNameToMarkdown; - this._docsInfo = config; - } - public isPublicType(typeName: string): boolean { - if (_.isUndefined(this._docsInfo.publicTypes)) { - return false; - } - const isPublic = _.includes(this._docsInfo.publicTypes, typeName); - return isPublic; - } - public getModulePathsIfExists(sectionName: string): string[] { - const modulePathsIfExists = this._docsInfo.sectionNameToModulePath[sectionName]; - return modulePathsIfExists; - } - public getMenu(selectedVersion?: string): { [section: string]: string[] } { - if (_.isUndefined(selectedVersion) || _.isUndefined(this._docsInfo.menuSubsectionToVersionWhenIntroduced)) { - return this._docsInfo.menu; - } + public displayName: string; + public packageUrl: string; + public subPackageName?: string; + public websitePath: string; + public docsJsonRoot: string; + public menu: DocsMenu; + public sections: SectionsMap; + public sectionNameToMarkdown: { [sectionName: string]: string }; + private _docsInfo: DocsInfoConfig; + constructor(config: DocsInfoConfig) { + this.displayName = config.displayName; + this.packageUrl = config.packageUrl; + this.subPackageName = config.subPackageName; + this.websitePath = config.websitePath; + this.docsJsonRoot = config.docsJsonRoot; + this.sections = config.sections; + this.sectionNameToMarkdown = config.sectionNameToMarkdown; + this._docsInfo = config; + } + public isPublicType(typeName: string): boolean { + if (_.isUndefined(this._docsInfo.publicTypes)) { + return false; + } + const isPublic = _.includes(this._docsInfo.publicTypes, typeName); + return isPublic; + } + public getModulePathsIfExists(sectionName: string): string[] { + const modulePathsIfExists = this._docsInfo.sectionNameToModulePath[sectionName]; + return modulePathsIfExists; + } + public getMenu(selectedVersion?: string): { [section: string]: string[] } { + if (_.isUndefined(selectedVersion) || _.isUndefined(this._docsInfo.menuSubsectionToVersionWhenIntroduced)) { + return this._docsInfo.menu; + } - const finalMenu = _.cloneDeep(this._docsInfo.menu); - if (_.isUndefined(finalMenu.contracts)) { - return finalMenu; - } + const finalMenu = _.cloneDeep(this._docsInfo.menu); + if (_.isUndefined(finalMenu.contracts)) { + return finalMenu; + } - // TODO: refactor to include more sections then simply the `contracts` section - finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { - const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName]; - if (!_.isUndefined(versionIntroducedIfExists)) { - const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0; - return existsInSelectedVersion; - } else { - return true; - } - }); - return finalMenu; - } - public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { - const menuSubsectionsBySection = {} as MenuSubsectionsBySection; - if (_.isUndefined(docAgnosticFormat)) { - return menuSubsectionsBySection; - } + // TODO: refactor to include more sections then simply the `contracts` section + finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { + const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName]; + if (!_.isUndefined(versionIntroducedIfExists)) { + const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0; + return existsInSelectedVersion; + } else { + return true; + } + }); + return finalMenu; + } + public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } - const docSections = _.keys(this.sections); - _.each(docSections, sectionName => { - const docSection = docAgnosticFormat[sectionName]; - if (_.isUndefined(docSection)) { - return; // no-op - } + const docSections = _.keys(this.sections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } - if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) { - const sortedTypesNames = _.sortBy(docSection.types, 'name'); - const typeNames = _.map(sortedTypesNames, t => t.name); - menuSubsectionsBySection[sectionName] = typeNames; - } else { - let eventNames: string[] = []; - if (!_.isUndefined(docSection.events)) { - const sortedEventNames = _.sortBy(docSection.events, 'name'); - eventNames = _.map(sortedEventNames, m => m.name); - } - const sortedMethodNames = _.sortBy(docSection.methods, 'name'); - const methodNames = _.map(sortedMethodNames, m => m.name); - menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; - } - }); - return menuSubsectionsBySection; - } - public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat) { - if (_.isUndefined(this.sections.types)) { - return {}; - } + if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) { + const sortedTypesNames = _.sortBy(docSection.types, 'name'); + const typeNames = _.map(sortedTypesNames, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + let eventNames: string[] = []; + if (!_.isUndefined(docSection.events)) { + const sortedEventNames = _.sortBy(docSection.events, 'name'); + eventNames = _.map(sortedEventNames, m => m.name); + } + const sortedMethodNames = _.sortBy(docSection.methods, 'name'); + const methodNames = _.map(sortedMethodNames, m => m.name); + menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; + } + }); + return menuSubsectionsBySection; + } + public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat) { + if (_.isUndefined(this.sections.types)) { + return {}; + } - const typeDocSection = docAgnosticFormat[this.sections.types]; - const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); - return typeDefinitionByName; - } - public isVisibleConstructor(sectionName: string): boolean { - return _.includes(this._docsInfo.visibleConstructors, sectionName); - } - public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat { - return this._docsInfo.convertToDocAgnosticFormatFn(docObj, this); - } + const typeDocSection = docAgnosticFormat[this.sections.types]; + const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); + return typeDefinitionByName; + } + public isVisibleConstructor(sectionName: string): boolean { + return _.includes(this._docsInfo.visibleConstructors, sectionName); + } + public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat { + return this._docsInfo.convertToDocAgnosticFormatFn(docObj, this); + } } diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 0fc41d775..2315847ad 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -19,17 +19,17 @@ import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { SectionHeader } from 'ts/pages/shared/section_header'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - AddressByContractName, - DocAgnosticFormat, - DoxityDocObj, - EtherscanLinkSuffixes, - Event, - Networks, - Property, - SolidityMethod, - Styles, - TypeDefinitionByName, - TypescriptMethod, + AddressByContractName, + DocAgnosticFormat, + DoxityDocObj, + EtherscanLinkSuffixes, + Event, + Networks, + Property, + SolidityMethod, + Styles, + TypeDefinitionByName, + TypescriptMethod, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -40,340 +40,340 @@ import { utils } from 'ts/utils/utils'; const SCROLL_TOP_ID = 'docsScrollTop'; const networkNameToColor: { [network: string]: string } = { - [Networks.kovan]: colors.purple, - [Networks.ropsten]: colors.red, - [Networks.mainnet]: colors.turquois, + [Networks.kovan]: colors.purple, + [Networks.ropsten]: colors.red, + [Networks.mainnet]: colors.turquois, }; export interface DocumentationAllProps { - source: string; - location: Location; - dispatcher: Dispatcher; - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface DocumentationState { - docAgnosticFormat?: DocAgnosticFormat; + docAgnosticFormat?: DocAgnosticFormat; } const styles: Styles = { - mainContainers: { - position: 'absolute', - top: 1, - left: 0, - bottom: 0, - right: 0, - overflowZ: 'hidden', - overflowY: 'scroll', - minHeight: 'calc(100vh - 1px)', - WebkitOverflowScrolling: 'touch', - }, - menuContainer: { - borderColor: colors.grey300, - maxWidth: 330, - marginLeft: 20, - }, + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 1px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, }; export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> { - constructor(props: DocumentationAllProps) { - super(props); - this.state = { - docAgnosticFormat: undefined, - }; - } - public componentWillMount() { - const pathName = this.props.location.pathname; - const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); - const versions = findVersions(lastSegment); - const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; - // tslint:disable-next-line:no-floating-promises - this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); - } - public render() { - const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) - ? {} - : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); - return ( - <div> - <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - docsVersion={this.props.docsVersion} - availableDocVersions={this.props.availableDocVersions} - menu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - shouldFullWidth={true} - docsInfo={this.props.docsInfo} - /> - {_.isUndefined(this.state.docAgnosticFormat) ? ( - <div className="col col-12" style={styles.mainContainers}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading documentation... - </div> - </div> - </div> - ) : ( - <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> - <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> - <div - className="border-right absolute" - style={{ ...styles.menuContainer, ...styles.mainContainers }} - > - <NestedSidebarMenu - selectedVersion={this.props.docsVersion} - versions={this.props.availableDocVersions} - topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - docPath={this.props.docsInfo.websitePath} - /> - </div> - </div> - <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> - <div id="documentation" style={styles.mainContainers} className="absolute"> - <div id={SCROLL_TOP_ID} /> - <h1 className="md-pl2 sm-pl3"> - <a href={this.props.docsInfo.packageUrl} target="_blank"> - {this.props.docsInfo.displayName} - </a> - </h1> - {this._renderDocumentation()} - </div> - </div> - </div> - )} - </div> - ); - } - private _renderDocumentation(): React.ReactNode { - const subMenus = _.values(this.props.docsInfo.getMenu()); - const orderedSectionNames = _.flatten(subMenus); + constructor(props: DocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + // tslint:disable-next-line:no-floating-promises + this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( + <div> + <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + docsVersion={this.props.docsVersion} + availableDocVersions={this.props.availableDocVersions} + menu={this.props.docsInfo.getMenu(this.props.docsVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + shouldFullWidth={true} + docsInfo={this.props.docsInfo} + /> + {_.isUndefined(this.state.docAgnosticFormat) ? ( + <div className="col col-12" style={styles.mainContainers}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading documentation... + </div> + </div> + </div> + ) : ( + <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> + <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> + <div + className="border-right absolute" + style={{ ...styles.menuContainer, ...styles.mainContainers }} + > + <NestedSidebarMenu + selectedVersion={this.props.docsVersion} + versions={this.props.availableDocVersions} + topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + docPath={this.props.docsInfo.websitePath} + /> + </div> + </div> + <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> + <div id="documentation" style={styles.mainContainers} className="absolute"> + <div id={SCROLL_TOP_ID} /> + <h1 className="md-pl2 sm-pl3"> + <a href={this.props.docsInfo.packageUrl} target="_blank"> + {this.props.docsInfo.displayName} + </a> + </h1> + {this._renderDocumentation()} + </div> + </div> + </div> + )} + </div> + ); + } + private _renderDocumentation(): React.ReactNode { + const subMenus = _.values(this.props.docsInfo.getMenu()); + const orderedSectionNames = _.flatten(subMenus); - const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.state.docAgnosticFormat); - const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); + const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.state.docAgnosticFormat); + const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); - return renderedSections; - } - private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { - const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName]; - if (!_.isUndefined(markdownFileIfExists)) { - return ( - <MarkdownSection - key={`markdown-section-${sectionName}`} - sectionName={sectionName} - markdownContent={markdownFileIfExists} - /> - ); - } + return renderedSections; + } + private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + <MarkdownSection + key={`markdown-section-${sectionName}`} + sectionName={sectionName} + markdownContent={markdownFileIfExists} + /> + ); + } - const docSection = this.state.docAgnosticFormat[sectionName]; - if (_.isUndefined(docSection)) { - return null; - } + const docSection = this.state.docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return null; + } - const sortedTypes = _.sortBy(docSection.types, 'name'); - const typeDefs = _.map(sortedTypes, customType => { - return ( - <TypeDefinition - sectionName={sectionName} - key={`type-${customType.name}`} - customType={customType} - docsInfo={this.props.docsInfo} - /> - ); - }); + const sortedTypes = _.sortBy(docSection.types, 'name'); + const typeDefs = _.map(sortedTypes, customType => { + return ( + <TypeDefinition + sectionName={sectionName} + key={`type-${customType.name}`} + customType={customType} + docsInfo={this.props.docsInfo} + /> + ); + }); - const sortedProperties = _.sortBy(docSection.properties, 'name'); - const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName)); + const sortedProperties = _.sortBy(docSection.properties, 'name'); + const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName)); - const sortedMethods = _.sortBy(docSection.methods, 'name'); - const methodDefs = _.map(sortedMethods, method => { - const isConstructor = false; - return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); - }); + const sortedMethods = _.sortBy(docSection.methods, 'name'); + const methodDefs = _.map(sortedMethods, method => { + const isConstructor = false; + return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); - const sortedEvents = _.sortBy(docSection.events, 'name'); - const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { - return ( - <EventDefinition - key={`event-${event.name}-${i}`} - event={event} - sectionName={sectionName} - docsInfo={this.props.docsInfo} - /> - ); - }); - return ( - <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> - <div className="flex"> - <div style={{ marginRight: 7 }}> - <SectionHeader sectionName={sectionName} /> - </div> - {this._renderNetworkBadgesIfExists(sectionName)} - </div> - {docSection.comment && <Comment comment={docSection.comment} />} - {docSection.constructors.length > 0 && - this.props.docsInfo.isVisibleConstructor(sectionName) && ( - <div> - <h2 className="thin">Constructor</h2> - {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} - </div> - )} - {docSection.properties.length > 0 && ( - <div> - <h2 className="thin">Properties</h2> - <div>{propertyDefs}</div> - </div> - )} - {docSection.methods.length > 0 && ( - <div> - <h2 className="thin">Methods</h2> - <div>{methodDefs}</div> - </div> - )} - {!_.isUndefined(docSection.events) && - docSection.events.length > 0 && ( - <div> - <h2 className="thin">Events</h2> - <div>{eventDefs}</div> - </div> - )} - {!_.isUndefined(typeDefs) && - typeDefs.length > 0 && ( - <div> - <div>{typeDefs}</div> - </div> - )} - </div> - ); - } - private _renderNetworkBadgesIfExists(sectionName: string) { - const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion]; - const badges = _.map( - networkToAddressByContractName, - (addressByContractName: AddressByContractName, networkName: string) => { - const contractAddress = addressByContractName[sectionName]; - if (_.isUndefined(contractAddress)) { - return null; - } - const linkIfExists = utils.getEtherScanLinkIfExists( - contractAddress, - constants.NETWORK_ID_BY_NAME[networkName], - EtherscanLinkSuffixes.Address, - ); - return ( - <a - key={`badge-${networkName}-${sectionName}`} - href={linkIfExists} - target="_blank" - style={{ color: colors.white, textDecoration: 'none' }} - > - <Badge title={networkName} backgroundColor={networkNameToColor[networkName]} /> - </a> - ); - }, - ); - return badges; - } - private _renderConstructors( - constructors: SolidityMethod[] | TypescriptMethod[], - sectionName: string, - typeDefinitionByName: TypeDefinitionByName, - ): React.ReactNode { - const constructorDefs = _.map(constructors, constructor => { - return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); - }); - return <div>{constructorDefs}</div>; - } - private _renderProperty(sectionName: string, property: Property): React.ReactNode { - return ( - <div key={`property-${property.name}-${property.type.name}`} className="pb3"> - <code className="hljs"> - {property.name}: - <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> - </code> - {property.source && ( - <SourceLink - version={this.props.docsVersion} - source={property.source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} - /> - )} - {property.comment && <Comment comment={property.comment} className="py2" />} - </div> - ); - } - private _renderMethodBlocks( - method: SolidityMethod | TypescriptMethod, - sectionName: string, - isConstructor: boolean, - typeDefinitionByName: TypeDefinitionByName, - ): React.ReactNode { - return ( - <MethodBlock - key={`method-${method.name}-${sectionName}`} - sectionName={sectionName} - method={method} - typeDefinitionByName={typeDefinitionByName} - libraryVersion={this.props.docsVersion} - docsInfo={this.props.docsInfo} - /> - ); - } - private _scrollToHash(): void { - const hashWithPrefix = this.props.location.hash; - let hash = hashWithPrefix.slice(1); - if (_.isEmpty(hash)) { - hash = SCROLL_TOP_ID; // scroll to the top - } + const sortedEvents = _.sortBy(docSection.events, 'name'); + const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { + return ( + <EventDefinition + key={`event-${event.name}-${i}`} + event={event} + sectionName={sectionName} + docsInfo={this.props.docsInfo} + /> + ); + }); + return ( + <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> + <div className="flex"> + <div style={{ marginRight: 7 }}> + <SectionHeader sectionName={sectionName} /> + </div> + {this._renderNetworkBadgesIfExists(sectionName)} + </div> + {docSection.comment && <Comment comment={docSection.comment} />} + {docSection.constructors.length > 0 && + this.props.docsInfo.isVisibleConstructor(sectionName) && ( + <div> + <h2 className="thin">Constructor</h2> + {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} + </div> + )} + {docSection.properties.length > 0 && ( + <div> + <h2 className="thin">Properties</h2> + <div>{propertyDefs}</div> + </div> + )} + {docSection.methods.length > 0 && ( + <div> + <h2 className="thin">Methods</h2> + <div>{methodDefs}</div> + </div> + )} + {!_.isUndefined(docSection.events) && + docSection.events.length > 0 && ( + <div> + <h2 className="thin">Events</h2> + <div>{eventDefs}</div> + </div> + )} + {!_.isUndefined(typeDefs) && + typeDefs.length > 0 && ( + <div> + <div>{typeDefs}</div> + </div> + )} + </div> + ); + } + private _renderNetworkBadgesIfExists(sectionName: string) { + const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion]; + const badges = _.map( + networkToAddressByContractName, + (addressByContractName: AddressByContractName, networkName: string) => { + const contractAddress = addressByContractName[sectionName]; + if (_.isUndefined(contractAddress)) { + return null; + } + const linkIfExists = utils.getEtherScanLinkIfExists( + contractAddress, + constants.NETWORK_ID_BY_NAME[networkName], + EtherscanLinkSuffixes.Address, + ); + return ( + <a + key={`badge-${networkName}-${sectionName}`} + href={linkIfExists} + target="_blank" + style={{ color: colors.white, textDecoration: 'none' }} + > + <Badge title={networkName} backgroundColor={networkNameToColor[networkName]} /> + </a> + ); + }, + ); + return badges; + } + private _renderConstructors( + constructors: SolidityMethod[] | TypescriptMethod[], + sectionName: string, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); + }); + return <div>{constructorDefs}</div>; + } + private _renderProperty(sectionName: string, property: Property): React.ReactNode { + return ( + <div key={`property-${property.name}-${property.type.name}`} className="pb3"> + <code className="hljs"> + {property.name}: + <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> + </code> + {property.source && ( + <SourceLink + version={this.props.docsVersion} + source={property.source} + baseUrl={this.props.docsInfo.packageUrl} + subPackageName={this.props.docsInfo.subPackageName} + /> + )} + {property.comment && <Comment comment={property.comment} className="py2" />} + </div> + ); + } + private _renderMethodBlocks( + method: SolidityMethod | TypescriptMethod, + sectionName: string, + isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + return ( + <MethodBlock + key={`method-${method.name}-${sectionName}`} + sectionName={sectionName} + method={method} + typeDefinitionByName={typeDefinitionByName} + libraryVersion={this.props.docsVersion} + docsInfo={this.props.docsInfo} + /> + ); + } + private _scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = SCROLL_TOP_ID; // scroll to the top + } - scroller.scrollTo(hash, { - duration: 0, - offset: 0, - containerId: 'documentation', - }); - } - private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { - const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot); - const versions = _.keys(versionToFileName); - this.props.dispatcher.updateAvailableDocVersions(versions); - const sortedVersions = semverSort.desc(versions); - const latestVersion = sortedVersions[0]; + scroller.scrollTo(hash, { + duration: 0, + offset: 0, + containerId: 'documentation', + }); + } + private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { + const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; - let versionToFetch = latestVersion; - if (!_.isUndefined(preferredVersionIfExists)) { - const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; - if (!_.isUndefined(preferredVersionFileNameIfExists)) { - versionToFetch = preferredVersionIfExists; - } - } - this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); - const versionFileNameToFetch = versionToFileName[versionToFetch]; - const versionDocObj = await docUtils.getJSONDocFileAsync( - versionFileNameToFetch, - this.props.docsInfo.docsJsonRoot, - ); - const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync( + versionFileNameToFetch, + this.props.docsInfo.docsJsonRoot, + ); + const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); - this.setState( - { - docAgnosticFormat, - }, - () => { - this._scrollToHash(); - }, - ); - } + this.setState( + { + docAgnosticFormat, + }, + () => { + this._scrollToHash(); + }, + ); + } } diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx index cfcc7f8d7..7dfdee771 100644 --- a/packages/website/ts/pages/documentation/enum.tsx +++ b/packages/website/ts/pages/documentation/enum.tsx @@ -3,20 +3,20 @@ import * as React from 'react'; import { EnumValue } from 'ts/types'; interface EnumProps { - values: EnumValue[]; + values: EnumValue[]; } export function Enum(props: EnumProps) { - const values = _.map(props.values, (value, i) => { - const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; - return `\n\t${value.name}${defaultValueIfAny},`; - }); - return ( - <span> - {`{`} - {values} - <br /> - {`}`} - </span> - ); + const values = _.map(props.values, (value, i) => { + const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; + return `\n\t${value.name}${defaultValueIfAny},`; + }); + return ( + <span> + {`{`} + {values} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx index 9274ae512..0e53e38e7 100644 --- a/packages/website/ts/pages/documentation/event_definition.tsx +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -7,76 +7,76 @@ import { Event, EventArg, HeaderSizes } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EventDefinitionProps { - event: Event; - sectionName: string; - docsInfo: DocsInfo; + event: Event; + sectionName: string; + docsInfo: DocsInfo; } interface EventDefinitionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> { - constructor(props: EventDefinitionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const event = this.props.event; - return ( - <div - id={`${this.props.sectionName}-${event.name}`} - className="pb2" - style={{ overflow: 'hidden', width: '100%' }} - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <AnchorTitle - headerSize={HeaderSizes.H3} - title={`Event ${event.name}`} - id={event.name} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - <div style={{ fontSize: 16 }}> - <pre> - <code className="hljs">{this._renderEventCode()}</code> - </pre> - </div> - </div> - ); - } - private _renderEventCode() { - const indexed = <span style={{ color: colors.green }}> indexed</span>; - const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { - const type = ( - <Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> - ); - return ( - <span key={`eventArg-${eventArg.name}`}> - {eventArg.name} - {eventArg.isIndexed ? indexed : ''}: {type}, - </span> - ); - }); - const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '\n\t', curr]; - }); - return ( - <span> - {`{`} - <br /> - {'\t'} - {argList} - <br /> - {`}`} - </span> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + constructor(props: EventDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const event = this.props.event; + return ( + <div + id={`${this.props.sectionName}-${event.name}`} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`Event ${event.name}`} + id={event.name} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className="hljs">{this._renderEventCode()}</code> + </pre> + </div> + </div> + ); + } + private _renderEventCode() { + const indexed = <span style={{ color: colors.green }}> indexed</span>; + const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { + const type = ( + <Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + return ( + <span key={`eventArg-${eventArg.name}`}> + {eventArg.name} + {eventArg.isIndexed ? indexed : ''}: {type}, + </span> + ); + }); + const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {argList} + <br /> + {`}`} + </span> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx index ee07a2c50..16a772125 100644 --- a/packages/website/ts/pages/documentation/interface.tsx +++ b/packages/website/ts/pages/documentation/interface.tsx @@ -6,56 +6,56 @@ import { Type } from 'ts/pages/documentation/type'; import { CustomType, TypeDocTypes } from 'ts/types'; interface InterfaceProps { - type: CustomType; - sectionName: string; - docsInfo: DocsInfo; + type: CustomType; + sectionName: string; + docsInfo: DocsInfo; } export function Interface(props: InterfaceProps) { - const type = props.type; - const properties = _.map(type.children, property => { - return ( - <span key={`property-${property.name}-${property.type}-${type.name}`}> - {property.name}:{' '} - {property.type.typeDocType !== TypeDocTypes.Reflection ? ( - <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> - ) : ( - <MethodSignature - method={property.type.method} - sectionName={props.sectionName} - shouldHideMethodName={true} - shouldUseArrowSyntax={true} - docsInfo={props.docsInfo} - /> - )}, - </span> - ); - }); - const hasIndexSignature = !_.isUndefined(type.indexSignature); - if (hasIndexSignature) { - const is = type.indexSignature; - const param = ( - <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}> - {is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} /> - </span> - ); - properties.push( - <span key={`indexSignature-${type.name}-${is.keyType.name}`}> - [{param}]: {is.valueName}, - </span>, - ); - } - const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '\n\t', curr]; - }); - return ( - <span> - {`{`} - <br /> - {'\t'} - {propertyList} - <br /> - {`}`} - </span> - ); + const type = props.type; + const properties = _.map(type.children, property => { + return ( + <span key={`property-${property.name}-${property.type}-${type.name}`}> + {property.name}:{' '} + {property.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + ) : ( + <MethodSignature + method={property.type.method} + sectionName={props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={props.docsInfo} + /> + )}, + </span> + ); + }); + const hasIndexSignature = !_.isUndefined(type.indexSignature); + if (hasIndexSignature) { + const is = type.indexSignature; + const param = ( + <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}> + {is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + </span> + ); + properties.push( + <span key={`indexSignature-${type.name}-${is.keyType.name}`}> + [{param}]: {is.valueName}, + </span>, + ); + } + const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {propertyList} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index fb03cf5be..dfde5931b 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -10,133 +10,133 @@ import { colors } from 'ts/utils/colors'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; interface MethodBlockProps { - method: SolidityMethod | TypescriptMethod; - sectionName: string; - libraryVersion: string; - typeDefinitionByName: TypeDefinitionByName; - docsInfo: DocsInfo; + method: SolidityMethod | TypescriptMethod; + sectionName: string; + libraryVersion: string; + typeDefinitionByName: TypeDefinitionByName; + docsInfo: DocsInfo; } interface MethodBlockState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } const styles: Styles = { - chip: { - fontSize: 13, - backgroundColor: colors.lightBlueA700, - color: colors.white, - height: 11, - borderRadius: 14, - marginTop: 19, - lineHeight: 0.8, - }, + chip: { + fontSize: 13, + backgroundColor: colors.lightBlueA700, + color: colors.white, + height: 11, + borderRadius: 14, + marginTop: 19, + lineHeight: 0.8, + }, }; export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> { - constructor(props: MethodBlockProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const method = this.props.method; - if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { - return null; - } + constructor(props: MethodBlockProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const method = this.props.method; + if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { + return null; + } - return ( - <div - id={`${this.props.sectionName}-${method.name}`} - style={{ overflow: 'hidden', width: '100%' }} - className="pb4" - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - {!method.isConstructor && ( - <div className="flex"> - {(method as TypescriptMethod).isStatic && this._renderChip('Static')} - {(method as SolidityMethod).isConstant && this._renderChip('Constant')} - {(method as SolidityMethod).isPayable && this._renderChip('Payable')} - <AnchorTitle - headerSize={HeaderSizes.H3} - title={method.name} - id={`${this.props.sectionName}-${method.name}`} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </div> - )} - <code className="hljs"> - <MethodSignature - method={method} - sectionName={this.props.sectionName} - typeDefinitionByName={this.props.typeDefinitionByName} - docsInfo={this.props.docsInfo} - /> - </code> - {(method as TypescriptMethod).source && ( - <SourceLink - version={this.props.libraryVersion} - source={(method as TypescriptMethod).source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} - /> - )} - {method.comment && <Comment comment={method.comment} className="py2" />} - {method.parameters && - !_.isEmpty(method.parameters) && ( - <div> - <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> - ARGUMENTS - </h4> - {this._renderParameterDescriptions(method.parameters)} - </div> - )} - {method.returnComment && ( - <div className="pt1 comment"> - <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> - RETURNS - </h4> - <Comment comment={method.returnComment} /> - </div> - )} - </div> - ); - } - private _renderChip(text: string) { - return ( - <div className="p1 mr1" style={styles.chip}> - {text} - </div> - ); - } - private _renderParameterDescriptions(parameters: Parameter[]) { - const descriptions = _.map(parameters, parameter => { - const isOptional = parameter.isOptional; - return ( - <div - key={`param-description-${parameter.name}`} - className="flex pb1 mb2" - style={{ borderBottom: '1px solid #f0f4f7' }} - > - <div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12"> - <div className="bold">{parameter.name}</div> - <div className="pt1" style={{ color: colors.grey, fontSize: 14 }}> - {isOptional && 'optional'} - </div> - </div> - <div className="col lg-col-8 md-col-8 sm-col-12 col-12"> - {parameter.comment && <Comment comment={parameter.comment} />} - </div> - </div> - ); - }); - return descriptions; - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + return ( + <div + id={`${this.props.sectionName}-${method.name}`} + style={{ overflow: 'hidden', width: '100%' }} + className="pb4" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + {!method.isConstructor && ( + <div className="flex"> + {(method as TypescriptMethod).isStatic && this._renderChip('Static')} + {(method as SolidityMethod).isConstant && this._renderChip('Constant')} + {(method as SolidityMethod).isPayable && this._renderChip('Payable')} + <AnchorTitle + headerSize={HeaderSizes.H3} + title={method.name} + id={`${this.props.sectionName}-${method.name}`} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </div> + )} + <code className="hljs"> + <MethodSignature + method={method} + sectionName={this.props.sectionName} + typeDefinitionByName={this.props.typeDefinitionByName} + docsInfo={this.props.docsInfo} + /> + </code> + {(method as TypescriptMethod).source && ( + <SourceLink + version={this.props.libraryVersion} + source={(method as TypescriptMethod).source} + baseUrl={this.props.docsInfo.packageUrl} + subPackageName={this.props.docsInfo.subPackageName} + /> + )} + {method.comment && <Comment comment={method.comment} className="py2" />} + {method.parameters && + !_.isEmpty(method.parameters) && ( + <div> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + ARGUMENTS + </h4> + {this._renderParameterDescriptions(method.parameters)} + </div> + )} + {method.returnComment && ( + <div className="pt1 comment"> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + RETURNS + </h4> + <Comment comment={method.returnComment} /> + </div> + )} + </div> + ); + } + private _renderChip(text: string) { + return ( + <div className="p1 mr1" style={styles.chip}> + {text} + </div> + ); + } + private _renderParameterDescriptions(parameters: Parameter[]) { + const descriptions = _.map(parameters, parameter => { + const isOptional = parameter.isOptional; + return ( + <div + key={`param-description-${parameter.name}`} + className="flex pb1 mb2" + style={{ borderBottom: '1px solid #f0f4f7' }} + > + <div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12"> + <div className="bold">{parameter.name}</div> + <div className="pt1" style={{ color: colors.grey, fontSize: 14 }}> + {isOptional && 'optional'} + </div> + </div> + <div className="col lg-col-8 md-col-8 sm-col-12 col-12"> + {parameter.comment && <Comment comment={parameter.comment} />} + </div> + </div> + ); + }); + return descriptions; + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx index 7c6bf96d2..041dcd093 100644 --- a/packages/website/ts/pages/documentation/method_signature.tsx +++ b/packages/website/ts/pages/documentation/method_signature.tsx @@ -6,94 +6,94 @@ import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } fro import { constants } from 'ts/utils/constants'; interface MethodSignatureProps { - method: TypescriptMethod | SolidityMethod; - sectionName: string; - shouldHideMethodName?: boolean; - shouldUseArrowSyntax?: boolean; - typeDefinitionByName?: TypeDefinitionByName; - docsInfo: DocsInfo; + method: TypescriptMethod | SolidityMethod; + sectionName: string; + shouldHideMethodName?: boolean; + shouldUseArrowSyntax?: boolean; + typeDefinitionByName?: TypeDefinitionByName; + docsInfo: DocsInfo; } const defaultProps = { - shouldHideMethodName: false, - shouldUseArrowSyntax: false, + shouldHideMethodName: false, + shouldUseArrowSyntax: false, }; export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => { - const sectionName = constants.TYPES_SECTION_NAME; - const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); - const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, ', ', curr]; - }); - const methodName = props.shouldHideMethodName ? '' : props.method.name; - const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) - ? undefined - : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); - return ( - <span> - {props.method.callPath} - {methodName} - {typeParameterIfExists}({paramString}) - {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} - {props.method.returnType && ( - <Type - type={props.method.returnType} - sectionName={sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - )} - </span> - ); + const sectionName = constants.TYPES_SECTION_NAME; + const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + const methodName = props.shouldHideMethodName ? '' : props.method.name; + const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) + ? undefined + : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + return ( + <span> + {props.method.callPath} + {methodName} + {typeParameterIfExists}({paramString}) + {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} + {props.method.returnType && ( + <Type + type={props.method.returnType} + sectionName={sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + )} + </span> + ); }; MethodSignature.defaultProps = defaultProps; function renderParameters( - method: TypescriptMethod | SolidityMethod, - docsInfo: DocsInfo, - sectionName: string, - typeDefinitionByName?: TypeDefinitionByName, + method: TypescriptMethod | SolidityMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, ) { - const parameters = method.parameters; - const params = _.map(parameters, (p: Parameter) => { - const isOptional = p.isOptional; - const type = ( - <Type - type={p.type} - sectionName={sectionName} - typeDefinitionByName={typeDefinitionByName} - docsInfo={docsInfo} - /> - ); - return ( - <span key={`param-${p.type}-${p.name}`}> - {p.name} - {isOptional && '?'}: {type} - </span> - ); - }); - return params; + const parameters = method.parameters; + const params = _.map(parameters, (p: Parameter) => { + const isOptional = p.isOptional; + const type = ( + <Type + type={p.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + ); + return ( + <span key={`param-${p.type}-${p.name}`}> + {p.name} + {isOptional && '?'}: {type} + </span> + ); + }); + return params; } function renderTypeParameter( - method: TypescriptMethod, - docsInfo: DocsInfo, - sectionName: string, - typeDefinitionByName?: TypeDefinitionByName, + method: TypescriptMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, ) { - const typeParameter = method.typeParameter; - const typeParam = ( - <span> - {`<${typeParameter.name} extends `} - <Type - type={typeParameter.type} - sectionName={sectionName} - typeDefinitionByName={typeDefinitionByName} - docsInfo={docsInfo} - /> - {`>`} - </span> - ); - return typeParam; + const typeParameter = method.typeParameter; + const typeParam = ( + <span> + {`<${typeParameter.name} extends `} + <Type + type={typeParameter.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + {`>`} + </span> + ); + return typeParam; } diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 32126b7da..6588ee39e 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -4,28 +4,28 @@ import { Source } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface SourceLinkProps { - source: Source; - baseUrl: string; - version: string; - subPackageName: string; + source: Source; + baseUrl: string; + version: string; + subPackageName: string; } const packagesWithNamespace = ['connect']; export function SourceLink(props: SourceLinkProps) { - const src = props.source; - const url = props.baseUrl; - const pkg = props.subPackageName; - let tagPrefix = pkg; - if (_.includes(packagesWithNamespace, pkg)) { - tagPrefix = `@0xproject/${pkg}`; - } - const sourceCodeUrl = `${url}/blob/${tagPrefix}%40${props.version}/packages/${pkg}/${src.fileName}#L${src.line}`; - return ( - <div className="pt2" style={{ fontSize: 14 }}> - <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> - Source - </a> - </div> - ); + const src = props.source; + const url = props.baseUrl; + const pkg = props.subPackageName; + let tagPrefix = pkg; + if (_.includes(packagesWithNamespace, pkg)) { + tagPrefix = `@0xproject/${pkg}`; + } + const sourceCodeUrl = `${url}/blob/${tagPrefix}%40${props.version}/packages/${pkg}/${src.fileName}#L${src.line}`; + return ( + <div className="pt2" style={{ fontSize: 14 }}> + <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> + Source + </a> + </div> + ); } diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx index 9a2696e22..e989e7129 100644 --- a/packages/website/ts/pages/documentation/type.tsx +++ b/packages/website/ts/pages/documentation/type.tsx @@ -11,202 +11,202 @@ import { utils } from 'ts/utils/utils'; // Some types reference other libraries. For these types, we want to link the user to the relevant documentation. const typeToUrl: { [typeName: string]: string } = { - Web3: constants.URL_WEB3_DOCS, - Provider: constants.URL_WEB3_PROVIDER_DOCS, - BigNumber: constants.URL_BIGNUMBERJS_GITHUB, - DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT, - LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT, + Web3: constants.URL_WEB3_DOCS, + Provider: constants.URL_WEB3_PROVIDER_DOCS, + BigNumber: constants.URL_BIGNUMBERJS_GITHUB, + DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT, + LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT, }; const typePrefix: { [typeName: string]: string } = { - Provider: 'Web3', - DecodedLogEntryEvent: 'Web3', - LogEntryEvent: 'Web3', + Provider: 'Web3', + DecodedLogEntryEvent: 'Web3', + LogEntryEvent: 'Web3', }; const typeToSection: { [typeName: string]: string } = { - ExchangeWrapper: 'exchange', - TokenWrapper: 'token', - TokenRegistryWrapper: 'tokenRegistry', - EtherTokenWrapper: 'etherToken', - ProxyWrapper: 'proxy', - TokenTransferProxyWrapper: 'proxy', - OrderStateWatcher: 'orderWatcher', + ExchangeWrapper: 'exchange', + TokenWrapper: 'token', + TokenRegistryWrapper: 'tokenRegistry', + EtherTokenWrapper: 'etherToken', + ProxyWrapper: 'proxy', + TokenTransferProxyWrapper: 'proxy', + OrderStateWatcher: 'orderWatcher', }; interface TypeProps { - type: TypeDef; - docsInfo: DocsInfo; - sectionName: string; - typeDefinitionByName?: TypeDefinitionByName; + type: TypeDef; + docsInfo: DocsInfo; + sectionName: string; + typeDefinitionByName?: TypeDefinitionByName; } // The return type needs to be `any` here so that we can recursively define <Type /> components within // <Type /> components (e.g when rendering the union type). export function Type(props: TypeProps): any { - const type = props.type; - const isReference = type.typeDocType === TypeDocTypes.Reference; - const isArray = type.typeDocType === TypeDocTypes.Array; - let typeNameColor = 'inherit'; - let typeName: string | React.ReactNode; - let typeArgs: React.ReactNode[] = []; - switch (type.typeDocType) { - case TypeDocTypes.Intrinsic: - case TypeDocTypes.Unknown: - typeName = type.name; - typeNameColor = colors.orange; - break; + const type = props.type; + const isReference = type.typeDocType === TypeDocTypes.Reference; + const isArray = type.typeDocType === TypeDocTypes.Array; + let typeNameColor = 'inherit'; + let typeName: string | React.ReactNode; + let typeArgs: React.ReactNode[] = []; + switch (type.typeDocType) { + case TypeDocTypes.Intrinsic: + case TypeDocTypes.Unknown: + typeName = type.name; + typeNameColor = colors.orange; + break; - case TypeDocTypes.Reference: - typeName = type.name; - typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { - if (arg.typeDocType === TypeDocTypes.Array) { - const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; - return ( - <span> - <Type - key={key} - type={arg.elementType} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - />[] - </span> - ); - } else { - const subType = ( - <Type - key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`} - type={arg} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - ); - return subType; - } - }); - break; + case TypeDocTypes.Reference: + typeName = type.name; + typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { + if (arg.typeDocType === TypeDocTypes.Array) { + const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; + return ( + <span> + <Type + key={key} + type={arg.elementType} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + />[] + </span> + ); + } else { + const subType = ( + <Type + key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`} + type={arg} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + return subType; + } + }); + break; - case TypeDocTypes.StringLiteral: - typeName = `'${type.value}'`; - typeNameColor = colors.green; - break; + case TypeDocTypes.StringLiteral: + typeName = `'${type.value}'`; + typeNameColor = colors.green; + break; - case TypeDocTypes.Array: - typeName = type.elementType.name; - break; + case TypeDocTypes.Array: + typeName = type.elementType.name; + break; - case TypeDocTypes.Union: - const unionTypes = _.map(type.types, t => { - return ( - <Type - key={`type-${t.name}-${t.value}-${t.typeDocType}`} - type={t} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - ); - }); - typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '|', curr]; - }); - break; + case TypeDocTypes.Union: + const unionTypes = _.map(type.types, t => { + return ( + <Type + key={`type-${t.name}-${t.value}-${t.typeDocType}`} + type={t} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + }); + typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '|', curr]; + }); + break; - case TypeDocTypes.TypeParameter: - typeName = type.name; - break; + case TypeDocTypes.TypeParameter: + typeName = type.name; + break; - default: - throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); - } - // HACK: Normalize BigNumber to simply BigNumber. For some reason the type - // name is unpredictably one or the other. - if (typeName === 'BigNumber') { - typeName = 'BigNumber'; - } - const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, ', ', curr]; - }); + default: + throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); + } + // HACK: Normalize BigNumber to simply BigNumber. For some reason the type + // name is unpredictably one or the other. + if (typeName === 'BigNumber') { + typeName = 'BigNumber'; + } + const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); - const typeNameUrlIfExists = typeToUrl[typeName as string]; - const typePrefixIfExists = typePrefix[typeName as string]; - const sectionNameIfExists = typeToSection[typeName as string]; - if (!_.isUndefined(typeNameUrlIfExists)) { - typeName = ( - <a - href={typeNameUrlIfExists} - target="_blank" - className="text-decoration-none" - style={{ color: colors.lightBlueA700 }} - > - {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''} - {typeName} - </a> - ); - } else if ( - (isReference || isArray) && - (props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists)) - ) { - const id = Math.random().toString(); - const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) - ? `${props.sectionName}-${typeName}` - : sectionNameIfExists; - let typeDefinition; - if (props.typeDefinitionByName) { - typeDefinition = props.typeDefinitionByName[typeName as string]; - } - typeName = ( - <ScrollLink - to={typeDefinitionAnchorId} - offset={0} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( - <span - onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} - style={{ color: colors.lightBlueA700, cursor: 'pointer' }} - > - {typeName} - </span> - ) : ( - <span - data-tip={true} - data-for={id} - onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} - style={{ - color: colors.lightBlueA700, - cursor: 'pointer', - display: 'inline-block', - }} - > - {typeName} - <ReactTooltip type="light" effect="solid" id={id} className="typeTooltip"> - <TypeDefinition - sectionName={props.sectionName} - customType={typeDefinition} - shouldAddId={false} - docsInfo={props.docsInfo} - /> - </ReactTooltip> - </span> - )} - </ScrollLink> - ); - } - return ( - <span> - <span style={{ color: typeNameColor }}>{typeName}</span> - {isArray && '[]'} - {!_.isEmpty(typeArgs) && ( - <span> - {'<'} - {commaSeparatedTypeArgs} - {'>'} - </span> - )} - </span> - ); + const typeNameUrlIfExists = typeToUrl[typeName as string]; + const typePrefixIfExists = typePrefix[typeName as string]; + const sectionNameIfExists = typeToSection[typeName as string]; + if (!_.isUndefined(typeNameUrlIfExists)) { + typeName = ( + <a + href={typeNameUrlIfExists} + target="_blank" + className="text-decoration-none" + style={{ color: colors.lightBlueA700 }} + > + {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''} + {typeName} + </a> + ); + } else if ( + (isReference || isArray) && + (props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists)) + ) { + const id = Math.random().toString(); + const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) + ? `${props.sectionName}-${typeName}` + : sectionNameIfExists; + let typeDefinition; + if (props.typeDefinitionByName) { + typeDefinition = props.typeDefinitionByName[typeName as string]; + } + typeName = ( + <ScrollLink + to={typeDefinitionAnchorId} + offset={0} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( + <span + onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ color: colors.lightBlueA700, cursor: 'pointer' }} + > + {typeName} + </span> + ) : ( + <span + data-tip={true} + data-for={id} + onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ + color: colors.lightBlueA700, + cursor: 'pointer', + display: 'inline-block', + }} + > + {typeName} + <ReactTooltip type="light" effect="solid" id={id} className="typeTooltip"> + <TypeDefinition + sectionName={props.sectionName} + customType={typeDefinition} + shouldAddId={false} + docsInfo={props.docsInfo} + /> + </ReactTooltip> + </span> + )} + </ScrollLink> + ); + } + return ( + <span> + <span style={{ color: typeNameColor }}>{typeName}</span> + {isArray && '[]'} + {!_.isEmpty(typeArgs) && ( + <span> + {'<'} + {commaSeparatedTypeArgs} + {'>'} + </span> + )} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx index 356926157..d46eec76c 100644 --- a/packages/website/ts/pages/documentation/type_definition.tsx +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -13,113 +13,113 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TypeDefinitionProps { - sectionName: string; - customType: CustomType; - shouldAddId?: boolean; - docsInfo: DocsInfo; + sectionName: string; + customType: CustomType; + shouldAddId?: boolean; + docsInfo: DocsInfo; } interface TypeDefinitionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> { - public static defaultProps: Partial<TypeDefinitionProps> = { - shouldAddId: true, - }; - constructor(props: TypeDefinitionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const customType = this.props.customType; - if (!this.props.docsInfo.isPublicType(customType.name)) { - return null; // no-op - } + public static defaultProps: Partial<TypeDefinitionProps> = { + shouldAddId: true, + }; + constructor(props: TypeDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const customType = this.props.customType; + if (!this.props.docsInfo.isPublicType(customType.name)) { + return null; // no-op + } - let typePrefix: string; - let codeSnippet: React.ReactNode; - switch (customType.kindString) { - case KindString.Interface: - typePrefix = 'Interface'; - codeSnippet = ( - <Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> - ); - break; + let typePrefix: string; + let codeSnippet: React.ReactNode; + switch (customType.kindString) { + case KindString.Interface: + typePrefix = 'Interface'; + codeSnippet = ( + <Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + break; - case KindString.Variable: - typePrefix = 'Enum'; - codeSnippet = <CustomEnum type={customType} />; - break; + case KindString.Variable: + typePrefix = 'Enum'; + codeSnippet = <CustomEnum type={customType} />; + break; - case KindString.Enumeration: - typePrefix = 'Enum'; - const enumValues = _.map(customType.children, (c: CustomTypeChild) => { - return { - name: c.name, - defaultValue: c.defaultValue, - }; - }); - codeSnippet = <Enum values={enumValues} />; - break; + case KindString.Enumeration: + typePrefix = 'Enum'; + const enumValues = _.map(customType.children, (c: CustomTypeChild) => { + return { + name: c.name, + defaultValue: c.defaultValue, + }; + }); + codeSnippet = <Enum values={enumValues} />; + break; - case KindString.TypeAlias: - typePrefix = 'Type Alias'; - codeSnippet = ( - <span> - <span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '} - {customType.type.typeDocType !== TypeDocTypes.Reflection ? ( - <Type - type={customType.type} - sectionName={this.props.sectionName} - docsInfo={this.props.docsInfo} - /> - ) : ( - <MethodSignature - method={customType.type.method} - sectionName={this.props.sectionName} - shouldHideMethodName={true} - shouldUseArrowSyntax={true} - docsInfo={this.props.docsInfo} - /> - )} - </span> - ); - break; + case KindString.TypeAlias: + typePrefix = 'Type Alias'; + codeSnippet = ( + <span> + <span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '} + {customType.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type + type={customType.type} + sectionName={this.props.sectionName} + docsInfo={this.props.docsInfo} + /> + ) : ( + <MethodSignature + method={customType.type.method} + sectionName={this.props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={this.props.docsInfo} + /> + )} + </span> + ); + break; - default: - throw utils.spawnSwitchErr('type.kindString', customType.kindString); - } + default: + throw utils.spawnSwitchErr('type.kindString', customType.kindString); + } - const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`; - return ( - <div - id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} - className="pb2" - style={{ overflow: 'hidden', width: '100%' }} - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <AnchorTitle - headerSize={HeaderSizes.H3} - title={`${typePrefix} ${customType.name}`} - id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - <div style={{ fontSize: 16 }}> - <pre> - <code className="hljs">{codeSnippet}</code> - </pre> - </div> - {customType.comment && <Comment comment={customType.comment} className="py2" />} - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`; + return ( + <div + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`${typePrefix} ${customType.name}`} + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className="hljs">{codeSnippet}</code> + </pre> + </div> + {customType.comment && <Comment comment={customType.comment} className="py2" />} + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index f437f5a8d..b4b5214a2 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -10,438 +10,438 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; export interface FAQProps { - source: string; - location: Location; + source: string; + location: Location; } interface FAQState {} const styles: Styles = { - thin: { - fontWeight: 100, - }, + thin: { + fontWeight: 100, + }, }; const sections: FAQSection[] = [ - { - name: '0x Protocol', - questions: [ - { - prompt: 'What is 0x?', - answer: ( - <div> - At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, low - friction exchange of Ethereum-based assets. Developers can use 0x as a platform to build - exchange applications on top of (<a - href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} - target="blank" - > - 0x.js - </a>{' '} - is a Javascript library for interacting with the 0x protocol). For end users, 0x will be the - infrastructure of a wide variety of user-facing applications i.e.{' '} - <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank"> - 0x Portal - </a>, a decentralized application that facilitates trustless trading of Ethereum-based tokens - between known counterparties. - </div> - ), - }, - { - prompt: 'What problem does 0x solve?', - answer: ( - <div> - In the two years since the Ethereum blockchain’s genesis block, numerous decentralized - applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. Rapid - iteration and a lack of best practices have left the blockchain scattered with proprietary and - application-specific implementations. As a result, end users are exposed to numerous smart - contracts of varying quality and security, with unique configuration processes and learning - curves, all of which implement the same functionality. This approach imposes unnecessary costs - on the network by fragmenting end users according to the particular dApp each user happens to be - using, eliminating valuable network effects around liquidity. 0x is the solution to this problem - by acting as modular, unopinionated building blocks that may be assembled and reconfigured. - </div> - ), - }, - { - prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', - answer: ( - <div> - <ul> - <li>0x is a protocol for exchange, not a user-facing exchange application.</li> - <li> - 0x is decentralized and trustless; there is no central party which can be hacked, run - away with customer funds or be subjected to government regulations. Hacks of Mt. Gox, - Shapeshift and Bitfinex have demonstrated that these types of systemic risks are - palpable. - </li> - <li> - Rather than a proprietary system that exists to extract rent for its owners, 0x is - public infrastructure that is funded by a globally distributed community of - stakeholders. While the protocol is free to use, it enables for-profit user-facing - exchange applications to be built on top of the protocol. - </li> - </ul> - </div> - ), - }, - { - prompt: 'If 0x protocol is free to use, where do transaction fees come in?', - answer: ( - <div> - 0x protocol uses off-chain order books to massively reduce friction costs for market makers and - ensure that the blockchain is only used for trade settlement. Hosting and maintaining an - off-chain order book is a service; to incent “Relayers” to provide this service they must be - able to charge transaction fees on trading activity. Relayers are free to set their transaction - fees to any value they desire. We expect Relayers to be highly competitive and transaction fees - to approach an efficient economic equilibrium over time. - </div> - ), - }, - { - prompt: 'What are the differences between 0x protocol and state channels?', - answer: ( - <div> - <div> - Participants in a state channel pass cryptographically signed messages back and forth, - accumulating intermediate state changes without publishing them to the canonical chain until - the channel is closed. State channels are ideal for “bar tab” applications where numerous - intermediate state changes may be accumulated off-chain before being settled by a final - on-chain transaction (i.e. day trading, poker, turn-based games). - </div> - <ul> - <li> - While state channels drastically reduce the number of on-chain transactions for specific - use cases, numerous on-chain transactions and a security deposit are required to open - and safely close a state channel making them less efficient than 0x for executing - one-time trades. - </li> - <li> - State channels are isolated from the Ethereum blockchain meaning that they cannot - interact with smart contracts. 0x is designed to be integrated directly into smart - contracts so trades can be executed programmatically in a single line of Solidity code. - </li> - </ul> - </div> - ), - }, - { - prompt: 'What types of digital assets are supported by 0x?', - answer: ( - <div> - 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. There are many - ERC20 tokens, worth a combined $2.2B, and more tokens are created each month. We believe that, - by 2020, thousands of assets will be tokenized and moved onto the Ethereum blockchain including - traditional securities such as equities, bonds and derivatives, fiat currencies and scarce - digital goods such as video game items. In the future, cross-blockchain solutions such as{' '} - <a href="https://cosmos.network/" target="_blank"> - Cosmos - </a>{' '} - and{' '} - <a href="http://polkadot.io/" target="_blank"> - Polkadot - </a>{' '} - will allow cryptocurrencies to freely move between blockchains and, naturally, currencies such - as Bitcoin will end up being represented as ERC20 tokens on the Ethereum blockchain. - </div> - ), - }, - { - prompt: '0x is open source: what prevents someone from forking the protocol?', - answer: ( - <div> - Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, but the - resulting clone networks have seen little adoption (as measured by transaction count or market - cap). This is because users have little to no incentive to switch over to a clone network if the - original has initial network effects and a talented developer team behind it. An exception is in - the case that a protocol includes a controversial feature such as a method of rent extraction or - a monetary policy that favors one group of users over another (Zcash developer subsidy - for - better or worse - resulted in Zclassic). Perceived inequality can provide a strong enough - incentive that users will fork the original protocol’s codebase and spin up a new network that - eliminates the controversial feature. In the case of 0x, there is no rent extraction and no - users are given special permissions. 0x protocol is upgradable. Cutting-edge technical - capabilities can be integrated into 0x via decentralized governance (see section below), - eliminating incentives to fork off of the original protocol and sacrifice the network effects - surrounding liquidity that result from the shared protocol and settlement layer. - </div> - ), - }, - ], - }, - { - name: '0x Token (ZRX)', - questions: [ - { - prompt: 'Explain how the 0x protocol token (zrx) works.', - answer: ( - <div> - <div> - 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} - <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank"> - coordination problem - </a>{' '} - and drive network effects around liquidity, creating a feedback loop where early adopters of - the protocol benefit from wider adoption and 2) to be used for decentralized governance over - 0x protocol's update mechanism. In more detail: - </div> - <ul> - <li> - ZRX tokens are used by Makers and Takers (market participants that generate and consume - orders, respectively) to pay transaction fees to Relayers (entities that host and - maintain public order books). - </li> - <li> - ZRX tokens are used for decentralized governance over 0x protocol’s update mechanism - which allows its underlying smart contracts to be replaced and improved over time. An - update mechanism is needed because 0x is built upon Ethereum’s rapidly evolving - technology stack, decentralized governance is needed because 0x protocol’s smart - contracts will have access to user funds and numerous dApps will need to plug into 0x - smart contracts. Decentralized governance ensures that this update process is secure and - minimizes disruption to the network. - </li> - </ul> - </div> - ), - }, - { - prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', - answer: ( - <div> - 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. To maximize the - alignment of stakeholder and end user incentives, the staked asset must provide utility within - the protocol. - </div> - ), - }, - { - prompt: 'How will decentralized governance work?', - answer: ( - <div> - Decentralized governance is an ongoing focus of research; it will involve token voting with ZRX. - Ultimately the solution will maximize security while also maximizing the protocol’s ability to - absorb new innovations. Until the governance structure is formalized and encoded within 0x DAO, - a multi-sig will be used as a placeholder. - </div> - ), - }, - ], - }, - { - name: 'ZRX Token Launch and Fund Use', - questions: [ - { - prompt: 'What is the total supply of ZRX tokens?', - answer: <div>1,000,000,000 ZRX. Fixed supply.</div>, - }, - { - prompt: 'When is the Token Launch? will there be a pre-sale?', - answer: <div>The token launch will be on August 15th, 2017. There will not be a pre-sale.</div>, - }, - { - prompt: 'What will the token launch proceeds be used for?', - answer: ( - <div> - 100% of the proceeds raised in the token launch will be used to fund the development of free and - open source software, tools and infrastructure that support the protocol and surrounding - ecosystem. Check out our{' '} - <a - href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY" - target="_blank" - > - development roadmap - </a>. - </div> - ), - }, - { - prompt: 'What will be the initial distribution of ZRX tokens?', - answer: ( - <div> - <div className="center" style={{ width: '100%' }}> - <img style={{ width: 350 }} src="/images/zrx_pie_chart.png" /> - </div> - <div className="py1"> - <div className="bold pb1">Token Launch (50%)</div> - <div> - ZRX is inherently a governance token that plays a critical role in the process of - upgrading 0x protocol. We are fully committed to formulating a functional and - theoretically sound governance model and we plan to dedicate significant resources to - R&D. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Retained by 0x (15%)</div> - <div> - The 0x core development team will be able to sustain itself for approximately five years - using funds raised through the token launch. If 0x protocol proves to be as foundational - a technology as we believe it to be, the retained ZRX tokens will allow the 0x core - development team to sustain operations beyond the first 5 years. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Developer Fund (15%)</div> - <div> - The Developer Fund will be used to make targeted capital injections into high potential - projects and teams that are attempting to grow the 0x ecosystem, strategic partnerships, - hackathon prizes and community development activities. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Founding Team (10%)</div> - <div> - The founding team’s allocation of ZRX will vest over a traditional 4 year vesting - schedule with a one year cliff. We believe this should be standard practice for any team - that is committed to making their project a long term success. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Early Backers & Advisors (10%)</div> - <div> - Our backers and advisors have provided capital, resources and guidance that have allowed - us to fill out our team, setup a robust legal entity and build a fully functional - product before launching a token. As a result, we have a proven track record and can - offer a token that holds genuine utility. - </div> - </div> - </div> - ), - }, - { - prompt: 'Can I mine ZRX tokens?', - answer: ( - <div> - No, the total supply of ZRX tokens is fixed and there is no continuous issuance model. Users - that facilitate trading over 0x protocol by operating a Relayer earn transaction fees - denominated in ZRX; as more trading activity is generated, more transaction fees are earned. - </div> - ), - }, - { - prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', - answer: <div>No, ZRX tokens sold in the token launch will immediately be liquid.</div>, - }, - { - prompt: 'Will there be a lockup period for tokens allocated to the founding team?', - answer: ( - <div> - Yes. ZRX tokens allocated to founders, advisors and staff members will be released over a 4 year - vesting schedule with a 25% cliff upon completion of the initial token launch and 25% released - each subsequent year in monthly installments. Staff members hired after the token launch will - have a 4 year vesting schedule with a one year cliff. - </div> - ), - }, - { - prompt: 'Which cryptocurrencies will be accepted in the token launch?', - answer: <div>ETH.</div>, - }, - { - prompt: 'When will 0x be live?', - answer: ( - <div> - An alpha version of 0x has been live on our private test network since January 2017. Version 1.0 - of 0x protocol will be deployed to the canonical Ethereum blockchain after a round of security - audits and prior to the public token launch. 0x will be using the 0x protocol during our token - launch. - </div> - ), - }, - { - prompt: 'Where can I find a development roadmap?', - answer: ( - <div> - Check it out{' '} - <a - href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo" - target="_blank" - > - here - </a>. - </div> - ), - }, - ], - }, - { - name: 'Team', - questions: [ - { - prompt: 'Where is 0x based?', - answer: <div>0x was founded in SF and is driven by a diverse group of contributors.</div>, - }, - { - prompt: 'How can I get involved?', - answer: ( - <div> - Join our{' '} - <a href={constants.URL_ZEROEX_CHAT} target="_blank"> - Rocket.chat - </a>! As an open source project, 0x will rely on a worldwide community of passionate developers - to contribute proposals, ideas and code. - </div> - ), - }, - { - prompt: 'Why the name 0x?', - answer: ( - <div> - 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. In a more - abstract context, as the first open protocol for exchange 0x represents the beginning of the end - for the exchange industry’s rent seeking oligopoly: zero exchange. - </div> - ), - }, - { - prompt: 'How do you pronounce 0x?', - answer: <div>We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.</div>, - }, - ], - }, + { + name: '0x Protocol', + questions: [ + { + prompt: 'What is 0x?', + answer: ( + <div> + At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, low + friction exchange of Ethereum-based assets. Developers can use 0x as a platform to build + exchange applications on top of (<a + href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} + target="blank" + > + 0x.js + </a>{' '} + is a Javascript library for interacting with the 0x protocol). For end users, 0x will be the + infrastructure of a wide variety of user-facing applications i.e.{' '} + <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank"> + 0x Portal + </a>, a decentralized application that facilitates trustless trading of Ethereum-based tokens + between known counterparties. + </div> + ), + }, + { + prompt: 'What problem does 0x solve?', + answer: ( + <div> + In the two years since the Ethereum blockchain’s genesis block, numerous decentralized + applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. Rapid + iteration and a lack of best practices have left the blockchain scattered with proprietary and + application-specific implementations. As a result, end users are exposed to numerous smart + contracts of varying quality and security, with unique configuration processes and learning + curves, all of which implement the same functionality. This approach imposes unnecessary costs + on the network by fragmenting end users according to the particular dApp each user happens to be + using, eliminating valuable network effects around liquidity. 0x is the solution to this problem + by acting as modular, unopinionated building blocks that may be assembled and reconfigured. + </div> + ), + }, + { + prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', + answer: ( + <div> + <ul> + <li>0x is a protocol for exchange, not a user-facing exchange application.</li> + <li> + 0x is decentralized and trustless; there is no central party which can be hacked, run + away with customer funds or be subjected to government regulations. Hacks of Mt. Gox, + Shapeshift and Bitfinex have demonstrated that these types of systemic risks are + palpable. + </li> + <li> + Rather than a proprietary system that exists to extract rent for its owners, 0x is + public infrastructure that is funded by a globally distributed community of + stakeholders. While the protocol is free to use, it enables for-profit user-facing + exchange applications to be built on top of the protocol. + </li> + </ul> + </div> + ), + }, + { + prompt: 'If 0x protocol is free to use, where do transaction fees come in?', + answer: ( + <div> + 0x protocol uses off-chain order books to massively reduce friction costs for market makers and + ensure that the blockchain is only used for trade settlement. Hosting and maintaining an + off-chain order book is a service; to incent “Relayers” to provide this service they must be + able to charge transaction fees on trading activity. Relayers are free to set their transaction + fees to any value they desire. We expect Relayers to be highly competitive and transaction fees + to approach an efficient economic equilibrium over time. + </div> + ), + }, + { + prompt: 'What are the differences between 0x protocol and state channels?', + answer: ( + <div> + <div> + Participants in a state channel pass cryptographically signed messages back and forth, + accumulating intermediate state changes without publishing them to the canonical chain until + the channel is closed. State channels are ideal for “bar tab” applications where numerous + intermediate state changes may be accumulated off-chain before being settled by a final + on-chain transaction (i.e. day trading, poker, turn-based games). + </div> + <ul> + <li> + While state channels drastically reduce the number of on-chain transactions for specific + use cases, numerous on-chain transactions and a security deposit are required to open + and safely close a state channel making them less efficient than 0x for executing + one-time trades. + </li> + <li> + State channels are isolated from the Ethereum blockchain meaning that they cannot + interact with smart contracts. 0x is designed to be integrated directly into smart + contracts so trades can be executed programmatically in a single line of Solidity code. + </li> + </ul> + </div> + ), + }, + { + prompt: 'What types of digital assets are supported by 0x?', + answer: ( + <div> + 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. There are many + ERC20 tokens, worth a combined $2.2B, and more tokens are created each month. We believe that, + by 2020, thousands of assets will be tokenized and moved onto the Ethereum blockchain including + traditional securities such as equities, bonds and derivatives, fiat currencies and scarce + digital goods such as video game items. In the future, cross-blockchain solutions such as{' '} + <a href="https://cosmos.network/" target="_blank"> + Cosmos + </a>{' '} + and{' '} + <a href="http://polkadot.io/" target="_blank"> + Polkadot + </a>{' '} + will allow cryptocurrencies to freely move between blockchains and, naturally, currencies such + as Bitcoin will end up being represented as ERC20 tokens on the Ethereum blockchain. + </div> + ), + }, + { + prompt: '0x is open source: what prevents someone from forking the protocol?', + answer: ( + <div> + Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, but the + resulting clone networks have seen little adoption (as measured by transaction count or market + cap). This is because users have little to no incentive to switch over to a clone network if the + original has initial network effects and a talented developer team behind it. An exception is in + the case that a protocol includes a controversial feature such as a method of rent extraction or + a monetary policy that favors one group of users over another (Zcash developer subsidy - for + better or worse - resulted in Zclassic). Perceived inequality can provide a strong enough + incentive that users will fork the original protocol’s codebase and spin up a new network that + eliminates the controversial feature. In the case of 0x, there is no rent extraction and no + users are given special permissions. 0x protocol is upgradable. Cutting-edge technical + capabilities can be integrated into 0x via decentralized governance (see section below), + eliminating incentives to fork off of the original protocol and sacrifice the network effects + surrounding liquidity that result from the shared protocol and settlement layer. + </div> + ), + }, + ], + }, + { + name: '0x Token (ZRX)', + questions: [ + { + prompt: 'Explain how the 0x protocol token (zrx) works.', + answer: ( + <div> + <div> + 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} + <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank"> + coordination problem + </a>{' '} + and drive network effects around liquidity, creating a feedback loop where early adopters of + the protocol benefit from wider adoption and 2) to be used for decentralized governance over + 0x protocol's update mechanism. In more detail: + </div> + <ul> + <li> + ZRX tokens are used by Makers and Takers (market participants that generate and consume + orders, respectively) to pay transaction fees to Relayers (entities that host and + maintain public order books). + </li> + <li> + ZRX tokens are used for decentralized governance over 0x protocol’s update mechanism + which allows its underlying smart contracts to be replaced and improved over time. An + update mechanism is needed because 0x is built upon Ethereum’s rapidly evolving + technology stack, decentralized governance is needed because 0x protocol’s smart + contracts will have access to user funds and numerous dApps will need to plug into 0x + smart contracts. Decentralized governance ensures that this update process is secure and + minimizes disruption to the network. + </li> + </ul> + </div> + ), + }, + { + prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', + answer: ( + <div> + 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. To maximize the + alignment of stakeholder and end user incentives, the staked asset must provide utility within + the protocol. + </div> + ), + }, + { + prompt: 'How will decentralized governance work?', + answer: ( + <div> + Decentralized governance is an ongoing focus of research; it will involve token voting with ZRX. + Ultimately the solution will maximize security while also maximizing the protocol’s ability to + absorb new innovations. Until the governance structure is formalized and encoded within 0x DAO, + a multi-sig will be used as a placeholder. + </div> + ), + }, + ], + }, + { + name: 'ZRX Token Launch and Fund Use', + questions: [ + { + prompt: 'What is the total supply of ZRX tokens?', + answer: <div>1,000,000,000 ZRX. Fixed supply.</div>, + }, + { + prompt: 'When is the Token Launch? will there be a pre-sale?', + answer: <div>The token launch will be on August 15th, 2017. There will not be a pre-sale.</div>, + }, + { + prompt: 'What will the token launch proceeds be used for?', + answer: ( + <div> + 100% of the proceeds raised in the token launch will be used to fund the development of free and + open source software, tools and infrastructure that support the protocol and surrounding + ecosystem. Check out our{' '} + <a + href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY" + target="_blank" + > + development roadmap + </a>. + </div> + ), + }, + { + prompt: 'What will be the initial distribution of ZRX tokens?', + answer: ( + <div> + <div className="center" style={{ width: '100%' }}> + <img style={{ width: 350 }} src="/images/zrx_pie_chart.png" /> + </div> + <div className="py1"> + <div className="bold pb1">Token Launch (50%)</div> + <div> + ZRX is inherently a governance token that plays a critical role in the process of + upgrading 0x protocol. We are fully committed to formulating a functional and + theoretically sound governance model and we plan to dedicate significant resources to + R&D. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Retained by 0x (15%)</div> + <div> + The 0x core development team will be able to sustain itself for approximately five years + using funds raised through the token launch. If 0x protocol proves to be as foundational + a technology as we believe it to be, the retained ZRX tokens will allow the 0x core + development team to sustain operations beyond the first 5 years. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Developer Fund (15%)</div> + <div> + The Developer Fund will be used to make targeted capital injections into high potential + projects and teams that are attempting to grow the 0x ecosystem, strategic partnerships, + hackathon prizes and community development activities. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Founding Team (10%)</div> + <div> + The founding team’s allocation of ZRX will vest over a traditional 4 year vesting + schedule with a one year cliff. We believe this should be standard practice for any team + that is committed to making their project a long term success. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Early Backers & Advisors (10%)</div> + <div> + Our backers and advisors have provided capital, resources and guidance that have allowed + us to fill out our team, setup a robust legal entity and build a fully functional + product before launching a token. As a result, we have a proven track record and can + offer a token that holds genuine utility. + </div> + </div> + </div> + ), + }, + { + prompt: 'Can I mine ZRX tokens?', + answer: ( + <div> + No, the total supply of ZRX tokens is fixed and there is no continuous issuance model. Users + that facilitate trading over 0x protocol by operating a Relayer earn transaction fees + denominated in ZRX; as more trading activity is generated, more transaction fees are earned. + </div> + ), + }, + { + prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', + answer: <div>No, ZRX tokens sold in the token launch will immediately be liquid.</div>, + }, + { + prompt: 'Will there be a lockup period for tokens allocated to the founding team?', + answer: ( + <div> + Yes. ZRX tokens allocated to founders, advisors and staff members will be released over a 4 year + vesting schedule with a 25% cliff upon completion of the initial token launch and 25% released + each subsequent year in monthly installments. Staff members hired after the token launch will + have a 4 year vesting schedule with a one year cliff. + </div> + ), + }, + { + prompt: 'Which cryptocurrencies will be accepted in the token launch?', + answer: <div>ETH.</div>, + }, + { + prompt: 'When will 0x be live?', + answer: ( + <div> + An alpha version of 0x has been live on our private test network since January 2017. Version 1.0 + of 0x protocol will be deployed to the canonical Ethereum blockchain after a round of security + audits and prior to the public token launch. 0x will be using the 0x protocol during our token + launch. + </div> + ), + }, + { + prompt: 'Where can I find a development roadmap?', + answer: ( + <div> + Check it out{' '} + <a + href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo" + target="_blank" + > + here + </a>. + </div> + ), + }, + ], + }, + { + name: 'Team', + questions: [ + { + prompt: 'Where is 0x based?', + answer: <div>0x was founded in SF and is driven by a diverse group of contributors.</div>, + }, + { + prompt: 'How can I get involved?', + answer: ( + <div> + Join our{' '} + <a href={constants.URL_ZEROEX_CHAT} target="_blank"> + Rocket.chat + </a>! As an open source project, 0x will rely on a worldwide community of passionate developers + to contribute proposals, ideas and code. + </div> + ), + }, + { + prompt: 'Why the name 0x?', + answer: ( + <div> + 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. In a more + abstract context, as the first open protocol for exchange 0x represents the beginning of the end + for the exchange industry’s rent seeking oligopoly: zero exchange. + </div> + ), + }, + { + prompt: 'How do you pronounce 0x?', + answer: <div>We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.</div>, + }, + ], + }, ]; export class FAQ extends React.Component<FAQProps, FAQState> { - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div> - <DocumentTitle title="0x FAQ" /> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> - <div id="faq" className="mx-auto max-width-4 pt4" style={{ color: colors.grey800 }}> - <h1 className="center" style={{ ...styles.thin }}> - 0x FAQ - </h1> - <div className="sm-px2 md-px2 lg-px0 pb4">{this._renderSections()}</div> - </div> - <Footer /> - </div> - ); - } - private _renderSections() { - const renderedSections = _.map(sections, (section: FAQSection, i: number) => { - const isFirstSection = i === 0; - return ( - <div key={section.name}> - <h3>{section.name}</h3> - {this._renderQuestions(section.questions, isFirstSection)} - </div> - ); - }); - return renderedSections; - } - private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { - const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { - const isFirstQuestion = i === 0; - return ( - <Question - key={question.prompt} - prompt={question.prompt} - answer={question.answer} - shouldDisplayExpanded={isFirstSection && isFirstQuestion} - /> - ); - }); - return renderedQuestions; - } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div> + <DocumentTitle title="0x FAQ" /> + <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <div id="faq" className="mx-auto max-width-4 pt4" style={{ color: colors.grey800 }}> + <h1 className="center" style={{ ...styles.thin }}> + 0x FAQ + </h1> + <div className="sm-px2 md-px2 lg-px0 pb4">{this._renderSections()}</div> + </div> + <Footer /> + </div> + ); + } + private _renderSections() { + const renderedSections = _.map(sections, (section: FAQSection, i: number) => { + const isFirstSection = i === 0; + return ( + <div key={section.name}> + <h3>{section.name}</h3> + {this._renderQuestions(section.questions, isFirstSection)} + </div> + ); + }); + return renderedSections; + } + private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { + const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { + const isFirstQuestion = i === 0; + return ( + <Question + key={question.prompt} + prompt={question.prompt} + answer={question.answer} + shouldDisplayExpanded={isFirstSection && isFirstQuestion} + /> + ); + }); + return renderedQuestions; + } } diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx index 58cf674ef..988c04bc9 100644 --- a/packages/website/ts/pages/faq/question.tsx +++ b/packages/website/ts/pages/faq/question.tsx @@ -4,48 +4,48 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface QuestionProps { - prompt: string; - answer: React.ReactNode; - shouldDisplayExpanded: boolean; + prompt: string; + answer: React.ReactNode; + shouldDisplayExpanded: boolean; } interface QuestionState { - isExpanded: boolean; + isExpanded: boolean; } export class Question extends React.Component<QuestionProps, QuestionState> { - constructor(props: QuestionProps) { - super(props); - this.state = { - isExpanded: props.shouldDisplayExpanded, - }; - } - public render() { - return ( - <div className="py1"> - <Card - initiallyExpanded={this.props.shouldDisplayExpanded} - onExpandChange={this._onExchangeChange.bind(this)} - > - <CardHeader - title={this.props.prompt} - style={{ - borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none', - }} - titleStyle={{ color: colors.darkerGrey }} - actAsExpander={true} - showExpandableButton={true} - /> - <CardText expandable={true}> - <div style={{ lineHeight: 1.4 }}>{this.props.answer}</div> - </CardText> - </Card> - </div> - ); - } - private _onExchangeChange() { - this.setState({ - isExpanded: !this.state.isExpanded, - }); - } + constructor(props: QuestionProps) { + super(props); + this.state = { + isExpanded: props.shouldDisplayExpanded, + }; + } + public render() { + return ( + <div className="py1"> + <Card + initiallyExpanded={this.props.shouldDisplayExpanded} + onExpandChange={this._onExchangeChange.bind(this)} + > + <CardHeader + title={this.props.prompt} + style={{ + borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none', + }} + titleStyle={{ color: colors.darkerGrey }} + actAsExpander={true} + showExpandableButton={true} + /> + <CardText expandable={true}> + <div style={{ lineHeight: 1.4 }}>{this.props.answer}</div> + </CardText> + </Card> + </div> + ); + } + private _onExchangeChange() { + this.setState({ + isExpanded: !this.state.isExpanded, + }); + } } diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 742d94a0f..ca76497df 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -11,755 +11,755 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface BoxContent { - title: string; - description: string; - imageUrl: string; - classNames: string; + title: string; + description: string; + imageUrl: string; + classNames: string; } interface AssetType { - title: string; - imageUrl: string; - style?: React.CSSProperties; + title: string; + imageUrl: string; + style?: React.CSSProperties; } interface UseCase { - imageUrl: string; - type: string; - description: string; - classNames: string; - style?: React.CSSProperties; - projectIconUrls: string[]; + imageUrl: string; + type: string; + description: string; + classNames: string; + style?: React.CSSProperties; + projectIconUrls: string[]; } interface Project { - logoFileName: string; - projectUrl: string; + logoFileName: string; + projectUrl: string; } const THROTTLE_TIMEOUT = 100; const boxContents: BoxContent[] = [ - { - title: 'Trustless exchange', - description: - "Built on Ethereum's distributed network with no centralized \ + { + title: 'Trustless exchange', + description: + "Built on Ethereum's distributed network with no centralized \ point of failure and no down time, each trade is settled atomically \ and without counterparty risk.", - imageUrl: '/images/landing/distributed_network.png', - classNames: '', - }, - { - title: 'Shared liquidity', - description: - 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ + imageUrl: '/images/landing/distributed_network.png', + classNames: '', + }, + { + title: 'Shared liquidity', + description: + 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ creating network effects around liquidity that compound as more relayers come online.', - imageUrl: '/images/landing/liquidity.png', - classNames: 'mx-auto', - }, - { - title: 'Open source', - description: - '0x is open source, permissionless and free to use. Trade directly with a known \ + imageUrl: '/images/landing/liquidity.png', + classNames: 'mx-auto', + }, + { + title: 'Open source', + description: + '0x is open source, permissionless and free to use. Trade directly with a known \ counterparty for free or pay a relayer some ZRX tokens to access their liquidity \ pool.', - imageUrl: '/images/landing/open_source.png', - classNames: 'right', - }, + imageUrl: '/images/landing/open_source.png', + classNames: 'right', + }, ]; const projects: Project[] = [ - { - logoFileName: 'ethfinex-top.png', - projectUrl: constants.PROJECT_URL_ETHFINEX, - }, - { - logoFileName: 'radar_relay_top.png', - projectUrl: constants.PROJECT_URL_RADAR_RELAY, - }, - { - logoFileName: 'paradex_top.png', - projectUrl: constants.PROJECT_URL_PARADEX, - }, - { - logoFileName: 'the_ocean.png', - projectUrl: constants.PROJECT_URL_0CEAN, - }, - { - logoFileName: 'dydx.png', - projectUrl: constants.PROJECT_URL_DYDX, - }, - { - logoFileName: 'melonport.png', - projectUrl: constants.PROJECT_URL_MELONPORT, - }, - { - logoFileName: 'maker.png', - projectUrl: constants.PROJECT_URL_MAKER, - }, - { - logoFileName: 'dharma.png', - projectUrl: constants.PROJECT_URL_DHARMA, - }, - { - logoFileName: 'lendroid.png', - projectUrl: constants.PROJECT_URL_LENDROID, - }, - { - logoFileName: 'district0x.png', - projectUrl: constants.PROJECT_URL_DISTRICT_0X, - }, - { - logoFileName: 'aragon.png', - projectUrl: constants.PROJECT_URL_ARAGON, - }, - { - logoFileName: 'blocknet.png', - projectUrl: constants.PROJECT_URL_BLOCKNET, - }, - { - logoFileName: 'status.png', - projectUrl: constants.PROJECT_URL_STATUS, - }, - { - logoFileName: 'augur.png', - projectUrl: constants.PROJECT_URL_AUGUR, - }, - { - logoFileName: 'anx.png', - projectUrl: constants.PROJECT_URL_OPEN_ANX, - }, - { - logoFileName: 'auctus.png', - projectUrl: constants.PROJECT_URL_AUCTUS, - }, + { + logoFileName: 'ethfinex-top.png', + projectUrl: constants.PROJECT_URL_ETHFINEX, + }, + { + logoFileName: 'radar_relay_top.png', + projectUrl: constants.PROJECT_URL_RADAR_RELAY, + }, + { + logoFileName: 'paradex_top.png', + projectUrl: constants.PROJECT_URL_PARADEX, + }, + { + logoFileName: 'the_ocean.png', + projectUrl: constants.PROJECT_URL_0CEAN, + }, + { + logoFileName: 'dydx.png', + projectUrl: constants.PROJECT_URL_DYDX, + }, + { + logoFileName: 'melonport.png', + projectUrl: constants.PROJECT_URL_MELONPORT, + }, + { + logoFileName: 'maker.png', + projectUrl: constants.PROJECT_URL_MAKER, + }, + { + logoFileName: 'dharma.png', + projectUrl: constants.PROJECT_URL_DHARMA, + }, + { + logoFileName: 'lendroid.png', + projectUrl: constants.PROJECT_URL_LENDROID, + }, + { + logoFileName: 'district0x.png', + projectUrl: constants.PROJECT_URL_DISTRICT_0X, + }, + { + logoFileName: 'aragon.png', + projectUrl: constants.PROJECT_URL_ARAGON, + }, + { + logoFileName: 'blocknet.png', + projectUrl: constants.PROJECT_URL_BLOCKNET, + }, + { + logoFileName: 'status.png', + projectUrl: constants.PROJECT_URL_STATUS, + }, + { + logoFileName: 'augur.png', + projectUrl: constants.PROJECT_URL_AUGUR, + }, + { + logoFileName: 'anx.png', + projectUrl: constants.PROJECT_URL_OPEN_ANX, + }, + { + logoFileName: 'auctus.png', + projectUrl: constants.PROJECT_URL_AUCTUS, + }, ]; export interface LandingProps { - location: Location; + location: Location; } interface LandingState { - screenWidth: ScreenWidths; + screenWidth: ScreenWidths; } export class Landing extends React.Component<LandingProps, LandingState> { - private _throttledScreenWidthUpdate: () => void; - constructor(props: LandingProps) { - super(props); - this.state = { - screenWidth: utils.getScreenWidth(), - }; - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - } - public componentDidMount() { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillUnmount() { - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - } - public render() { - return ( - <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> - <DocumentTitle title="0x Protocol" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - isNightVersion={true} - style={{ backgroundColor: colors.heroGrey, position: 'relative' }} - /> - {this._renderHero()} - {this._renderProjects()} - {this._renderTokenizationSection()} - {this._renderProtocolSection()} - {this._renderInfoBoxes()} - {this._renderBuildingBlocksSection()} - {this._renderUseCases()} - {this._renderCallToAction()} - <Footer /> - </div> - ); - } - private _renderHero() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const buttonLabelStyle: React.CSSProperties = { - textTransform: 'none', - fontSize: isSmallScreen ? 12 : 14, - fontWeight: 400, - }; - const lightButtonStyle: React.CSSProperties = { - borderRadius: 6, - border: '1px solid #D8D8D8', - lineHeight: '33px', - height: 38, - }; - const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 clearfix"> - <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> - <div className="col lg-col-5 md-col-5 col-12 sm-center"> - <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> - </div> - <div className={left} style={{ color: colors.white }}> - <div style={{ paddingLeft: isSmallScreen ? 0 : 12 }}> - <div - className="sm-pb2" - style={{ - fontFamily: 'Roboto Mono', - fontSize: isSmallScreen ? 26 : 34, - }} - > - Powering decentralized exchange - </div> - <div - className="pt2 h5 sm-mx-auto" - style={{ - maxWidth: 446, - fontFamily: 'Roboto Mono', - lineHeight: 1.7, - fontWeight: 300, - }} - > - 0x is an open, permissionless protocol allowing for ERC20 tokens to be traded on the - Ethereum blockchain. - </div> - <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 342 }}> - <div className="lg-pr2 md-pr2 col col-6 sm-center"> - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 157.36 }} - buttonStyle={{ borderRadius: 6 }} - labelStyle={buttonLabelStyle} - label="Build on 0x" - onClick={_.noop} - /> - </Link> - </div> - <div className="col col-6 sm-center"> - <a - href={constants.URL_ZEROEX_CHAT} - target="_blank" - className="text-decoration-none" - > - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor="white" - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label="Join the community" - onClick={_.noop} - /> - </a> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderProjects() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const isMediumScreen = this.state.screenWidth === ScreenWidths.Md; - const projectList = _.map(projects, (project: Project, i: number) => { - const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - i % 2; - return ( - <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> - <div> - <a href={project.projectUrl} target="_blank" className="text-decoration-none"> - <img - src={`/images/landing/project_logos/${project.logoFileName}`} - height={isSmallScreen ? 60 : 92} - /> - </a> - </div> - </div> - ); - }); - const titleStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - color: colors.grey, - textTransform: 'uppercase', - fontWeight: 300, - letterSpacing: 3, - }; - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.projectsGrey }}> - <div className="mx-auto max-width-4 clearfix sm-px3"> - <div className="h4 pb3 md-pl3 sm-pl2" style={titleStyle}> - Projects building on 0x - </div> - <div className="clearfix">{projectList}</div> - <div - className="pt3 mx-auto center" - style={{ - color: colors.landingLinkGrey, - fontFamily: 'Roboto Mono', - maxWidth: 300, - fontSize: 14, - }} - > - view the{' '} - <Link - to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} - className="text-decoration-none underline" - style={{ color: colors.landingLinkGrey }} - > - full list - </Link> - </div> - </div> - </div> - ); - } - private _renderTokenizationSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> - <div className="mx-auto max-width-4 py4 clearfix"> - {isSmallScreen && this._renderTokenCloud()} - <div className="col lg-col-6 md-col-6 col-12"> - <div className="mx-auto" style={{ maxWidth: 385, paddingTop: 7 }}> - <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> - The world's value is becoming tokenized - </div> - <div - className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" - style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7 }} - > - {isSmallScreen ? ( - <span> - The Ethereum blockchain is an open, borderless financial system that represents - a wide variety of assets as cryptographic tokens. In the future, most digital - assets and goods will be tokenized. - </span> - ) : ( - <div> - <div> - The Ethereum blockchain is an open, borderless financial system that - represents - </div> - <div> - a wide variety of assets as cryptographic tokens. In the future, most - digital assets and goods will be tokenized. - </div> - </div> - )} - </div> - <div className="flex pt1 sm-px3">{this._renderAssetTypes()}</div> - </div> - </div> - {!isSmallScreen && this._renderTokenCloud()} - </div> - </div> - ); - } - private _renderProtocolSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="clearfix lg-py4 md-py4 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix"> - <div className="col lg-col-6 md-col-6 col-12 sm-center"> - <img src="/images/landing/relayer_diagram.png" height={isSmallScreen ? 326 : 426} /> - </div> - <div - className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto" - style={{ - color: colors.beigeWhite, - paddingTop: 8, - maxWidth: isSmallScreen ? 'none' : 445, - }} - > - <div className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" style={{ fontFamily: 'Roboto Mono' }}> - <div>Off-chain order relay</div> - <div>On-chain settlement</div> - </div> - <div - className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" - style={{ - fontFamily: 'Roboto Mono', - lineHeight: 1.7, - fontWeight: 300, - maxWidth: 445, - }} - > - In 0x protocol, orders are transported off-chain, massively reducing gas costs and - eliminating blockchain bloat. Relayers help broadcast orders and collect a fee each time - they facilitate a trade. Anyone can build a relayer. - </div> - <div - className="pt3 sm-mx-auto sm-px3" - style={{ - color: colors.landingLinkGrey, - maxWidth: isSmallScreen ? 412 : 'none', - }} - > - <div className="flex" style={{ fontSize: 18 }}> - <div - className="lg-h4 md-h4 sm-h5" - style={{ - letterSpacing: isSmallScreen ? 1 : 3, - fontFamily: 'Roboto Mono', - }} - > - RELAYERS BUILDING ON 0X - </div> - <div className="h5" style={{ marginLeft: isSmallScreen ? 26 : 49 }}> - <Link - to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} - className="text-decoration-none underline" - style={{ - color: colors.landingLinkGrey, - fontFamily: 'Roboto Mono', - }} - > - view all - </Link> - </div> - </div> - <div className="lg-flex md-flex sm-clearfix pt3" style={{ opacity: 0.4 }}> - <div className="col col-4 sm-center"> - <img - src="/images/landing/ethfinex.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - <div className="col col-4 center"> - <img - src="/images/landing/radar_relay.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - <div className="col col-4 sm-center" style={{ textAlign: 'right' }}> - <img - src="/images/landing/paradex.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderBuildingBlocksSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const descriptionStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - lineHeight: isSmallScreen ? 1.5 : 2, - fontWeight: 300, - fontSize: 15, - maxWidth: isSmallScreen ? 375 : 'none', - }; - const callToActionStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - fontSize: 15, - fontWeight: 300, - maxWidth: isSmallScreen ? 375 : 441, - }; - return ( - <div className="clearfix lg-pt4 md-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix"> - {isSmallScreen && this._renderBlockChipImage()} - <div - className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3" - style={{ color: colors.beigeWhite }} - > - <div - className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center" - style={{ fontFamily: 'Roboto Mono' }} - > - A building block for dApps - </div> - <div className="pb3 pt2 sm-mx-auto sm-center" style={descriptionStyle}> - 0x protocol is a pluggable building block for dApps that require exchange functionality. - Join the many developers that are already using 0x in their web applications and smart - contracts. - </div> - <div className="sm-mx-auto sm-center" style={callToActionStyle}> - Learn how in our{' '} - <Link - to={WebsitePaths.ZeroExJs} - className="text-decoration-none underline" - style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} - > - 0x.js - </Link>{' '} - and{' '} - <Link - to={WebsitePaths.SmartContracts} - className="text-decoration-none underline" - style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} - > - smart contract - </Link>{' '} - docs - </div> - </div> - {!isSmallScreen && this._renderBlockChipImage()} - </div> - </div> - ); - } - private _renderBlockChipImage() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="col lg-col-6 md-col-6 col-12 sm-center"> - <img src="/images/landing/0x_chips.png" height={isSmallScreen ? 240 : 368} /> - </div> - ); - } - private _renderTokenCloud() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="col lg-col-6 md-col-6 col-12 center"> - <img src="/images/landing/tokenized_world.png" height={isSmallScreen ? 280 : 364.5} /> - </div> - ); - } - private _renderAssetTypes() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const assetTypes: AssetType[] = [ - { - title: 'Currency', - imageUrl: '/images/landing/currency.png', - }, - { - title: 'Traditional assets', - imageUrl: '/images/landing/stocks.png', - style: { - paddingLeft: isSmallScreen ? 41 : 56, - paddingRight: isSmallScreen ? 41 : 56, - }, - }, - { - title: 'Digital goods', - imageUrl: '/images/landing/digital_goods.png', - }, - ]; - const assets = _.map(assetTypes, (assetType: AssetType) => { - const style = _.isUndefined(assetType.style) ? {} : assetType.style; - return ( - <div key={`asset-${assetType.title}`} className="center" style={{ opacity: 0.8, ...style }}> - <div> - <img src={assetType.imageUrl} height="80" /> - </div> - <div - style={{ - fontFamily: 'Roboto Mono', - fontSize: 13.5, - fontWeight: 400, - opacity: 0.75, - }} - > - {assetType.title} - </div> - </div> - ); - }); - return assets; - } - private _renderInfoBoxes() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const boxStyle: React.CSSProperties = { - maxWidth: 252, - height: 386, - backgroundColor: colors.grey50, - borderRadius: 5, - padding: '10px 24px 24px', - }; - const boxes = _.map(boxContents, (boxContent: BoxContent) => { - return ( - <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> - <div className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`} style={boxStyle}> - <div> - <img src={boxContent.imageUrl} style={{ height: 210 }} /> - </div> - <div className="h3" style={{ color: 'black', fontFamily: 'Roboto Mono' }}> - {boxContent.title} - </div> - <div className="pt2 pb2" style={{ fontFamily: 'Roboto Mono', fontSize: 14 }}> - {boxContent.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto py4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> - {boxes} - </div> - </div> - ); - } - private _renderUseCases() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + private _throttledScreenWidthUpdate: () => void; + constructor(props: LandingProps) { + super(props); + this.state = { + screenWidth: utils.getScreenWidth(), + }; + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount() { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillUnmount() { + window.removeEventListener('resize', this._throttledScreenWidthUpdate); + } + public render() { + return ( + <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> + <DocumentTitle title="0x Protocol" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + isNightVersion={true} + style={{ backgroundColor: colors.heroGrey, position: 'relative' }} + /> + {this._renderHero()} + {this._renderProjects()} + {this._renderTokenizationSection()} + {this._renderProtocolSection()} + {this._renderInfoBoxes()} + {this._renderBuildingBlocksSection()} + {this._renderUseCases()} + {this._renderCallToAction()} + <Footer /> + </div> + ); + } + private _renderHero() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: isSmallScreen ? 12 : 14, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #D8D8D8', + lineHeight: '33px', + height: 38, + }; + const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; + return ( + <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 clearfix"> + <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> + <div className="col lg-col-5 md-col-5 col-12 sm-center"> + <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> + </div> + <div className={left} style={{ color: colors.white }}> + <div style={{ paddingLeft: isSmallScreen ? 0 : 12 }}> + <div + className="sm-pb2" + style={{ + fontFamily: 'Roboto Mono', + fontSize: isSmallScreen ? 26 : 34, + }} + > + Powering decentralized exchange + </div> + <div + className="pt2 h5 sm-mx-auto" + style={{ + maxWidth: 446, + fontFamily: 'Roboto Mono', + lineHeight: 1.7, + fontWeight: 300, + }} + > + 0x is an open, permissionless protocol allowing for ERC20 tokens to be traded on the + Ethereum blockchain. + </div> + <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 342 }}> + <div className="lg-pr2 md-pr2 col col-6 sm-center"> + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <RaisedButton + style={{ borderRadius: 6, minWidth: 157.36 }} + buttonStyle={{ borderRadius: 6 }} + labelStyle={buttonLabelStyle} + label="Build on 0x" + onClick={_.noop} + /> + </Link> + </div> + <div className="col col-6 sm-center"> + <a + href={constants.URL_ZEROEX_CHAT} + target="_blank" + className="text-decoration-none" + > + <RaisedButton + style={{ borderRadius: 6, minWidth: 150 }} + buttonStyle={lightButtonStyle} + labelColor="white" + backgroundColor={colors.heroGrey} + labelStyle={buttonLabelStyle} + label="Join the community" + onClick={_.noop} + /> + </a> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderProjects() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const isMediumScreen = this.state.screenWidth === ScreenWidths.Md; + const projectList = _.map(projects, (project: Project, i: number) => { + const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - i % 2; + return ( + <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> + <div> + <a href={project.projectUrl} target="_blank" className="text-decoration-none"> + <img + src={`/images/landing/project_logos/${project.logoFileName}`} + height={isSmallScreen ? 60 : 92} + /> + </a> + </div> + </div> + ); + }); + const titleStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + color: colors.grey, + textTransform: 'uppercase', + fontWeight: 300, + letterSpacing: 3, + }; + return ( + <div className="clearfix py4" style={{ backgroundColor: colors.projectsGrey }}> + <div className="mx-auto max-width-4 clearfix sm-px3"> + <div className="h4 pb3 md-pl3 sm-pl2" style={titleStyle}> + Projects building on 0x + </div> + <div className="clearfix">{projectList}</div> + <div + className="pt3 mx-auto center" + style={{ + color: colors.landingLinkGrey, + fontFamily: 'Roboto Mono', + maxWidth: 300, + fontSize: 14, + }} + > + view the{' '} + <Link + to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} + className="text-decoration-none underline" + style={{ color: colors.landingLinkGrey }} + > + full list + </Link> + </div> + </div> + </div> + ); + } + private _renderTokenizationSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> + <div className="mx-auto max-width-4 py4 clearfix"> + {isSmallScreen && this._renderTokenCloud()} + <div className="col lg-col-6 md-col-6 col-12"> + <div className="mx-auto" style={{ maxWidth: 385, paddingTop: 7 }}> + <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + The world's value is becoming tokenized + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" + style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7 }} + > + {isSmallScreen ? ( + <span> + The Ethereum blockchain is an open, borderless financial system that represents + a wide variety of assets as cryptographic tokens. In the future, most digital + assets and goods will be tokenized. + </span> + ) : ( + <div> + <div> + The Ethereum blockchain is an open, borderless financial system that + represents + </div> + <div> + a wide variety of assets as cryptographic tokens. In the future, most + digital assets and goods will be tokenized. + </div> + </div> + )} + </div> + <div className="flex pt1 sm-px3">{this._renderAssetTypes()}</div> + </div> + </div> + {!isSmallScreen && this._renderTokenCloud()} + </div> + </div> + ); + } + private _renderProtocolSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="clearfix lg-py4 md-py4 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix"> + <div className="col lg-col-6 md-col-6 col-12 sm-center"> + <img src="/images/landing/relayer_diagram.png" height={isSmallScreen ? 326 : 426} /> + </div> + <div + className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto" + style={{ + color: colors.beigeWhite, + paddingTop: 8, + maxWidth: isSmallScreen ? 'none' : 445, + }} + > + <div className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" style={{ fontFamily: 'Roboto Mono' }}> + <div>Off-chain order relay</div> + <div>On-chain settlement</div> + </div> + <div + className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" + style={{ + fontFamily: 'Roboto Mono', + lineHeight: 1.7, + fontWeight: 300, + maxWidth: 445, + }} + > + In 0x protocol, orders are transported off-chain, massively reducing gas costs and + eliminating blockchain bloat. Relayers help broadcast orders and collect a fee each time + they facilitate a trade. Anyone can build a relayer. + </div> + <div + className="pt3 sm-mx-auto sm-px3" + style={{ + color: colors.landingLinkGrey, + maxWidth: isSmallScreen ? 412 : 'none', + }} + > + <div className="flex" style={{ fontSize: 18 }}> + <div + className="lg-h4 md-h4 sm-h5" + style={{ + letterSpacing: isSmallScreen ? 1 : 3, + fontFamily: 'Roboto Mono', + }} + > + RELAYERS BUILDING ON 0X + </div> + <div className="h5" style={{ marginLeft: isSmallScreen ? 26 : 49 }}> + <Link + to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} + className="text-decoration-none underline" + style={{ + color: colors.landingLinkGrey, + fontFamily: 'Roboto Mono', + }} + > + view all + </Link> + </div> + </div> + <div className="lg-flex md-flex sm-clearfix pt3" style={{ opacity: 0.4 }}> + <div className="col col-4 sm-center"> + <img + src="/images/landing/ethfinex.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + <div className="col col-4 center"> + <img + src="/images/landing/radar_relay.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + <div className="col col-4 sm-center" style={{ textAlign: 'right' }}> + <img + src="/images/landing/paradex.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderBuildingBlocksSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const descriptionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + lineHeight: isSmallScreen ? 1.5 : 2, + fontWeight: 300, + fontSize: 15, + maxWidth: isSmallScreen ? 375 : 'none', + }; + const callToActionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + fontSize: 15, + fontWeight: 300, + maxWidth: isSmallScreen ? 375 : 441, + }; + return ( + <div className="clearfix lg-pt4 md-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix"> + {isSmallScreen && this._renderBlockChipImage()} + <div + className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3" + style={{ color: colors.beigeWhite }} + > + <div + className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center" + style={{ fontFamily: 'Roboto Mono' }} + > + A building block for dApps + </div> + <div className="pb3 pt2 sm-mx-auto sm-center" style={descriptionStyle}> + 0x protocol is a pluggable building block for dApps that require exchange functionality. + Join the many developers that are already using 0x in their web applications and smart + contracts. + </div> + <div className="sm-mx-auto sm-center" style={callToActionStyle}> + Learn how in our{' '} + <Link + to={WebsitePaths.ZeroExJs} + className="text-decoration-none underline" + style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} + > + 0x.js + </Link>{' '} + and{' '} + <Link + to={WebsitePaths.SmartContracts} + className="text-decoration-none underline" + style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} + > + smart contract + </Link>{' '} + docs + </div> + </div> + {!isSmallScreen && this._renderBlockChipImage()} + </div> + </div> + ); + } + private _renderBlockChipImage() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="col lg-col-6 md-col-6 col-12 sm-center"> + <img src="/images/landing/0x_chips.png" height={isSmallScreen ? 240 : 368} /> + </div> + ); + } + private _renderTokenCloud() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="col lg-col-6 md-col-6 col-12 center"> + <img src="/images/landing/tokenized_world.png" height={isSmallScreen ? 280 : 364.5} /> + </div> + ); + } + private _renderAssetTypes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const assetTypes: AssetType[] = [ + { + title: 'Currency', + imageUrl: '/images/landing/currency.png', + }, + { + title: 'Traditional assets', + imageUrl: '/images/landing/stocks.png', + style: { + paddingLeft: isSmallScreen ? 41 : 56, + paddingRight: isSmallScreen ? 41 : 56, + }, + }, + { + title: 'Digital goods', + imageUrl: '/images/landing/digital_goods.png', + }, + ]; + const assets = _.map(assetTypes, (assetType: AssetType) => { + const style = _.isUndefined(assetType.style) ? {} : assetType.style; + return ( + <div key={`asset-${assetType.title}`} className="center" style={{ opacity: 0.8, ...style }}> + <div> + <img src={assetType.imageUrl} height="80" /> + </div> + <div + style={{ + fontFamily: 'Roboto Mono', + fontSize: 13.5, + fontWeight: 400, + opacity: 0.75, + }} + > + {assetType.title} + </div> + </div> + ); + }); + return assets; + } + private _renderInfoBoxes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const boxStyle: React.CSSProperties = { + maxWidth: 252, + height: 386, + backgroundColor: colors.grey50, + borderRadius: 5, + padding: '10px 24px 24px', + }; + const boxes = _.map(boxContents, (boxContent: BoxContent) => { + return ( + <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> + <div className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`} style={boxStyle}> + <div> + <img src={boxContent.imageUrl} style={{ height: 210 }} /> + </div> + <div className="h3" style={{ color: 'black', fontFamily: 'Roboto Mono' }}> + {boxContent.title} + </div> + <div className="pt2 pb2" style={{ fontFamily: 'Roboto Mono', fontSize: 14 }}> + {boxContent.description} + </div> + </div> + </div> + ); + }); + return ( + <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto py4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> + {boxes} + </div> + </div> + ); + } + private _renderUseCases() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const useCases: UseCase[] = [ - { - imageUrl: '/images/landing/governance_icon.png', - type: 'Decentralized governance', - description: - 'Decentralized organizations use tokens to represent ownership and \ + const useCases: UseCase[] = [ + { + imageUrl: '/images/landing/governance_icon.png', + type: 'Decentralized governance', + description: + 'Decentralized organizations use tokens to represent ownership and \ guide their governance logic. 0x allows decentralized organizations \ to seamlessly and safely trade ownership for startup capital.', - projectIconUrls: ['/images/landing/aragon.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/prediction_market_icon.png', - type: 'Prediction markets', - description: - 'Decentralized prediction market platforms generate sets of tokens that \ + projectIconUrls: ['/images/landing/aragon.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/prediction_market_icon.png', + type: 'Prediction markets', + description: + 'Decentralized prediction market platforms generate sets of tokens that \ represent a financial stake in the outcomes of real-world events. 0x allows \ these tokens to be instantly tradable.', - projectIconUrls: ['/images/landing/augur.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/stable_tokens_icon.png', - type: 'Stable tokens', - description: - 'Novel economic constructs such as stable coins require efficient, liquid \ + projectIconUrls: ['/images/landing/augur.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/stable_tokens_icon.png', + type: 'Stable tokens', + description: + 'Novel economic constructs such as stable coins require efficient, liquid \ markets to succeed. 0x will facilitate the underlying economic mechanisms \ that allow these tokens to remain stable.', - projectIconUrls: ['/images/landing/maker.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/loans_icon.png', - type: 'Decentralized loans', - description: - 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ + projectIconUrls: ['/images/landing/maker.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/loans_icon.png', + type: 'Decentralized loans', + description: + 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ 0x enables an ecosystem of lenders to self-organize and efficiently determine \ market prices for all outstanding loans.', - projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], - classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', - style: { - width: 291, - float: 'right', - marginTop: !isSmallScreen ? 38 : 0, - }, - }, - { - imageUrl: '/images/landing/fund_management_icon.png', - type: 'Fund management', - description: - 'Decentralized fund management limits fund managers to investing in pre-agreed \ + projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], + classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', + style: { + width: 291, + float: 'right', + marginTop: !isSmallScreen ? 38 : 0, + }, + }, + { + imageUrl: '/images/landing/fund_management_icon.png', + type: 'Fund management', + description: + 'Decentralized fund management limits fund managers to investing in pre-agreed \ upon asset classes. Embedding 0x into fund management smart contracts enables \ them to enforce these security constraints.', - projectIconUrls: ['/images/landing/melonport.png'], - classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', - style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, - }, - ]; + projectIconUrls: ['/images/landing/melonport.png'], + classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', + style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, + }, + ]; - const cases = _.map(useCases, (useCase: UseCase) => { - const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; - const useCaseBoxStyle = { - color: colors.grey, - border: '1px solid #565656', - borderRadius: 4, - maxWidth: isSmallScreen ? 375 : 'none', - ...style, - }; - const typeStyle: React.CSSProperties = { - color: colors.lightGrey, - fontSize: 13, - textTransform: 'uppercase', - fontFamily: 'Roboto Mono', - fontWeight: 300, - }; - return ( - <div - key={`useCase-${useCase.type}`} - className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`} - > - <div className="relative p2 pb2 sm-mx-auto" style={useCaseBoxStyle}> - <div className="absolute center" style={{ top: -35, width: 'calc(100% - 32px)' }}> - <img src={useCase.imageUrl} style={{ height: 50 }} /> - </div> - <div className="pt2 center" style={typeStyle}> - {useCase.type} - </div> - <div - className="pt2" - style={{ - lineHeight: 1.5, - fontSize: 14, - overflow: 'hidden', - height: 104, - }} - > - {useCase.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix pb4 lg-pt2 md-pt2 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix" style={{ maxWidth: '67em' }}> - {cases} - </div> - </div> - ); - } - private _renderCallToAction() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const buttonLabelStyle: React.CSSProperties = { - textTransform: 'none', - fontSize: 15, - fontWeight: 400, - }; - const lightButtonStyle: React.CSSProperties = { - borderRadius: 6, - border: '1px solid #a0a0a0', - lineHeight: '33px', - height: 49, - }; - const callToActionClassNames = - 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ + const cases = _.map(useCases, (useCase: UseCase) => { + const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; + const useCaseBoxStyle = { + color: colors.grey, + border: '1px solid #565656', + borderRadius: 4, + maxWidth: isSmallScreen ? 375 : 'none', + ...style, + }; + const typeStyle: React.CSSProperties = { + color: colors.lightGrey, + fontSize: 13, + textTransform: 'uppercase', + fontFamily: 'Roboto Mono', + fontWeight: 300, + }; + return ( + <div + key={`useCase-${useCase.type}`} + className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`} + > + <div className="relative p2 pb2 sm-mx-auto" style={useCaseBoxStyle}> + <div className="absolute center" style={{ top: -35, width: 'calc(100% - 32px)' }}> + <img src={useCase.imageUrl} style={{ height: 50 }} /> + </div> + <div className="pt2 center" style={typeStyle}> + {useCase.type} + </div> + <div + className="pt2" + style={{ + lineHeight: 1.5, + fontSize: 14, + overflow: 'hidden', + height: 104, + }} + > + {useCase.description} + </div> + </div> + </div> + ); + }); + return ( + <div className="clearfix pb4 lg-pt2 md-pt2 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix" style={{ maxWidth: '67em' }}> + {cases} + </div> + </div> + ); + } + private _renderCallToAction() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: 15, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #a0a0a0', + lineHeight: '33px', + height: 49, + }; + const callToActionClassNames = + 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ lg-right-align md-right-align sm-center sm-px3 h4'; - return ( - <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 pb4 mb3 clearfix"> - <div - className={callToActionClassNames} - style={{ - fontFamily: 'Roboto Mono', - color: colors.white, - lineHeight: isSmallScreen ? 1.7 : 3, - }} - > - Get started on building the decentralized future - </div> - <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2"> - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor={colors.white} - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label="Build on 0x" - onClick={_.noop} - /> - </Link> - </div> - </div> - </div> - ); - } - private _updateScreenWidth() { - const newScreenWidth = utils.getScreenWidth(); - if (newScreenWidth !== this.state.screenWidth) { - this.setState({ - screenWidth: newScreenWidth, - }); - } - } + return ( + <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 pb4 mb3 clearfix"> + <div + className={callToActionClassNames} + style={{ + fontFamily: 'Roboto Mono', + color: colors.white, + lineHeight: isSmallScreen ? 1.7 : 3, + }} + > + Get started on building the decentralized future + </div> + <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2"> + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <RaisedButton + style={{ borderRadius: 6, minWidth: 150 }} + buttonStyle={lightButtonStyle} + labelColor={colors.white} + backgroundColor={colors.heroGrey} + labelStyle={buttonLabelStyle} + label="Build on 0x" + onClick={_.noop} + /> + </Link> + </div> + </div> + </div> + ); + } + private _updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + if (newScreenWidth !== this.state.screenWidth) { + this.setState({ + screenWidth: newScreenWidth, + }); + } + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index 9d8d4142d..ff277c377 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -5,38 +5,38 @@ import { TopBar } from 'ts/components/top_bar'; import { Styles } from 'ts/types'; export interface NotFoundProps { - location: Location; + location: Location; } interface NotFoundState {} const styles: Styles = { - thin: { - fontWeight: 100, - }, + thin: { + fontWeight: 100, + }, }; export class NotFound extends React.Component<NotFoundProps, NotFoundState> { - public render() { - return ( - <div> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> - <div className="mx-auto max-width-4 py4"> - <div className="center py4"> - <div className="py4"> - <div className="py4"> - <h1 style={{ ...styles.thin }}>404 Not Found</h1> - <div className="py1"> - <div className="py3"> - Hm... looks like we couldn't find what you are looking for. - </div> - </div> - </div> - </div> - </div> - </div> - <Footer /> - </div> - ); - } + public render() { + return ( + <div> + <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <div className="mx-auto max-width-4 py4"> + <div className="center py4"> + <div className="py4"> + <div className="py4"> + <h1 style={{ ...styles.thin }}>404 Not Found</h1> + <div className="py1"> + <div className="py3"> + Hm... looks like we couldn't find what you are looking for. + </div> + </div> + </div> + </div> + </div> + </div> + <Footer /> + </div> + ); + } } diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx index 118aa0ea5..db5be1f59 100644 --- a/packages/website/ts/pages/shared/anchor_title.tsx +++ b/packages/website/ts/pages/shared/anchor_title.tsx @@ -5,87 +5,87 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const headerSizeToScrollOffset: { [headerSize: string]: number } = { - h2: -20, - h3: 0, + h2: -20, + h3: 0, }; interface AnchorTitleProps { - title: string | React.ReactNode; - id: string; - headerSize: HeaderSizes; - shouldShowAnchor: boolean; + title: string | React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; } interface AnchorTitleState { - isHovering: boolean; + isHovering: boolean; } const styles: Styles = { - anchor: { - fontSize: 20, - transform: 'rotate(45deg)', - cursor: 'pointer', - }, - headers: { - WebkitMarginStart: 0, - WebkitMarginEnd: 0, - fontWeight: 'bold', - display: 'block', - }, - h1: { - fontSize: '1.8em', - WebkitMarginBefore: '0.83em', - WebkitMarginAfter: '0.83em', - }, - h2: { - fontSize: '1.5em', - WebkitMarginBefore: '0.83em', - WebkitMarginAfter: '0.83em', - }, - h3: { - fontSize: '1.17em', - WebkitMarginBefore: '1em', - WebkitMarginAfter: '1em', - }, + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h2: { + fontSize: '1.5em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h3: { + fontSize: '1.17em', + WebkitMarginBefore: '1em', + WebkitMarginAfter: '1em', + }, }; export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> { - constructor(props: AnchorTitleProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - let opacity = 0; - if (this.props.shouldShowAnchor) { - opacity = this.state.isHovering ? 0.6 : 1; - } - return ( - <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}> - <div className="inline-block" style={{ paddingRight: 4 }}> - {this.props.title} - </div> - <ScrollLink - to={this.props.id} - offset={headerSizeToScrollOffset[this.props.headerSize]} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <i - className="zmdi zmdi-link" - onClick={utils.setUrlHash.bind(utils, this.props.id)} - style={{ ...styles.anchor, opacity }} - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - /> - </ScrollLink> - </div> - ); - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + opacity = this.state.isHovering ? 0.6 : 1; + } + return ( + <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}> + <div className="inline-block" style={{ paddingRight: 4 }}> + {this.props.title} + </div> + <ScrollLink + to={this.props.id} + offset={headerSizeToScrollOffset[this.props.headerSize]} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <i + className="zmdi zmdi-link" + onClick={utils.setUrlHash.bind(utils, this.props.id)} + style={{ ...styles.anchor, opacity }} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + /> + </ScrollLink> + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx index a9d95979b..be96fda16 100644 --- a/packages/website/ts/pages/shared/markdown_code_block.tsx +++ b/packages/website/ts/pages/shared/markdown_code_block.tsx @@ -3,23 +3,23 @@ import * as React from 'react'; import * as HighLight from 'react-highlight'; interface MarkdownCodeBlockProps { - literal: string; - language: string; + literal: string; + language: string; } interface MarkdownCodeBlockState {} export class MarkdownCodeBlock extends React.Component<MarkdownCodeBlockProps, MarkdownCodeBlockState> { - // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying - // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed. - public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) { - return nextProps.literal !== this.props.literal || nextProps.language !== this.props.language; - } - public render() { - return ( - <span style={{ fontSize: 16 }}> - <HighLight className={this.props.language || 'javascript'}>{this.props.literal}</HighLight> - </span> - ); - } + // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying + // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed. + public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) { + return nextProps.literal !== this.props.literal || nextProps.language !== this.props.language; + } + public render() { + return ( + <span style={{ fontSize: 16 }}> + <HighLight className={this.props.language || 'javascript'}>{this.props.literal}</HighLight> + </span> + ); + } } diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx index c875ab736..5487dc8cc 100644 --- a/packages/website/ts/pages/shared/markdown_section.tsx +++ b/packages/website/ts/pages/shared/markdown_section.tsx @@ -9,66 +9,66 @@ import { HeaderSizes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface MarkdownSectionProps { - sectionName: string; - markdownContent: string; - headerSize?: HeaderSizes; - githubLink?: string; + sectionName: string; + markdownContent: string; + headerSize?: HeaderSizes; + githubLink?: string; } interface MarkdownSectionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> { - public static defaultProps: Partial<MarkdownSectionProps> = { - headerSize: HeaderSizes.H3, - }; - constructor(props: MarkdownSectionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const sectionName = this.props.sectionName; - const id = utils.getIdFromName(sectionName); - return ( - <div - className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden" - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <ScrollElement name={id}> - <div className="clearfix"> - <div className="col lg-col-8 md-col-8 sm-col-12"> - <span style={{ textTransform: 'capitalize' }}> - <AnchorTitle - headerSize={this.props.headerSize} - title={sectionName} - id={id} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </span> - </div> - <div className="col col-4 sm-hide xs-hide py2 right-align"> - {!_.isUndefined(this.props.githubLink) && ( - <RaisedButton - href={this.props.githubLink} - target="_blank" - label="Edit on Github" - icon={<i className="zmdi zmdi-github" style={{ fontSize: 23 }} />} - /> - )} - </div> - </div> - <ReactMarkdown source={this.props.markdownContent} renderers={{ CodeBlock: MarkdownCodeBlock }} /> - </ScrollElement> - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + public static defaultProps: Partial<MarkdownSectionProps> = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName; + const id = utils.getIdFromName(sectionName); + return ( + <div + className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <div className="clearfix"> + <div className="col lg-col-8 md-col-8 sm-col-12"> + <span style={{ textTransform: 'capitalize' }}> + <AnchorTitle + headerSize={this.props.headerSize} + title={sectionName} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </span> + </div> + <div className="col col-4 sm-hide xs-hide py2 right-align"> + {!_.isUndefined(this.props.githubLink) && ( + <RaisedButton + href={this.props.githubLink} + target="_blank" + label="Edit on Github" + icon={<i className="zmdi zmdi-github" style={{ fontSize: 23 }} />} + /> + )} + </div> + </div> + <ReactMarkdown source={this.props.markdownContent} renderers={{ CodeBlock: MarkdownCodeBlock }} /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx index cd7ea68e6..849c33504 100644 --- a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx +++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx @@ -9,146 +9,146 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface NestedSidebarMenuProps { - topLevelMenu: { [topLevel: string]: string[] }; - menuSubsectionsBySection: MenuSubsectionsBySection; - shouldDisplaySectionHeaders?: boolean; - onMenuItemClick?: () => void; - selectedVersion?: string; - versions?: string[]; - docPath?: string; - isSectionHeaderClickable?: boolean; + topLevelMenu: { [topLevel: string]: string[] }; + menuSubsectionsBySection: MenuSubsectionsBySection; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; + docPath?: string; + isSectionHeaderClickable?: boolean; } interface NestedSidebarMenuState {} const styles: Styles = { - menuItemWithHeaders: { - minHeight: 0, - }, - menuItemWithoutHeaders: { - minHeight: 48, - }, - menuItemInnerDivWithHeaders: { - lineHeight: 2, - }, + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + lineHeight: 2, + }, }; export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> { - public static defaultProps: Partial<NestedSidebarMenuProps> = { - shouldDisplaySectionHeaders: true, - onMenuItemClick: _.noop, - }; - public render() { - const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { - const finalSectionName = sectionName.replace(/-/g, ' '); - if (this.props.shouldDisplaySectionHeaders) { - const id = utils.getIdFromName(sectionName); - return ( - <div key={`section-${sectionName}`} className="py1"> - <ScrollLink - to={id} - offset={-20} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <div style={{ color: colors.grey, cursor: 'pointer' }} className="pb1"> - {finalSectionName.toUpperCase()} - </div> - </ScrollLink> - {this._renderMenuItems(menuItems)} - </div> - ); - } else { - return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>; - } - }); - return ( - <div> - {!_.isUndefined(this.props.versions) && - !_.isUndefined(this.props.selectedVersion) && - !_.isUndefined(this.props.docPath) && ( - <VersionDropDown - selectedVersion={this.props.selectedVersion} - versions={this.props.versions} - docPath={this.props.docPath} - /> - )} - {navigation} - </div> - ); - } - private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { - const menuItemStyles = this.props.shouldDisplaySectionHeaders - ? styles.menuItemWithHeaders - : styles.menuItemWithoutHeaders; - const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; - const menuItems = _.map(menuItemNames, menuItemName => { - const id = utils.getIdFromName(menuItemName); - return ( - <div key={menuItemName}> - <ScrollLink - key={`menuItem-${menuItemName}`} - to={id} - offset={-10} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <MenuItem - onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} - style={menuItemStyles} - innerDivStyle={menuItemInnerDivStyles} - > - <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> - </MenuItem> - </ScrollLink> - {this._renderMenuItemSubsections(menuItemName)} - </div> - ); - }); - return menuItems; - } - private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { - if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { - return null; - } - return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); - } - private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { - return ( - <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}> - {_.map(entityNames, entityName => { - const name = `${menuItemName}-${entityName}`; - const id = utils.getIdFromName(name); - return ( - <li key={`menuItem-${entityName}`}> - <ScrollLink - to={id} - offset={0} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - onTouchTap={this._onMenuItemClick.bind(this, name)} - > - <MenuItem - onTouchTap={this._onMenuItemClick.bind(this, name)} - style={{ minHeight: 35 }} - innerDivStyle={{ - paddingLeft: 36, - fontSize: 14, - lineHeight: '35px', - }} - > - {entityName} - </MenuItem> - </ScrollLink> - </li> - ); - })} - </ul> - ); - } - private _onMenuItemClick(name: string): void { - const id = utils.getIdFromName(name); - utils.setUrlHash(id); - this.props.onMenuItemClick(); - } + public static defaultProps: Partial<NestedSidebarMenuProps> = { + shouldDisplaySectionHeaders: true, + onMenuItemClick: _.noop, + }; + public render() { + const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { + const finalSectionName = sectionName.replace(/-/g, ' '); + if (this.props.shouldDisplaySectionHeaders) { + const id = utils.getIdFromName(sectionName); + return ( + <div key={`section-${sectionName}`} className="py1"> + <ScrollLink + to={id} + offset={-20} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <div style={{ color: colors.grey, cursor: 'pointer' }} className="pb1"> + {finalSectionName.toUpperCase()} + </div> + </ScrollLink> + {this._renderMenuItems(menuItems)} + </div> + ); + } else { + return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>; + } + }); + return ( + <div> + {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && + !_.isUndefined(this.props.docPath) && ( + <VersionDropDown + selectedVersion={this.props.selectedVersion} + versions={this.props.versions} + docPath={this.props.docPath} + /> + )} + {navigation} + </div> + ); + } + private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { + const menuItemStyles = this.props.shouldDisplaySectionHeaders + ? styles.menuItemWithHeaders + : styles.menuItemWithoutHeaders; + const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; + const menuItems = _.map(menuItemNames, menuItemName => { + const id = utils.getIdFromName(menuItemName); + return ( + <div key={menuItemName}> + <ScrollLink + key={`menuItem-${menuItemName}`} + to={id} + offset={-10} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} + style={menuItemStyles} + innerDivStyle={menuItemInnerDivStyles} + > + <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> + </MenuItem> + </ScrollLink> + {this._renderMenuItemSubsections(menuItemName)} + </div> + ); + }); + return menuItems; + } + private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { + if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + return null; + } + return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + } + private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + return ( + <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}> + {_.map(entityNames, entityName => { + const name = `${menuItemName}-${entityName}`; + const id = utils.getIdFromName(name); + return ( + <li key={`menuItem-${entityName}`}> + <ScrollLink + to={id} + offset={0} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + onTouchTap={this._onMenuItemClick.bind(this, name)} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, name)} + style={{ minHeight: 35 }} + innerDivStyle={{ + paddingLeft: 36, + fontSize: 14, + lineHeight: '35px', + }} + > + {entityName} + </MenuItem> + </ScrollLink> + </li> + ); + })} + </ul> + ); + } + private _onMenuItemClick(name: string): void { + const id = utils.getIdFromName(name); + utils.setUrlHash(id); + this.props.onMenuItemClick(); + } } diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx index f9aa1a5e6..a5f5f52cf 100644 --- a/packages/website/ts/pages/shared/section_header.tsx +++ b/packages/website/ts/pages/shared/section_header.tsx @@ -5,46 +5,46 @@ import { HeaderSizes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface SectionHeaderProps { - sectionName: string; - headerSize?: HeaderSizes; + sectionName: string; + headerSize?: HeaderSizes; } interface SectionHeaderState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> { - public static defaultProps: Partial<SectionHeaderProps> = { - headerSize: HeaderSizes.H2, - }; - constructor(props: SectionHeaderProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const sectionName = this.props.sectionName.replace(/-/g, ' '); - const id = utils.getIdFromName(sectionName); - return ( - <div - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <ScrollElement name={id}> - <AnchorTitle - headerSize={this.props.headerSize} - title={<span style={{ textTransform: 'capitalize' }}>{sectionName}</span>} - id={id} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </ScrollElement> - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + public static defaultProps: Partial<SectionHeaderProps> = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(sectionName); + return ( + <div + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <AnchorTitle + headerSize={this.props.headerSize} + title={<span style={{ textTransform: 'capitalize' }}>{sectionName}</span>} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx index a647252ba..b922e1048 100644 --- a/packages/website/ts/pages/shared/version_drop_down.tsx +++ b/packages/website/ts/pages/shared/version_drop_down.tsx @@ -4,34 +4,34 @@ import MenuItem from 'material-ui/MenuItem'; import * as React from 'react'; interface VersionDropDownProps { - selectedVersion: string; - versions: string[]; - docPath: string; + selectedVersion: string; + versions: string[]; + docPath: string; } interface VersionDropDownState {} export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> { - public render() { - return ( - <div className="mx-auto" style={{ width: 120 }}> - <DropDownMenu - maxHeight={300} - value={this.props.selectedVersion} - onChange={this._updateSelectedVersion.bind(this)} - > - {this._renderDropDownItems()} - </DropDownMenu> - </div> - ); - } - private _renderDropDownItems() { - const items = _.map(this.props.versions, version => { - return <MenuItem key={version} value={version} primaryText={`v${version}`} />; - }); - return items; - } - private _updateSelectedVersion(e: any, index: number, value: string) { - window.location.href = `${this.props.docPath}/${value}${window.location.hash}`; - } + public render() { + return ( + <div className="mx-auto" style={{ width: 120 }}> + <DropDownMenu + maxHeight={300} + value={this.props.selectedVersion} + onChange={this._updateSelectedVersion.bind(this)} + > + {this._renderDropDownItems()} + </DropDownMenu> + </div> + ); + } + private _renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return <MenuItem key={version} value={version} primaryText={`v${version}`} />; + }); + return items; + } + private _updateSelectedVersion(e: any, index: number, value: string) { + window.location.href = `${this.props.docPath}/${value}${window.location.hash}`; + } } diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index dba5ed6a0..d065614ba 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -16,186 +16,186 @@ import { utils } from 'ts/utils/utils'; const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; export interface WikiProps { - source: string; - location: Location; + source: string; + location: Location; } interface WikiState { - articlesBySection: ArticlesBySection; + articlesBySection: ArticlesBySection; } const styles: Styles = { - mainContainers: { - position: 'absolute', - top: 1, - left: 0, - bottom: 0, - right: 0, - overflowZ: 'hidden', - overflowY: 'scroll', - minHeight: 'calc(100vh - 1px)', - WebkitOverflowScrolling: 'touch', - }, - menuContainer: { - borderColor: colors.grey300, - maxWidth: 330, - marginLeft: 20, - }, + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 1px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, }; export class Wiki extends React.Component<WikiProps, WikiState> { - private _wikiBackoffTimeoutId: number; - constructor(props: WikiProps) { - super(props); - this.state = { - articlesBySection: undefined, - }; - } - public componentWillMount() { - // tslint:disable-next-line:no-floating-promises - this._fetchArticlesBySectionAsync(); - } - public componentWillUnmount() { - clearTimeout(this._wikiBackoffTimeoutId); - } - public render() { - const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) - ? {} - : this._getMenuSubsectionsBySection(this.state.articlesBySection); - return ( - <div> - <DocumentTitle title="0x Protocol Wiki" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - menuSubsectionsBySection={menuSubsectionsBySection} - shouldFullWidth={true} - /> - {_.isUndefined(this.state.articlesBySection) ? ( - <div className="col col-12" style={styles.mainContainers}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading wiki... - </div> - </div> - </div> - ) : ( - <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> - <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> - <div - className="border-right absolute pt2" - style={{ ...styles.menuContainer, ...styles.mainContainers }} - > - <NestedSidebarMenu - topLevelMenu={menuSubsectionsBySection} - menuSubsectionsBySection={menuSubsectionsBySection} - isSectionHeaderClickable={true} - /> - </div> - </div> - <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> - <div id="documentation" style={styles.mainContainers} className="absolute"> - <div id="0xProtocolWiki" /> - <h1 className="md-pl2 sm-pl3"> - <a href={constants.URL_GITHUB_WIKI} target="_blank"> - 0x Protocol Wiki - </a> - </h1> - <div id="wiki">{this._renderWikiArticles()}</div> - </div> - </div> - </div> - )} - </div> - ); - } - private _renderWikiArticles(): React.ReactNode { - const sectionNames = _.keys(this.state.articlesBySection); - const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); - return sections; - } - private _renderSection(sectionName: string) { - const articles = this.state.articlesBySection[sectionName]; - const renderedArticles = _.map(articles, (article: Article) => { - const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; - return ( - <div key={`markdown-section-${article.title}`}> - <MarkdownSection - sectionName={article.title} - markdownContent={article.content} - headerSize={HeaderSizes.H2} - githubLink={githubLink} - /> - <div className="mb4 mt3 p3 center" style={{ backgroundColor: colors.lightestGrey }}> - See a way to make this article better?{' '} - <a href={githubLink} target="_blank"> - Edit here → - </a> - </div> - </div> - ); - }); - return ( - <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> - <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} /> - {renderedArticles} - </div> - ); - } - private _scrollToHash(): void { - const hashWithPrefix = this.props.location.hash; - let hash = hashWithPrefix.slice(1); - if (_.isEmpty(hash)) { - hash = '0xProtocolWiki'; // scroll to the top - } + private _wikiBackoffTimeoutId: number; + constructor(props: WikiProps) { + super(props); + this.state = { + articlesBySection: undefined, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchArticlesBySectionAsync(); + } + public componentWillUnmount() { + clearTimeout(this._wikiBackoffTimeoutId); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) + ? {} + : this._getMenuSubsectionsBySection(this.state.articlesBySection); + return ( + <div> + <DocumentTitle title="0x Protocol Wiki" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + menuSubsectionsBySection={menuSubsectionsBySection} + shouldFullWidth={true} + /> + {_.isUndefined(this.state.articlesBySection) ? ( + <div className="col col-12" style={styles.mainContainers}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading wiki... + </div> + </div> + </div> + ) : ( + <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> + <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> + <div + className="border-right absolute pt2" + style={{ ...styles.menuContainer, ...styles.mainContainers }} + > + <NestedSidebarMenu + topLevelMenu={menuSubsectionsBySection} + menuSubsectionsBySection={menuSubsectionsBySection} + isSectionHeaderClickable={true} + /> + </div> + </div> + <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> + <div id="documentation" style={styles.mainContainers} className="absolute"> + <div id="0xProtocolWiki" /> + <h1 className="md-pl2 sm-pl3"> + <a href={constants.URL_GITHUB_WIKI} target="_blank"> + 0x Protocol Wiki + </a> + </h1> + <div id="wiki">{this._renderWikiArticles()}</div> + </div> + </div> + </div> + )} + </div> + ); + } + private _renderWikiArticles(): React.ReactNode { + const sectionNames = _.keys(this.state.articlesBySection); + const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); + return sections; + } + private _renderSection(sectionName: string) { + const articles = this.state.articlesBySection[sectionName]; + const renderedArticles = _.map(articles, (article: Article) => { + const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; + return ( + <div key={`markdown-section-${article.title}`}> + <MarkdownSection + sectionName={article.title} + markdownContent={article.content} + headerSize={HeaderSizes.H2} + githubLink={githubLink} + /> + <div className="mb4 mt3 p3 center" style={{ backgroundColor: colors.lightestGrey }}> + See a way to make this article better?{' '} + <a href={githubLink} target="_blank"> + Edit here → + </a> + </div> + </div> + ); + }); + return ( + <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> + <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} /> + {renderedArticles} + </div> + ); + } + private _scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = '0xProtocolWiki'; // scroll to the top + } - scroller.scrollTo(hash, { - duration: 0, - offset: 0, - containerId: 'documentation', - }); - } - private async _fetchArticlesBySectionAsync(): Promise<void> { - const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; - const response = await fetch(endpoint); - if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { - // We need to backoff and try fetching again later - this._wikiBackoffTimeoutId = window.setTimeout(() => { - // tslint:disable-next-line:no-floating-promises - this._fetchArticlesBySectionAsync(); - }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); - return; - } - if (response.status !== 200) { - // TODO: Show the user an error message when the wiki fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); - return; - } - const articlesBySection = await response.json(); - this.setState( - { - articlesBySection, - }, - () => { - this._scrollToHash(); - }, - ); - } - private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { - const sectionNames = _.keys(articlesBySection); - const menuSubsectionsBySection: { [section: string]: string[] } = {}; - for (const sectionName of sectionNames) { - const articles = articlesBySection[sectionName]; - const articleNames = _.map(articles, article => article.title); - menuSubsectionsBySection[sectionName] = articleNames; - } - return menuSubsectionsBySection; - } + scroller.scrollTo(hash, { + duration: 0, + offset: 0, + containerId: 'documentation', + }); + } + private async _fetchArticlesBySectionAsync(): Promise<void> { + const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; + const response = await fetch(endpoint); + if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { + // We need to backoff and try fetching again later + this._wikiBackoffTimeoutId = window.setTimeout(() => { + // tslint:disable-next-line:no-floating-promises + this._fetchArticlesBySectionAsync(); + }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); + return; + } + if (response.status !== 200) { + // TODO: Show the user an error message when the wiki fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); + return; + } + const articlesBySection = await response.json(); + this.setState( + { + articlesBySection, + }, + () => { + this._scrollToHash(); + }, + ); + } + private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { + const sectionNames = _.keys(articlesBySection); + const menuSubsectionsBySection: { [section: string]: string[] } = {}; + for (const sectionName of sectionNames) { + const articles = articlesBySection[sectionName]; + const articleNames = _.map(articles, article => article.title); + menuSubsectionsBySection[sectionName] = articleNames; + } + return menuSubsectionsBySection; + } } diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 19300e242..42989e5e1 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -2,235 +2,235 @@ import { BigNumber } from '@0xproject/utils'; import { Dispatch } from 'redux'; import { State } from 'ts/redux/reducer'; import { - ActionTypes, - AssetToken, - BlockchainErrs, - Order, - ProviderType, - ScreenWidths, - Side, - SignatureData, - Token, - TokenStateByAddress, + ActionTypes, + AssetToken, + BlockchainErrs, + Order, + ProviderType, + ScreenWidths, + Side, + SignatureData, + Token, + TokenStateByAddress, } from 'ts/types'; export class Dispatcher { - private _dispatch: Dispatch<State>; - constructor(dispatch: Dispatch<State>) { - this._dispatch = dispatch; - } - // Portal - public resetState() { - this._dispatch({ - type: ActionTypes.ResetState, - }); - } - public updateNodeVersion(nodeVersion: string) { - this._dispatch({ - data: nodeVersion, - type: ActionTypes.UpdateNodeVersion, - }); - } - public updateScreenWidth(screenWidth: ScreenWidths) { - this._dispatch({ - data: screenWidth, - type: ActionTypes.UpdateScreenWidth, - }); - } - public swapAssetTokenSymbols() { - this._dispatch({ - type: ActionTypes.SwapAssetTokens, - }); - } - public updateOrderSalt(salt: BigNumber) { - this._dispatch({ - data: salt, - type: ActionTypes.UpdateOrderSalt, - }); - } - public updateUserSuppliedOrderCache(order: Order) { - this._dispatch({ - data: order, - type: ActionTypes.UpdateUserSuppliedOrderCache, - }); - } - public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { - this._dispatch({ - data: shouldBeOpen, - type: ActionTypes.UpdateShouldBlockchainErrDialogBeOpen, - }); - } - public updateChosenAssetToken(side: Side, token: AssetToken) { - this._dispatch({ - data: { - side, - token, - }, - type: ActionTypes.UpdateChosenAssetToken, - }); - } - public updateChosenAssetTokenAddress(side: Side, address: string) { - this._dispatch({ - data: { - address, - side, - }, - type: ActionTypes.UpdateChosenAssetTokenAddress, - }); - } - public updateOrderTakerAddress(address: string) { - this._dispatch({ - data: address, - type: ActionTypes.UpdateOrderTakerAddress, - }); - } - public updateUserAddress(address: string) { - this._dispatch({ - data: address, - type: ActionTypes.UpdateUserAddress, - }); - } - public updateOrderExpiry(unixTimestampSec: BigNumber) { - this._dispatch({ - data: unixTimestampSec, - type: ActionTypes.UpdateOrderExpiry, - }); - } - public encounteredBlockchainError(err: BlockchainErrs) { - this._dispatch({ - data: err, - type: ActionTypes.BlockchainErrEncountered, - }); - } - public updateBlockchainIsLoaded(isLoaded: boolean) { - this._dispatch({ - data: isLoaded, - type: ActionTypes.UpdateBlockchainIsLoaded, - }); - } - public addTokenToTokenByAddress(token: Token) { - this._dispatch({ - data: token, - type: ActionTypes.AddTokenToTokenByAddress, - }); - } - public removeTokenToTokenByAddress(token: Token) { - this._dispatch({ - data: token, - type: ActionTypes.RemoveTokenFromTokenByAddress, - }); - } - public clearTokenByAddress() { - this._dispatch({ - type: ActionTypes.ClearTokenByAddress, - }); - } - public updateTokenByAddress(tokens: Token[]) { - this._dispatch({ - data: tokens, - type: ActionTypes.UpdateTokenByAddress, - }); - } - public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { - this._dispatch({ - data: tokenStateByAddress, - type: ActionTypes.UpdateTokenStateByAddress, - }); - } - public removeFromTokenStateByAddress(tokenAddress: string) { - this._dispatch({ - data: tokenAddress, - type: ActionTypes.RemoveFromTokenStateByAddress, - }); - } - public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { - this._dispatch({ - data: { - address, - allowance, - }, - type: ActionTypes.ReplaceTokenAllowanceByAddress, - }); - } - public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { - this._dispatch({ - data: { - address, - balance, - }, - type: ActionTypes.ReplaceTokenBalanceByAddress, - }); - } - public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { - this._dispatch({ - data: { - address, - balanceDelta, - }, - type: ActionTypes.UpdateTokenBalanceByAddress, - }); - } - public updateSignatureData(signatureData: SignatureData) { - this._dispatch({ - data: signatureData, - type: ActionTypes.UpdateOrderSignatureData, - }); - } - public updateUserEtherBalance(balance: BigNumber) { - this._dispatch({ - data: balance, - type: ActionTypes.UpdateUserEtherBalance, - }); - } - public updateNetworkId(networkId: number) { - this._dispatch({ - data: networkId, - type: ActionTypes.UpdateNetworkId, - }); - } - public updateOrderFillAmount(amount: BigNumber) { - this._dispatch({ - data: amount, - type: ActionTypes.UpdateOrderFillAmount, - }); - } + private _dispatch: Dispatch<State>; + constructor(dispatch: Dispatch<State>) { + this._dispatch = dispatch; + } + // Portal + public resetState() { + this._dispatch({ + type: ActionTypes.ResetState, + }); + } + public updateNodeVersion(nodeVersion: string) { + this._dispatch({ + data: nodeVersion, + type: ActionTypes.UpdateNodeVersion, + }); + } + public updateScreenWidth(screenWidth: ScreenWidths) { + this._dispatch({ + data: screenWidth, + type: ActionTypes.UpdateScreenWidth, + }); + } + public swapAssetTokenSymbols() { + this._dispatch({ + type: ActionTypes.SwapAssetTokens, + }); + } + public updateOrderSalt(salt: BigNumber) { + this._dispatch({ + data: salt, + type: ActionTypes.UpdateOrderSalt, + }); + } + public updateUserSuppliedOrderCache(order: Order) { + this._dispatch({ + data: order, + type: ActionTypes.UpdateUserSuppliedOrderCache, + }); + } + public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { + this._dispatch({ + data: shouldBeOpen, + type: ActionTypes.UpdateShouldBlockchainErrDialogBeOpen, + }); + } + public updateChosenAssetToken(side: Side, token: AssetToken) { + this._dispatch({ + data: { + side, + token, + }, + type: ActionTypes.UpdateChosenAssetToken, + }); + } + public updateChosenAssetTokenAddress(side: Side, address: string) { + this._dispatch({ + data: { + address, + side, + }, + type: ActionTypes.UpdateChosenAssetTokenAddress, + }); + } + public updateOrderTakerAddress(address: string) { + this._dispatch({ + data: address, + type: ActionTypes.UpdateOrderTakerAddress, + }); + } + public updateUserAddress(address: string) { + this._dispatch({ + data: address, + type: ActionTypes.UpdateUserAddress, + }); + } + public updateOrderExpiry(unixTimestampSec: BigNumber) { + this._dispatch({ + data: unixTimestampSec, + type: ActionTypes.UpdateOrderExpiry, + }); + } + public encounteredBlockchainError(err: BlockchainErrs) { + this._dispatch({ + data: err, + type: ActionTypes.BlockchainErrEncountered, + }); + } + public updateBlockchainIsLoaded(isLoaded: boolean) { + this._dispatch({ + data: isLoaded, + type: ActionTypes.UpdateBlockchainIsLoaded, + }); + } + public addTokenToTokenByAddress(token: Token) { + this._dispatch({ + data: token, + type: ActionTypes.AddTokenToTokenByAddress, + }); + } + public removeTokenToTokenByAddress(token: Token) { + this._dispatch({ + data: token, + type: ActionTypes.RemoveTokenFromTokenByAddress, + }); + } + public clearTokenByAddress() { + this._dispatch({ + type: ActionTypes.ClearTokenByAddress, + }); + } + public updateTokenByAddress(tokens: Token[]) { + this._dispatch({ + data: tokens, + type: ActionTypes.UpdateTokenByAddress, + }); + } + public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { + this._dispatch({ + data: tokenStateByAddress, + type: ActionTypes.UpdateTokenStateByAddress, + }); + } + public removeFromTokenStateByAddress(tokenAddress: string) { + this._dispatch({ + data: tokenAddress, + type: ActionTypes.RemoveFromTokenStateByAddress, + }); + } + public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { + this._dispatch({ + data: { + address, + allowance, + }, + type: ActionTypes.ReplaceTokenAllowanceByAddress, + }); + } + public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { + this._dispatch({ + data: { + address, + balance, + }, + type: ActionTypes.ReplaceTokenBalanceByAddress, + }); + } + public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { + this._dispatch({ + data: { + address, + balanceDelta, + }, + type: ActionTypes.UpdateTokenBalanceByAddress, + }); + } + public updateSignatureData(signatureData: SignatureData) { + this._dispatch({ + data: signatureData, + type: ActionTypes.UpdateOrderSignatureData, + }); + } + public updateUserEtherBalance(balance: BigNumber) { + this._dispatch({ + data: balance, + type: ActionTypes.UpdateUserEtherBalance, + }); + } + public updateNetworkId(networkId: number) { + this._dispatch({ + data: networkId, + type: ActionTypes.UpdateNetworkId, + }); + } + public updateOrderFillAmount(amount: BigNumber) { + this._dispatch({ + data: amount, + type: ActionTypes.UpdateOrderFillAmount, + }); + } - // Docs - public updateCurrentDocsVersion(version: string) { - this._dispatch({ - data: version, - type: ActionTypes.UpdateLibraryVersion, - }); - } - public updateAvailableDocVersions(versions: string[]) { - this._dispatch({ - data: versions, - type: ActionTypes.UpdateAvailableLibraryVersions, - }); - } + // Docs + public updateCurrentDocsVersion(version: string) { + this._dispatch({ + data: version, + type: ActionTypes.UpdateLibraryVersion, + }); + } + public updateAvailableDocVersions(versions: string[]) { + this._dispatch({ + data: versions, + type: ActionTypes.UpdateAvailableLibraryVersions, + }); + } - // Shared - public showFlashMessage(msg: string | React.ReactNode) { - this._dispatch({ - data: msg, - type: ActionTypes.ShowFlashMessage, - }); - } - public hideFlashMessage() { - this._dispatch({ - type: ActionTypes.HideFlashMessage, - }); - } - public updateProviderType(providerType: ProviderType) { - this._dispatch({ - type: ActionTypes.UpdateProviderType, - data: providerType, - }); - } - public updateInjectedProviderName(injectedProviderName: string) { - this._dispatch({ - type: ActionTypes.UpdateInjectedProviderName, - data: injectedProviderName, - }); - } + // Shared + public showFlashMessage(msg: string | React.ReactNode) { + this._dispatch({ + data: msg, + type: ActionTypes.ShowFlashMessage, + }); + } + public hideFlashMessage() { + this._dispatch({ + type: ActionTypes.HideFlashMessage, + }); + } + public updateProviderType(providerType: ProviderType) { + this._dispatch({ + type: ActionTypes.UpdateProviderType, + data: providerType, + }); + } + public updateInjectedProviderName(injectedProviderName: string) { + this._dispatch({ + type: ActionTypes.UpdateInjectedProviderName, + data: injectedProviderName, + }); + } } diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index cc467eadd..06ac8b670 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -2,18 +2,18 @@ import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import { - Action, - ActionTypes, - BlockchainErrs, - Order, - ProviderType, - ScreenWidths, - Side, - SideToAssetToken, - SignatureData, - TokenByAddress, - TokenState, - TokenStateByAddress, + Action, + ActionTypes, + BlockchainErrs, + Order, + ProviderType, + ScreenWidths, + Side, + SideToAssetToken, + SignatureData, + TokenByAddress, + TokenState, + TokenStateByAddress, } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -23,367 +23,367 @@ import { utils } from 'ts/utils/utils'; const DEFAULT_DOCS_VERSION = '0.0.0'; export interface State { - // Portal - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - networkId: number; - orderExpiryTimestamp: BigNumber; - orderFillAmount: BigNumber; - orderTakerAddress: string; - orderSignatureData: SignatureData; - orderSalt: BigNumber; - nodeVersion: string; - screenWidth: ScreenWidths; - shouldBlockchainErrDialogBeOpen: boolean; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; - // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. - userSuppliedOrderCache: Order; - - // Docs - docsVersion: string; - availableDocVersions: string[]; - - // Shared - flashMessage: string | React.ReactNode; - providerType: ProviderType; - injectedProviderName: string; + // Portal + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + networkId: number; + orderExpiryTimestamp: BigNumber; + orderFillAmount: BigNumber; + orderTakerAddress: string; + orderSignatureData: SignatureData; + orderSalt: BigNumber; + nodeVersion: string; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. + userSuppliedOrderCache: Order; + + // Docs + docsVersion: string; + availableDocVersions: string[]; + + // Shared + flashMessage: string | React.ReactNode; + providerType: ProviderType; + injectedProviderName: string; } const INITIAL_STATE: State = { - // Portal - blockchainErr: BlockchainErrs.NoError, - blockchainIsLoaded: false, - networkId: undefined, - orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), - orderFillAmount: undefined, - orderSignatureData: { - hash: '', - r: '', - s: '', - v: 27, - }, - orderTakerAddress: '', - orderSalt: ZeroEx.generatePseudoRandomSalt(), - nodeVersion: undefined, - screenWidth: utils.getScreenWidth(), - shouldBlockchainErrDialogBeOpen: false, - sideToAssetToken: { - [Side.Deposit]: {}, - [Side.Receive]: {}, - }, - tokenByAddress: {}, - tokenStateByAddress: {}, - userAddress: '', - userEtherBalance: new BigNumber(0), - userSuppliedOrderCache: undefined, - - // Docs - docsVersion: DEFAULT_DOCS_VERSION, - availableDocVersions: [DEFAULT_DOCS_VERSION], - - // Shared - flashMessage: undefined, - providerType: ProviderType.Injected, - injectedProviderName: '', + // Portal + blockchainErr: BlockchainErrs.NoError, + blockchainIsLoaded: false, + networkId: undefined, + orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), + orderFillAmount: undefined, + orderSignatureData: { + hash: '', + r: '', + s: '', + v: 27, + }, + orderTakerAddress: '', + orderSalt: ZeroEx.generatePseudoRandomSalt(), + nodeVersion: undefined, + screenWidth: utils.getScreenWidth(), + shouldBlockchainErrDialogBeOpen: false, + sideToAssetToken: { + [Side.Deposit]: {}, + [Side.Receive]: {}, + }, + tokenByAddress: {}, + tokenStateByAddress: {}, + userAddress: '', + userEtherBalance: new BigNumber(0), + userSuppliedOrderCache: undefined, + + // Docs + docsVersion: DEFAULT_DOCS_VERSION, + availableDocVersions: [DEFAULT_DOCS_VERSION], + + // Shared + flashMessage: undefined, + providerType: ProviderType.Injected, + injectedProviderName: '', }; export function reducer(state: State = INITIAL_STATE, action: Action) { - switch (action.type) { - // Portal - case ActionTypes.ResetState: - return INITIAL_STATE; - - case ActionTypes.UpdateOrderSalt: { - return { - ...state, - orderSalt: action.data, - }; - } - - case ActionTypes.UpdateNodeVersion: { - return { - ...state, - nodeVersion: action.data, - }; - } - - case ActionTypes.UpdateOrderFillAmount: { - return { - ...state, - orderFillAmount: action.data, - }; - } - - case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: { - return { - ...state, - shouldBlockchainErrDialogBeOpen: action.data, - }; - } - - case ActionTypes.UpdateUserEtherBalance: { - return { - ...state, - userEtherBalance: action.data, - }; - } - - case ActionTypes.UpdateUserSuppliedOrderCache: { - return { - ...state, - userSuppliedOrderCache: action.data, - }; - } - - case ActionTypes.ClearTokenByAddress: { - return { - ...state, - tokenByAddress: {}, - }; - } - - case ActionTypes.AddTokenToTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; - newTokenByAddress[action.data.address] = action.data; - return { - ...state, - tokenByAddress: newTokenByAddress, - }; - } - - case ActionTypes.RemoveTokenFromTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; - delete newTokenByAddress[action.data.address]; - return { - ...state, - tokenByAddress: newTokenByAddress, - }; - } - - case ActionTypes.UpdateTokenByAddress: { - const tokenByAddress = state.tokenByAddress; - const tokens = action.data; - _.each(tokens, token => { - const updatedToken = { - ...tokenByAddress[token.address], - ...token, - }; - tokenByAddress[token.address] = updatedToken; - }); - return { - ...state, - tokenByAddress, - }; - } - - case ActionTypes.UpdateTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const updatedTokenStateByAddress = action.data; - _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { - const updatedTokenState = { - ...tokenStateByAddress[address], - ...tokenState, - }; - tokenStateByAddress[address] = updatedTokenState; - }); - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.RemoveFromTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const tokenAddress = action.data; - delete tokenStateByAddress[tokenAddress]; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.ReplaceTokenAllowanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const allowance = action.data.allowance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - allowance, - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.ReplaceTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balance = action.data.balance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance, - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.UpdateTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balanceDelta = action.data.balanceDelta; - const tokenAddress = action.data.address; - const currBalance = tokenStateByAddress[tokenAddress].balance; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance: currBalance.plus(balanceDelta), - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.UpdateOrderSignatureData: { - return { - ...state, - orderSignatureData: action.data, - }; - } - - case ActionTypes.UpdateScreenWidth: { - return { - ...state, - screenWidth: action.data, - }; - } - - case ActionTypes.UpdateBlockchainIsLoaded: { - return { - ...state, - blockchainIsLoaded: action.data, - }; - } - - case ActionTypes.BlockchainErrEncountered: { - return { - ...state, - blockchainErr: action.data, - }; - } - - case ActionTypes.UpdateNetworkId: { - return { - ...state, - networkId: action.data, - }; - } - - case ActionTypes.UpdateChosenAssetToken: { - const newSideToAssetToken = { - ...state.sideToAssetToken, - [action.data.side]: action.data.token, - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.UpdateChosenAssetTokenAddress: { - const newAssetToken = state.sideToAssetToken[action.data.side]; - newAssetToken.address = action.data.address; - const newSideToAssetToken = { - ...state.sideToAssetToken, - [action.data.side]: newAssetToken, - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.SwapAssetTokens: { - const newSideToAssetToken = { - [Side.Deposit]: state.sideToAssetToken[Side.Receive], - [Side.Receive]: state.sideToAssetToken[Side.Deposit], - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.UpdateOrderExpiry: { - return { - ...state, - orderExpiryTimestamp: action.data, - }; - } - - case ActionTypes.UpdateOrderTakerAddress: { - return { - ...state, - orderTakerAddress: action.data, - }; - } - - case ActionTypes.UpdateUserAddress: { - return { - ...state, - userAddress: action.data, - }; - } - - // Docs - case ActionTypes.UpdateLibraryVersion: { - return { - ...state, - docsVersion: action.data, - }; - } - case ActionTypes.UpdateAvailableLibraryVersions: { - return { - ...state, - availableDocVersions: action.data, - }; - } - - // Shared - case ActionTypes.ShowFlashMessage: { - return { - ...state, - flashMessage: action.data, - }; - } - - case ActionTypes.HideFlashMessage: { - return { - ...state, - flashMessage: undefined, - }; - } - - case ActionTypes.UpdateProviderType: { - return { - ...state, - providerType: action.data, - }; - } - - case ActionTypes.UpdateInjectedProviderName: { - return { - ...state, - injectedProviderName: action.data, - }; - } - - default: - return state; - } + switch (action.type) { + // Portal + case ActionTypes.ResetState: + return INITIAL_STATE; + + case ActionTypes.UpdateOrderSalt: { + return { + ...state, + orderSalt: action.data, + }; + } + + case ActionTypes.UpdateNodeVersion: { + return { + ...state, + nodeVersion: action.data, + }; + } + + case ActionTypes.UpdateOrderFillAmount: { + return { + ...state, + orderFillAmount: action.data, + }; + } + + case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: { + return { + ...state, + shouldBlockchainErrDialogBeOpen: action.data, + }; + } + + case ActionTypes.UpdateUserEtherBalance: { + return { + ...state, + userEtherBalance: action.data, + }; + } + + case ActionTypes.UpdateUserSuppliedOrderCache: { + return { + ...state, + userSuppliedOrderCache: action.data, + }; + } + + case ActionTypes.ClearTokenByAddress: { + return { + ...state, + tokenByAddress: {}, + }; + } + + case ActionTypes.AddTokenToTokenByAddress: { + const newTokenByAddress = state.tokenByAddress; + newTokenByAddress[action.data.address] = action.data; + return { + ...state, + tokenByAddress: newTokenByAddress, + }; + } + + case ActionTypes.RemoveTokenFromTokenByAddress: { + const newTokenByAddress = state.tokenByAddress; + delete newTokenByAddress[action.data.address]; + return { + ...state, + tokenByAddress: newTokenByAddress, + }; + } + + case ActionTypes.UpdateTokenByAddress: { + const tokenByAddress = state.tokenByAddress; + const tokens = action.data; + _.each(tokens, token => { + const updatedToken = { + ...tokenByAddress[token.address], + ...token, + }; + tokenByAddress[token.address] = updatedToken; + }); + return { + ...state, + tokenByAddress, + }; + } + + case ActionTypes.UpdateTokenStateByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const updatedTokenStateByAddress = action.data; + _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { + const updatedTokenState = { + ...tokenStateByAddress[address], + ...tokenState, + }; + tokenStateByAddress[address] = updatedTokenState; + }); + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.RemoveFromTokenStateByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const tokenAddress = action.data; + delete tokenStateByAddress[tokenAddress]; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.ReplaceTokenAllowanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const allowance = action.data.allowance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + allowance, + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.ReplaceTokenBalanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const balance = action.data.balance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + balance, + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.UpdateTokenBalanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const balanceDelta = action.data.balanceDelta; + const tokenAddress = action.data.address; + const currBalance = tokenStateByAddress[tokenAddress].balance; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + balance: currBalance.plus(balanceDelta), + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.UpdateOrderSignatureData: { + return { + ...state, + orderSignatureData: action.data, + }; + } + + case ActionTypes.UpdateScreenWidth: { + return { + ...state, + screenWidth: action.data, + }; + } + + case ActionTypes.UpdateBlockchainIsLoaded: { + return { + ...state, + blockchainIsLoaded: action.data, + }; + } + + case ActionTypes.BlockchainErrEncountered: { + return { + ...state, + blockchainErr: action.data, + }; + } + + case ActionTypes.UpdateNetworkId: { + return { + ...state, + networkId: action.data, + }; + } + + case ActionTypes.UpdateChosenAssetToken: { + const newSideToAssetToken = { + ...state.sideToAssetToken, + [action.data.side]: action.data.token, + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.UpdateChosenAssetTokenAddress: { + const newAssetToken = state.sideToAssetToken[action.data.side]; + newAssetToken.address = action.data.address; + const newSideToAssetToken = { + ...state.sideToAssetToken, + [action.data.side]: newAssetToken, + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.SwapAssetTokens: { + const newSideToAssetToken = { + [Side.Deposit]: state.sideToAssetToken[Side.Receive], + [Side.Receive]: state.sideToAssetToken[Side.Deposit], + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.UpdateOrderExpiry: { + return { + ...state, + orderExpiryTimestamp: action.data, + }; + } + + case ActionTypes.UpdateOrderTakerAddress: { + return { + ...state, + orderTakerAddress: action.data, + }; + } + + case ActionTypes.UpdateUserAddress: { + return { + ...state, + userAddress: action.data, + }; + } + + // Docs + case ActionTypes.UpdateLibraryVersion: { + return { + ...state, + docsVersion: action.data, + }; + } + case ActionTypes.UpdateAvailableLibraryVersions: { + return { + ...state, + availableDocVersions: action.data, + }; + } + + // Shared + case ActionTypes.ShowFlashMessage: { + return { + ...state, + flashMessage: action.data, + }; + } + + case ActionTypes.HideFlashMessage: { + return { + ...state, + flashMessage: undefined, + }; + } + + case ActionTypes.UpdateProviderType: { + return { + ...state, + providerType: action.data, + }; + } + + case ActionTypes.UpdateInjectedProviderName: { + return { + ...state, + injectedProviderName: action.data, + }; + } + + default: + return state; + } } diff --git a/packages/website/ts/schemas/order_schema.ts b/packages/website/ts/schemas/order_schema.ts index fd0bf113a..bfbf9eb8b 100644 --- a/packages/website/ts/schemas/order_schema.ts +++ b/packages/website/ts/schemas/order_schema.ts @@ -1,15 +1,15 @@ export const orderSchema = { - id: '/Order', - properties: { - maker: { $ref: '/OrderTaker' }, - taker: { $ref: '/OrderTaker' }, - salt: { type: 'string' }, - signature: { $ref: '/SignatureData' }, - expiration: { type: 'string' }, - feeRecipient: { type: 'string' }, - exchangeContract: { type: 'string' }, - networkId: { type: 'number' }, - }, - required: ['maker', 'taker', 'salt', 'signature', 'expiration', 'feeRecipient', 'exchangeContract', 'networkId'], - type: 'object', + id: '/Order', + properties: { + maker: { $ref: '/OrderTaker' }, + taker: { $ref: '/OrderTaker' }, + salt: { type: 'string' }, + signature: { $ref: '/SignatureData' }, + expiration: { type: 'string' }, + feeRecipient: { type: 'string' }, + exchangeContract: { type: 'string' }, + networkId: { type: 'number' }, + }, + required: ['maker', 'taker', 'salt', 'signature', 'expiration', 'feeRecipient', 'exchangeContract', 'networkId'], + type: 'object', }; diff --git a/packages/website/ts/schemas/order_taker_schema.ts b/packages/website/ts/schemas/order_taker_schema.ts index c84ec4a9f..c784c29c5 100644 --- a/packages/website/ts/schemas/order_taker_schema.ts +++ b/packages/website/ts/schemas/order_taker_schema.ts @@ -1,11 +1,11 @@ export const orderTakerSchema = { - id: '/OrderTaker', - properties: { - address: { type: 'string' }, - token: { $ref: '/Token' }, - amount: { type: 'string' }, - feeAmount: { type: 'string' }, - }, - required: ['address', 'token', 'amount', 'feeAmount'], - type: 'object', + id: '/OrderTaker', + properties: { + address: { type: 'string' }, + token: { $ref: '/Token' }, + amount: { type: 'string' }, + feeAmount: { type: 'string' }, + }, + required: ['address', 'token', 'amount', 'feeAmount'], + type: 'object', }; diff --git a/packages/website/ts/schemas/signature_data_schema.ts b/packages/website/ts/schemas/signature_data_schema.ts index 8cafff9e8..8d3f15926 100644 --- a/packages/website/ts/schemas/signature_data_schema.ts +++ b/packages/website/ts/schemas/signature_data_schema.ts @@ -1,11 +1,11 @@ export const signatureDataSchema = { - id: '/SignatureData', - properties: { - hash: { type: 'string' }, - r: { type: 'string' }, - s: { type: 'string' }, - v: { type: 'number' }, - }, - required: ['hash', 'r', 's', 'v'], - type: 'object', + id: '/SignatureData', + properties: { + hash: { type: 'string' }, + r: { type: 'string' }, + s: { type: 'string' }, + v: { type: 'number' }, + }, + required: ['hash', 'r', 's', 'v'], + type: 'object', }; diff --git a/packages/website/ts/schemas/token_schema.ts b/packages/website/ts/schemas/token_schema.ts index 6a3bed786..92b53a463 100644 --- a/packages/website/ts/schemas/token_schema.ts +++ b/packages/website/ts/schemas/token_schema.ts @@ -1,11 +1,11 @@ export const tokenSchema = { - id: '/Token', - properties: { - name: { type: 'string' }, - symbol: { type: 'string' }, - decimals: { type: 'number' }, - address: { type: 'string' }, - }, - required: ['name', 'symbol', 'decimals', 'address'], - type: 'object', + id: '/Token', + properties: { + name: { type: 'string' }, + symbol: { type: 'string' }, + decimals: { type: 'number' }, + address: { type: 'string' }, + }, + required: ['name', 'symbol', 'decimals', 'address'], + type: 'object', }; diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts index a3aaafc98..5177501c6 100644 --- a/packages/website/ts/schemas/validator.ts +++ b/packages/website/ts/schemas/validator.ts @@ -5,15 +5,15 @@ import { signatureDataSchema } from 'ts/schemas/signature_data_schema'; import { tokenSchema } from 'ts/schemas/token_schema'; export class SchemaValidator { - private _validator: Validator; - constructor() { - this._validator = new Validator(); - this._validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); - this._validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); - this._validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); - this._validator.addSchema(orderSchema as JSONSchema, orderSchema.id); - } - public validate(instance: object, schema: Schema) { - return this._validator.validate(instance, schema); - } + private _validator: Validator; + constructor() { + this._validator = new Validator(); + this._validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); + this._validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); + this._validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); + this._validator.addSchema(orderSchema as JSONSchema, orderSchema.id); + } + public validate(instance: object, schema: Schema) { + return this._validator.validate(instance, schema); + } } diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index a853792cb..f873f95fa 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -2,680 +2,680 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; export enum Side { - Receive = 'RECEIVE', - Deposit = 'DEPOSIT', + Receive = 'RECEIVE', + Deposit = 'DEPOSIT', } export interface Token { - iconUrl?: string; - name: string; - address: string; - symbol: string; - decimals: number; - isTracked: boolean; - isRegistered: boolean; + iconUrl?: string; + name: string; + address: string; + symbol: string; + decimals: number; + isTracked: boolean; + isRegistered: boolean; } export interface TokenByAddress { - [address: string]: Token; + [address: string]: Token; } export interface TokenState { - allowance: BigNumber; - balance: BigNumber; + allowance: BigNumber; + balance: BigNumber; } export interface TokenStateByAddress { - [address: string]: TokenState; + [address: string]: TokenState; } export interface AssetToken { - address?: string; - amount?: BigNumber; + address?: string; + amount?: BigNumber; } export interface SideToAssetToken { - [side: string]: AssetToken; + [side: string]: AssetToken; } export interface SignatureData { - hash: string; - r: string; - s: string; - v: number; + hash: string; + r: string; + s: string; + v: number; } export interface HashData { - depositAmount: BigNumber; - depositTokenContractAddr: string; - feeRecipientAddress: string; - makerFee: BigNumber; - orderExpiryTimestamp: BigNumber; - orderMakerAddress: string; - orderTakerAddress: string; - receiveAmount: BigNumber; - receiveTokenContractAddr: string; - takerFee: BigNumber; - orderSalt: BigNumber; + depositAmount: BigNumber; + depositTokenContractAddr: string; + feeRecipientAddress: string; + makerFee: BigNumber; + orderExpiryTimestamp: BigNumber; + orderMakerAddress: string; + orderTakerAddress: string; + receiveAmount: BigNumber; + receiveTokenContractAddr: string; + takerFee: BigNumber; + orderSalt: BigNumber; } export interface OrderToken { - name: string; - symbol: string; - decimals: number; - address: string; + name: string; + symbol: string; + decimals: number; + address: string; } export interface OrderParty { - address: string; - token: OrderToken; - amount: string; - feeAmount: string; + address: string; + token: OrderToken; + amount: string; + feeAmount: string; } export interface Order { - maker: OrderParty; - taker: OrderParty; - expiration: string; - feeRecipient: string; - salt: string; - signature: SignatureData; - exchangeContract: string; - networkId: number; + maker: OrderParty; + taker: OrderParty; + expiration: string; + feeRecipient: string; + salt: string; + signature: SignatureData; + exchangeContract: string; + networkId: number; } export interface Fill { - logIndex: number; - maker: string; - taker: string; - makerToken: string; - takerToken: string; - filledMakerTokenAmount: BigNumber; - filledTakerTokenAmount: BigNumber; - paidMakerFee: BigNumber; - paidTakerFee: BigNumber; - orderHash: string; - transactionHash: string; - blockTimestamp: number; + logIndex: number; + maker: string; + taker: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + orderHash: string; + transactionHash: string; + blockTimestamp: number; } export enum BalanceErrs { - incorrectNetworkForFaucet, - faucetRequestFailed, - faucetQueueIsFull, - mintingFailed, - sendFailed, - allowanceSettingFailed, + incorrectNetworkForFaucet, + faucetRequestFailed, + faucetQueueIsFull, + mintingFailed, + sendFailed, + allowanceSettingFailed, } export enum ActionTypes { - // Portal - UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH', - UpdateNodeVersion = 'UPDATE_NODE_VERSION', - ResetState = 'RESET_STATE', - AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', - BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED', - ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS', - UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED', - UpdateNetworkId = 'UPDATE_NETWORK_ID', - UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN', - UpdateChosenAssetTokenAddress = 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', - UpdateOrderTakerAddress = 'UPDATE_ORDER_TAKER_ADDRESS', - UpdateOrderSalt = 'UPDATE_ORDER_SALT', - UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA', - UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS', - RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS', - UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS', - RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', - ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', - ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', - UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', - UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY', - SwapAssetTokens = 'SWAP_ASSET_TOKENS', - UpdateUserAddress = 'UPDATE_USER_ADDRESS', - UpdateUserEtherBalance = 'UPDATE_USER_ETHER_BALANCE', - UpdateUserSuppliedOrderCache = 'UPDATE_USER_SUPPLIED_ORDER_CACHE', - UpdateOrderFillAmount = 'UPDATE_ORDER_FILL_AMOUNT', - UpdateShouldBlockchainErrDialogBeOpen = 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', - - // Docs - UpdateLibraryVersion = 'UPDATE_LIBRARY_VERSION', - UpdateAvailableLibraryVersions = 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', - - // Shared - ShowFlashMessage = 'SHOW_FLASH_MESSAGE', - HideFlashMessage = 'HIDE_FLASH_MESSAGE', - UpdateProviderType = 'UPDATE_PROVIDER_TYPE', - UpdateInjectedProviderName = 'UPDATE_INJECTED_PROVIDER_NAME', + // Portal + UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH', + UpdateNodeVersion = 'UPDATE_NODE_VERSION', + ResetState = 'RESET_STATE', + AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', + BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED', + ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS', + UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED', + UpdateNetworkId = 'UPDATE_NETWORK_ID', + UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN', + UpdateChosenAssetTokenAddress = 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', + UpdateOrderTakerAddress = 'UPDATE_ORDER_TAKER_ADDRESS', + UpdateOrderSalt = 'UPDATE_ORDER_SALT', + UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA', + UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS', + RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS', + UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS', + RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', + ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', + ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', + UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', + UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY', + SwapAssetTokens = 'SWAP_ASSET_TOKENS', + UpdateUserAddress = 'UPDATE_USER_ADDRESS', + UpdateUserEtherBalance = 'UPDATE_USER_ETHER_BALANCE', + UpdateUserSuppliedOrderCache = 'UPDATE_USER_SUPPLIED_ORDER_CACHE', + UpdateOrderFillAmount = 'UPDATE_ORDER_FILL_AMOUNT', + UpdateShouldBlockchainErrDialogBeOpen = 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', + + // Docs + UpdateLibraryVersion = 'UPDATE_LIBRARY_VERSION', + UpdateAvailableLibraryVersions = 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', + + // Shared + ShowFlashMessage = 'SHOW_FLASH_MESSAGE', + HideFlashMessage = 'HIDE_FLASH_MESSAGE', + UpdateProviderType = 'UPDATE_PROVIDER_TYPE', + UpdateInjectedProviderName = 'UPDATE_INJECTED_PROVIDER_NAME', } export interface Action { - type: ActionTypes; - data?: any; + type: ActionTypes; + data?: any; } export interface TrackedTokensByNetworkId { - [networkId: number]: Token[]; + [networkId: number]: Token[]; } export interface TrackedTokensByUserAddress { - [userAddress: string]: TrackedTokensByNetworkId; + [userAddress: string]: TrackedTokensByNetworkId; } export interface Styles { - [name: string]: React.CSSProperties; + [name: string]: React.CSSProperties; } export interface ProfileInfo { - name: string; - title?: string; - description: string; - image: string; - linkedIn?: string; - github?: string; - angellist?: string; - medium?: string; - twitter?: string; + name: string; + title?: string; + description: string; + image: string; + linkedIn?: string; + github?: string; + angellist?: string; + medium?: string; + twitter?: string; } export interface Partner { - name: string; - logo: string; - url: string; + name: string; + logo: string; + url: string; } export interface Statistic { - title: string; - figure: string; + title: string; + figure: string; } export interface StatisticByKey { - [key: string]: Statistic; + [key: string]: Statistic; } export interface ERC20MarketInfo { - etherMarketCapUsd: number; - numLiquidERC20Tokens: number; - marketCapERC20TokensUsd: number; + etherMarketCapUsd: number; + numLiquidERC20Tokens: number; + marketCapERC20TokensUsd: number; } export enum ExchangeContractErrs { - OrderFillExpired = 'ORDER_FILL_EXPIRED', - OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', - OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', - OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', - FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', - InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', - InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', - InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', - InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', - TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', - InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', } export interface ContractResponse { - logs: ContractEvent[]; + logs: ContractEvent[]; } export interface ContractEvent { - event: string; - args: any; + event: string; + args: any; } export type InputErrMsg = React.ReactNode | string | undefined; export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; export enum ScreenWidths { - Sm = 'SM', - Md = 'MD', - Lg = 'LG', + Sm = 'SM', + Md = 'MD', + Lg = 'LG', } export enum AlertTypes { - ERROR, - SUCCESS, + ERROR, + SUCCESS, } export enum EtherscanLinkSuffixes { - Address = 'address', - Tx = 'tx', + Address = 'address', + Tx = 'tx', } export enum BlockchainErrs { - AContractNotDeployedOnNetwork = 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', - DisconnectedFromEthereumNode = 'DISCONNECTED_FROM_ETHEREUM_NODE', - NoError = 'NO_ERROR', + AContractNotDeployedOnNetwork = 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', + DisconnectedFromEthereumNode = 'DISCONNECTED_FROM_ETHEREUM_NODE', + NoError = 'NO_ERROR', } export enum BlockchainCallErrs { - ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', - UserHasNoAssociatedAddresses = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - UnhandledError = 'UNHANDLED_ERROR', - TokenAddressIsInvalid = 'TOKEN_ADDRESS_IS_INVALID', + ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', + UserHasNoAssociatedAddresses = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + UnhandledError = 'UNHANDLED_ERROR', + TokenAddressIsInvalid = 'TOKEN_ADDRESS_IS_INVALID', } // Exception: We don't make the values uppercase because these KindString's need to // match up those returned by TypeDoc export enum KindString { - Constructor = 'Constructor', - Property = 'Property', - Method = 'Method', - Interface = 'Interface', - TypeAlias = 'Type alias', - Variable = 'Variable', - Function = 'Function', - Enumeration = 'Enumeration', + Constructor = 'Constructor', + Property = 'Property', + Method = 'Method', + Interface = 'Interface', + TypeAlias = 'Type alias', + Variable = 'Variable', + Function = 'Function', + Enumeration = 'Enumeration', } export interface EnumValue { - name: string; - defaultValue?: string; + name: string; + defaultValue?: string; } export enum Environments { - DEVELOPMENT, - PRODUCTION, + DEVELOPMENT, + PRODUCTION, } export type ContractInstance = any; // TODO: add type definition for Contract export interface TypeDocType { - type: TypeDocTypes; - value: string; - name: string; - types: TypeDocType[]; - typeArguments?: TypeDocType[]; - declaration: TypeDocNode; - elementType?: TypeDocType; + type: TypeDocTypes; + value: string; + name: string; + types: TypeDocType[]; + typeArguments?: TypeDocType[]; + declaration: TypeDocNode; + elementType?: TypeDocType; } export interface TypeDocFlags { - isStatic?: boolean; - isOptional?: boolean; - isPublic?: boolean; + isStatic?: boolean; + isOptional?: boolean; + isPublic?: boolean; } export interface TypeDocGroup { - title: string; - children: number[]; + title: string; + children: number[]; } export interface TypeDocNode { - id?: number; - name?: string; - kind?: string; - defaultValue?: string; - kindString?: string; - type?: TypeDocType; - fileName?: string; - line?: number; - comment?: TypeDocNode; - text?: string; - shortText?: string; - returns?: string; - declaration: TypeDocNode; - flags?: TypeDocFlags; - indexSignature?: TypeDocNode[]; - signatures?: TypeDocNode[]; - parameters?: TypeDocNode[]; - typeParameter?: TypeDocNode[]; - sources?: TypeDocNode[]; - children?: TypeDocNode[]; - groups?: TypeDocGroup[]; + id?: number; + name?: string; + kind?: string; + defaultValue?: string; + kindString?: string; + type?: TypeDocType; + fileName?: string; + line?: number; + comment?: TypeDocNode; + text?: string; + shortText?: string; + returns?: string; + declaration: TypeDocNode; + flags?: TypeDocFlags; + indexSignature?: TypeDocNode[]; + signatures?: TypeDocNode[]; + parameters?: TypeDocNode[]; + typeParameter?: TypeDocNode[]; + sources?: TypeDocNode[]; + children?: TypeDocNode[]; + groups?: TypeDocGroup[]; } export enum TypeDocTypes { - Intrinsic = 'intrinsic', - Reference = 'reference', - Array = 'array', - StringLiteral = 'stringLiteral', - Reflection = 'reflection', - Union = 'union', - TypeParameter = 'typeParameter', - Unknown = 'unknown', + Intrinsic = 'intrinsic', + Reference = 'reference', + Array = 'array', + StringLiteral = 'stringLiteral', + Reflection = 'reflection', + Union = 'union', + TypeParameter = 'typeParameter', + Unknown = 'unknown', } export interface DocAgnosticFormat { - [sectionName: string]: DocSection; + [sectionName: string]: DocSection; } export interface DocSection { - comment: string; - constructors: Array<TypescriptMethod | SolidityMethod>; - methods: Array<TypescriptMethod | SolidityMethod>; - properties: Property[]; - types: CustomType[]; - events?: Event[]; + comment: string; + constructors: Array<TypescriptMethod | SolidityMethod>; + methods: Array<TypescriptMethod | SolidityMethod>; + properties: Property[]; + types: CustomType[]; + events?: Event[]; } export interface Event { - name: string; - eventArgs: EventArg[]; + name: string; + eventArgs: EventArg[]; } export interface EventArg { - isIndexed: boolean; - name: string; - type: Type; + isIndexed: boolean; + name: string; + type: Type; } export interface Property { - name: string; - type: Type; - source?: Source; - comment?: string; + name: string; + type: Type; + source?: Source; + comment?: string; } export interface BaseMethod { - isConstructor: boolean; - name: string; - returnComment?: string | undefined; - callPath: string; - parameters: Parameter[]; - returnType: Type; - comment?: string; + isConstructor: boolean; + name: string; + returnComment?: string | undefined; + callPath: string; + parameters: Parameter[]; + returnType: Type; + comment?: string; } export interface TypescriptMethod extends BaseMethod { - source?: Source; - isStatic?: boolean; - typeParameter?: TypeParameter; + source?: Source; + isStatic?: boolean; + typeParameter?: TypeParameter; } export interface SolidityMethod extends BaseMethod { - isConstant?: boolean; - isPayable?: boolean; + isConstant?: boolean; + isPayable?: boolean; } export interface Source { - fileName: string; - line: number; + fileName: string; + line: number; } export interface Parameter { - name: string; - comment: string; - isOptional: boolean; - type: Type; + name: string; + comment: string; + isOptional: boolean; + type: Type; } export interface TypeParameter { - name: string; - type: Type; + name: string; + type: Type; } export interface Type { - name: string; - typeDocType: TypeDocTypes; - value?: string; - typeArguments?: Type[]; - elementType?: ElementType; - types?: Type[]; - method?: TypescriptMethod; + name: string; + typeDocType: TypeDocTypes; + value?: string; + typeArguments?: Type[]; + elementType?: ElementType; + types?: Type[]; + method?: TypescriptMethod; } export interface ElementType { - name: string; - typeDocType: TypeDocTypes; + name: string; + typeDocType: TypeDocTypes; } export interface IndexSignature { - keyName: string; - keyType: Type; - valueName: string; + keyName: string; + keyType: Type; + valueName: string; } export interface CustomType { - name: string; - kindString: string; - type?: Type; - method?: TypescriptMethod; - indexSignature?: IndexSignature; - defaultValue?: string; - comment?: string; - children?: CustomTypeChild[]; + name: string; + kindString: string; + type?: Type; + method?: TypescriptMethod; + indexSignature?: IndexSignature; + defaultValue?: string; + comment?: string; + children?: CustomTypeChild[]; } export interface CustomTypeChild { - name: string; - type?: Type; - defaultValue?: string; + name: string; + type?: Type; + defaultValue?: string; } export interface FAQQuestion { - prompt: string; - answer: React.ReactNode; + prompt: string; + answer: React.ReactNode; } export interface FAQSection { - name: string; - questions: FAQQuestion[]; + name: string; + questions: FAQQuestion[]; } export interface S3FileObject { - Key: { - _text: string; - }; + Key: { + _text: string; + }; } export interface MenuSubsectionsBySection { - [section: string]: string[]; + [section: string]: string[]; } export enum ProviderType { - Injected = 'INJECTED', - Ledger = 'LEDGER', + Injected = 'INJECTED', + Ledger = 'LEDGER', } export interface Fact { - title: string; - explanation: string; - image: string; + title: string; + explanation: string; + image: string; } interface LedgerGetAddressResult { - address: string; + address: string; } interface LedgerSignResult { - v: string; - r: string; - s: string; + v: string; + r: string; + s: string; } interface LedgerCommunication { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } export interface LedgerEthConnection { - getAddress_async: ( - derivationPath: string, - askForDeviceConfirmation: boolean, - shouldGetChainCode: boolean, - ) => Promise<LedgerGetAddressResult>; - signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<LedgerSignResult>; - signTransaction_async: (derivationPath: string, txHex: string) => Promise<LedgerSignResult>; - comm: LedgerCommunication; + getAddress_async: ( + derivationPath: string, + askForDeviceConfirmation: boolean, + shouldGetChainCode: boolean, + ) => Promise<LedgerGetAddressResult>; + signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<LedgerSignResult>; + signTransaction_async: (derivationPath: string, txHex: string) => Promise<LedgerSignResult>; + comm: LedgerCommunication; } export interface SignPersonalMessageParams { - data: string; + data: string; } export interface TxParams { - nonce: string; - gasPrice?: number; - gasLimit: string; - to: string; - value?: string; - data?: string; - chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 + nonce: string; + gasPrice?: number; + gasLimit: string; + to: string; + value?: string; + data?: string; + chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 } export interface PublicNodeUrlsByNetworkId { - [networkId: number]: string[]; + [networkId: number]: string[]; } export interface JSONRPCPayload { - params: any[]; - method: string; + params: any[]; + method: string; } export interface BlogPost { - image: string; - date: string; - title: string; - description: string; - url: string; + image: string; + date: string; + title: string; + description: string; + url: string; } export interface TypeDefinitionByName { - [typeName: string]: CustomType; + [typeName: string]: CustomType; } export interface Article { - section: string; - title: string; - content: string; - fileName: string; + section: string; + title: string; + content: string; + fileName: string; } export interface ArticlesBySection { - [section: string]: Article[]; + [section: string]: Article[]; } export interface DialogConfigs { - title: string; - isModal: boolean; - actions: any[]; + title: string; + isModal: boolean; + actions: any[]; } export enum TokenVisibility { - ALL = 'ALL', - UNTRACKED = 'UNTRACKED', - TRACKED = 'TRACKED', + ALL = 'ALL', + UNTRACKED = 'UNTRACKED', + TRACKED = 'TRACKED', } export enum HeaderSizes { - H1 = 'h1', - H2 = 'h2', - H3 = 'h3', + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', } export interface DoxityDocObj { - [contractName: string]: DoxityContractObj; + [contractName: string]: DoxityContractObj; } export interface DoxityContractObj { - title: string; - fileName: string; - name: string; - abiDocs: DoxityAbiDoc[]; + title: string; + fileName: string; + name: string; + abiDocs: DoxityAbiDoc[]; } export interface DoxityAbiDoc { - constant: boolean; - inputs: DoxityInput[]; - name: string; - outputs: DoxityOutput[]; - payable: boolean; - type: string; - details?: string; - return?: string; + constant: boolean; + inputs: DoxityInput[]; + name: string; + outputs: DoxityOutput[]; + payable: boolean; + type: string; + details?: string; + return?: string; } export interface DoxityOutput { - name: string; - type: string; + name: string; + type: string; } export interface DoxityInput { - name: string; - type: string; - description: string; - indexed?: boolean; + name: string; + type: string; + description: string; + indexed?: boolean; } export interface VersionToFileName { - [version: string]: string; + [version: string]: string; } export enum Docs { - ZeroExJs, - SmartContracts, + ZeroExJs, + SmartContracts, } export interface ContractAddresses { - [version: string]: { - [network: string]: AddressByContractName; - }; + [version: string]: { + [network: string]: AddressByContractName; + }; } export interface AddressByContractName { - [contractName: string]: string; + [contractName: string]: string; } export enum Networks { - mainnet = 'Mainnet', - kovan = 'Kovan', - ropsten = 'Ropsten', - rinkeby = 'Rinkeby', + mainnet = 'Mainnet', + kovan = 'Kovan', + ropsten = 'Ropsten', + rinkeby = 'Rinkeby', } export enum AbiTypes { - Constructor = 'constructor', - Function = 'function', - Event = 'event', + Constructor = 'constructor', + Function = 'function', + Event = 'event', } export enum WebsitePaths { - Portal = '/portal', - Wiki = '/wiki', - ZeroExJs = '/docs/0xjs', - Home = '/', - FAQ = '/faq', - About = '/about', - Whitepaper = '/pdfs/0x_white_paper.pdf', - SmartContracts = '/docs/contracts', - Connect = '/docs/connect', + Portal = '/portal', + Wiki = '/wiki', + ZeroExJs = '/docs/0xjs', + Home = '/', + FAQ = '/faq', + About = '/about', + Whitepaper = '/pdfs/0x_white_paper.pdf', + SmartContracts = '/docs/contracts', + Connect = '/docs/connect', } export interface DocsMenu { - [sectionName: string]: string[]; + [sectionName: string]: string[]; } export interface SectionsMap { - [sectionName: string]: string; + [sectionName: string]: string; } export interface DocsInfoConfig { - displayName: string; - packageUrl: string; - websitePath: string; - docsJsonRoot: string; - menu: DocsMenu; - sections: SectionsMap; - sectionNameToMarkdown: { [sectionName: string]: string }; - visibleConstructors: string[]; - convertToDocAgnosticFormatFn: (docObj: DoxityDocObj | TypeDocNode, docsInfo?: any) => DocAgnosticFormat; - subPackageName?: string; - publicTypes?: string[]; - sectionNameToModulePath?: { [sectionName: string]: string[] }; - menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; + displayName: string; + packageUrl: string; + websitePath: string; + docsJsonRoot: string; + menu: DocsMenu; + sections: SectionsMap; + sectionNameToMarkdown: { [sectionName: string]: string }; + visibleConstructors: string[]; + convertToDocAgnosticFormatFn: (docObj: DoxityDocObj | TypeDocNode, docsInfo?: any) => DocAgnosticFormat; + subPackageName?: string; + publicTypes?: string[]; + sectionNameToModulePath?: { [sectionName: string]: string[] }; + menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; } export interface TimestampMsRange { - startTimestampMs: number; - endTimestampMs: number; + startTimestampMs: number; + endTimestampMs: number; } export interface OutdatedWrappedEtherByNetworkId { - [networkId: number]: { - address: string; - timestampMsRange: TimestampMsRange; - }; + [networkId: number]: { + address: string; + timestampMsRange: TimestampMsRange; + }; } export enum SmartContractDocSections { - Introduction = 'Introduction', - Exchange = 'Exchange', - TokenTransferProxy = 'TokenTransferProxy', - TokenRegistry = 'TokenRegistry', - ZRXToken = 'ZRXToken', + Introduction = 'Introduction', + Exchange = 'Exchange', + TokenTransferProxy = 'TokenTransferProxy', + TokenRegistry = 'TokenRegistry', + ZRXToken = 'ZRXToken', } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/colors.ts b/packages/website/ts/utils/colors.ts index dabc1fd54..58ce667e3 100644 --- a/packages/website/ts/utils/colors.ts +++ b/packages/website/ts/utils/colors.ts @@ -1,43 +1,43 @@ import { colors as materialUiColors } from 'material-ui/styles'; export const colors = { - ...materialUiColors, - grey50: '#FAFAFA', - grey100: '#F5F5F5', - lightestGrey: '#F0F0F0', - greyishPink: '#E6E5E5', - grey300: '#E0E0E0', - beigeWhite: '#E4E4E4', - grey400: '#BDBDBD', - lightGrey: '#BBBBBB', - grey500: '#9E9E9E', - grey: '#A5A5A5', - darkGrey: '#818181', - landingLinkGrey: '#919191', - grey700: '#616161', - grey800: '#424242', - darkerGrey: '#393939', - heroGrey: '#404040', - projectsGrey: '#343333', - darkestGrey: '#272727', - dharmaDarkGrey: '#252525', - lightBlue: '#60A4F4', - lightBlueA700: '#0091EA', - darkBlue: '#4D5481', - turquois: '#058789', - lightPurple: '#A81CA6', - purple: '#690596', - red200: '#EF9A9A', - red: '#E91751', - red500: '#F44336', - red600: '#E53935', - limeGreen: '#66DE75', - lightGreen: '#4DC55C', - lightestGreen: '#89C774', - brightGreen: '#00C33E', - green400: '#66BB6A', - green: '#4DA24B', - amber600: '#FFB300', - orange: '#E69D00', - amber800: '#FF8F00', + ...materialUiColors, + grey50: '#FAFAFA', + grey100: '#F5F5F5', + lightestGrey: '#F0F0F0', + greyishPink: '#E6E5E5', + grey300: '#E0E0E0', + beigeWhite: '#E4E4E4', + grey400: '#BDBDBD', + lightGrey: '#BBBBBB', + grey500: '#9E9E9E', + grey: '#A5A5A5', + darkGrey: '#818181', + landingLinkGrey: '#919191', + grey700: '#616161', + grey800: '#424242', + darkerGrey: '#393939', + heroGrey: '#404040', + projectsGrey: '#343333', + darkestGrey: '#272727', + dharmaDarkGrey: '#252525', + lightBlue: '#60A4F4', + lightBlueA700: '#0091EA', + darkBlue: '#4D5481', + turquois: '#058789', + lightPurple: '#A81CA6', + purple: '#690596', + red200: '#EF9A9A', + red: '#E91751', + red500: '#F44336', + red600: '#E53935', + limeGreen: '#66DE75', + lightGreen: '#4DC55C', + lightestGreen: '#89C774', + brightGreen: '#00C33E', + green400: '#66BB6A', + green: '#4DA24B', + amber600: '#FFB300', + orange: '#E69D00', + amber800: '#FF8F00', }; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 6327ea05a..3d37a89ab 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,126 +1,126 @@ import * as _ from 'lodash'; import { - ContractAddresses, - Environments, - Networks, - OutdatedWrappedEtherByNetworkId, - PublicNodeUrlsByNetworkId, - SmartContractDocSections, + ContractAddresses, + Environments, + Networks, + OutdatedWrappedEtherByNetworkId, + PublicNodeUrlsByNetworkId, + SmartContractDocSections, } from 'ts/types'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( - ['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'], - BASE_URL, + ['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'], + BASE_URL, ); const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { - BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', - BASE_URL, - BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', - CONTRACT_ADDRESS: { - '1.0.0': { - [Networks.mainnet]: { - [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', - [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', - [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', - [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', - }, - [Networks.ropsten]: { - [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', - [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', - [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', - [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', - }, - [Networks.kovan]: { - [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', - [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', - [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', - [SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', - }, - }, - } as ContractAddresses, - DEFAULT_DERIVATION_PATH: `44'/60'/0'`, - // WARNING: ZRX & WETH MUST always be default trackedTokens - DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], - DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', - DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', - DOMAIN_PRODUCTION: '0xproject.com', - ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, - ICON_URL_BY_SYMBOL: { - REP: '/images/token_icons/augur.png', - DGD: '/images/token_icons/digixdao.png', - WETH: '/images/token_icons/ether_erc20.png', - MLN: '/images/token_icons/melon.png', - GNT: '/images/token_icons/golem.png', - MKR: '/images/token_icons/makerdao.png', - ZRX: '/images/token_icons/zero_ex.png', - ANT: '/images/token_icons/aragon.png', - BNT: '/images/token_icons/bancor.png', - BAT: '/images/token_icons/basicattentiontoken.png', - CVC: '/images/token_icons/civic.png', - EOS: '/images/token_icons/eos.png', - FUN: '/images/token_icons/funfair.png', - GNO: '/images/token_icons/gnosis.png', - ICN: '/images/token_icons/iconomi.png', - OMG: '/images/token_icons/omisego.png', - SNT: '/images/token_icons/status.png', - STORJ: '/images/token_icons/storjcoinx.png', - PAY: '/images/token_icons/tenx.png', - QTUM: '/images/token_icons/qtum.png', - DNT: '/images/token_icons/district0x.png', - SNGLS: '/images/token_icons/singularity.png', - EDG: '/images/token_icons/edgeless.png', - '1ST': '/images/token_icons/firstblood.jpg', - WINGS: '/images/token_icons/wings.png', - BQX: '/images/token_icons/bitquence.png', - LUN: '/images/token_icons/lunyr.png', - RLC: '/images/token_icons/iexec.png', - MCO: '/images/token_icons/monaco.png', - ADT: '/images/token_icons/adtoken.png', - CFI: '/images/token_icons/cofound-it.png', - ROL: '/images/token_icons/etheroll.png', - WGNT: '/images/token_icons/golem.png', - MTL: '/images/token_icons/metal.png', - NMR: '/images/token_icons/numeraire.png', - SAN: '/images/token_icons/santiment.png', - TAAS: '/images/token_icons/taas.png', - TKN: '/images/token_icons/tokencard.png', - TRST: '/images/token_icons/trust.png', - } as { [symbol: string]: string }, - IS_MAINNET_ENABLED: true, - LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', - LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19', - // NEW_WRAPPED_ETHERS is temporary until we remove the SHOULD_DEPRECATE_OLD_WETH_TOKEN flag - // and add the new WETHs to the tokenRegistry - NEW_WRAPPED_ETHERS: { - 1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - 42: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', - } as { [networkId: string]: string }, - OUTDATED_WRAPPED_ETHERS: [ - { - 42: { - address: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', - timestampMsRange: { - startTimestampMs: 1502455607000, - endTimestampMs: 1513790926000, - }, - }, - 1: { - address: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', - timestampMsRange: { - startTimestampMs: 1502455607000, - endTimestampMs: 1513790926000, - }, - }, - }, - ] as OutdatedWrappedEtherByNetworkId[], - // The order matters. We first try first node and only then fall back to others. - PUBLIC_NODE_URLS_BY_NETWORK_ID: { - [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], - [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], - } as PublicNodeUrlsByNetworkId, - SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, - SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], + BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', + BASE_URL, + BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', + CONTRACT_ADDRESS: { + '1.0.0': { + [Networks.mainnet]: { + [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', + [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', + }, + [Networks.ropsten]: { + [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', + [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', + }, + [Networks.kovan]: { + [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', + [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + [SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', + }, + }, + } as ContractAddresses, + DEFAULT_DERIVATION_PATH: `44'/60'/0'`, + // WARNING: ZRX & WETH MUST always be default trackedTokens + DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], + DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', + DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', + DOMAIN_PRODUCTION: '0xproject.com', + ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, + ICON_URL_BY_SYMBOL: { + REP: '/images/token_icons/augur.png', + DGD: '/images/token_icons/digixdao.png', + WETH: '/images/token_icons/ether_erc20.png', + MLN: '/images/token_icons/melon.png', + GNT: '/images/token_icons/golem.png', + MKR: '/images/token_icons/makerdao.png', + ZRX: '/images/token_icons/zero_ex.png', + ANT: '/images/token_icons/aragon.png', + BNT: '/images/token_icons/bancor.png', + BAT: '/images/token_icons/basicattentiontoken.png', + CVC: '/images/token_icons/civic.png', + EOS: '/images/token_icons/eos.png', + FUN: '/images/token_icons/funfair.png', + GNO: '/images/token_icons/gnosis.png', + ICN: '/images/token_icons/iconomi.png', + OMG: '/images/token_icons/omisego.png', + SNT: '/images/token_icons/status.png', + STORJ: '/images/token_icons/storjcoinx.png', + PAY: '/images/token_icons/tenx.png', + QTUM: '/images/token_icons/qtum.png', + DNT: '/images/token_icons/district0x.png', + SNGLS: '/images/token_icons/singularity.png', + EDG: '/images/token_icons/edgeless.png', + '1ST': '/images/token_icons/firstblood.jpg', + WINGS: '/images/token_icons/wings.png', + BQX: '/images/token_icons/bitquence.png', + LUN: '/images/token_icons/lunyr.png', + RLC: '/images/token_icons/iexec.png', + MCO: '/images/token_icons/monaco.png', + ADT: '/images/token_icons/adtoken.png', + CFI: '/images/token_icons/cofound-it.png', + ROL: '/images/token_icons/etheroll.png', + WGNT: '/images/token_icons/golem.png', + MTL: '/images/token_icons/metal.png', + NMR: '/images/token_icons/numeraire.png', + SAN: '/images/token_icons/santiment.png', + TAAS: '/images/token_icons/taas.png', + TKN: '/images/token_icons/tokencard.png', + TRST: '/images/token_icons/trust.png', + } as { [symbol: string]: string }, + IS_MAINNET_ENABLED: true, + LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', + LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19', + // NEW_WRAPPED_ETHERS is temporary until we remove the SHOULD_DEPRECATE_OLD_WETH_TOKEN flag + // and add the new WETHs to the tokenRegistry + NEW_WRAPPED_ETHERS: { + 1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + 42: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + } as { [networkId: string]: string }, + OUTDATED_WRAPPED_ETHERS: [ + { + 42: { + address: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', + timestampMsRange: { + startTimestampMs: 1502455607000, + endTimestampMs: 1513790926000, + }, + }, + 1: { + address: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', + timestampMsRange: { + startTimestampMs: 1502455607000, + endTimestampMs: 1513790926000, + }, + }, + }, + ] as OutdatedWrappedEtherByNetworkId[], + // The order matters. We first try first node and only then fall back to others. + PUBLIC_NODE_URLS_BY_NETWORK_ID: { + [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], + [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], + } as PublicNodeUrlsByNetworkId, + SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, + SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], }; diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index bb6407ec5..dded82114 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -2,86 +2,86 @@ import { BigNumber } from '@0xproject/utils'; import { Networks } from 'ts/types'; export const constants = { - DECIMAL_PLACES_ETH: 18, - DECIMAL_PLACES_ZRX: 18, - DOCS_SCROLL_DURATION_MS: 0, - DOCS_CONTAINER_ID: 'documentation', - GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { - 1: 4145578, - 42: 3117574, - 50: 0, - } as { [networkId: number]: number }, - HOME_SCROLL_DURATION_MS: 500, - HTTP_NO_CONTENT_STATUS_CODE: 204, - LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER: 'didAcceptPortalDisclaimer', - LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE: 'hasDismissedWethNotice', - MAKER_FEE: new BigNumber(0), - MAINNET_NAME: 'Main network', - MINT_AMOUNT: new BigNumber('100000000000000000000'), - NETWORK_ID_MAINNET: 1, - NETWORK_ID_TESTNET: 42, - NETWORK_ID_TESTRPC: 50, - NETWORK_NAME_BY_ID: { - 1: Networks.mainnet, - 3: Networks.ropsten, - 4: Networks.rinkeby, - 42: Networks.kovan, - } as { [symbol: number]: string }, - NETWORK_ID_BY_NAME: { - [Networks.mainnet]: 1, - [Networks.ropsten]: 3, - [Networks.rinkeby]: 4, - [Networks.kovan]: 42, - } as { [networkName: string]: number }, - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - PROVIDER_NAME_LEDGER: 'Ledger', - PROVIDER_NAME_METAMASK: 'Metamask', - PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', - PROVIDER_NAME_GENERIC: 'Injected Web3', - PROVIDER_NAME_PUBLIC: '0x Public', - ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', - SUCCESS_STATUS: 200, - UNAVAILABLE_STATUS: 503, - TAKER_FEE: new BigNumber(0), - TESTNET_NAME: 'Kovan', - TYPES_SECTION_NAME: 'types', - PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', - PROJECT_URL_RADAR_RELAY: 'https://radarrelay.com', - PROJECT_URL_PARADEX: 'https://paradex.io', - PROJECT_URL_DYDX: 'https://dydx.exchange', - PROJECT_URL_MELONPORT: 'https://melonport.com', - PROJECT_URL_DISTRICT_0X: 'https://district0x.io', - PROJECT_URL_DHARMA: 'https://dharma.io', - PROJECT_URL_LENDROID: 'https://lendroid.com', - PROJECT_URL_MAKER: 'https://makerdao.com', - PROJECT_URL_ARAGON: 'https://aragon.one', - PROJECT_URL_BLOCKNET: 'https://blocknet.co', - PROJECT_URL_0CEAN: 'http://the0cean.com', - PROJECT_URL_STATUS: 'https://status.im', - PROJECT_URL_AUGUR: 'https://augur.net', - PROJECT_URL_AUCTUS: 'https://auctus.org', - PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', - URL_ANGELLIST: 'https://angel.co/0xproject/jobs', - URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', - URL_BITLY_API: 'https://api-ssl.bitly.com', - URL_BLOG: 'https://blog.0xproject.com/latest', - URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', - URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', - URL_ETHER_FAUCET: 'https://faucet.0xproject.com', - URL_GITHUB_ORG: 'https://github.com/0xProject', - URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', - URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', - URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', - URL_PARITY_CHROME_STORE: - 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', - URL_REDDIT: 'https://reddit.com/r/0xproject', - URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', - URL_TWITTER: 'https://twitter.com/0xproject', - URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', - URL_WEB3_DECODED_LOG_ENTRY_EVENT: - 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', - URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', - URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', - URL_WETH_IO: 'https://weth.io/', - URL_ZEROEX_CHAT: 'https://chat.0xproject.com', + DECIMAL_PLACES_ETH: 18, + DECIMAL_PLACES_ZRX: 18, + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { + 1: 4145578, + 42: 3117574, + 50: 0, + } as { [networkId: number]: number }, + HOME_SCROLL_DURATION_MS: 500, + HTTP_NO_CONTENT_STATUS_CODE: 204, + LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER: 'didAcceptPortalDisclaimer', + LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE: 'hasDismissedWethNotice', + MAKER_FEE: new BigNumber(0), + MAINNET_NAME: 'Main network', + MINT_AMOUNT: new BigNumber('100000000000000000000'), + NETWORK_ID_MAINNET: 1, + NETWORK_ID_TESTNET: 42, + NETWORK_ID_TESTRPC: 50, + NETWORK_NAME_BY_ID: { + 1: Networks.mainnet, + 3: Networks.ropsten, + 4: Networks.rinkeby, + 42: Networks.kovan, + } as { [symbol: number]: string }, + NETWORK_ID_BY_NAME: { + [Networks.mainnet]: 1, + [Networks.ropsten]: 3, + [Networks.rinkeby]: 4, + [Networks.kovan]: 42, + } as { [networkName: string]: number }, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + PROVIDER_NAME_LEDGER: 'Ledger', + PROVIDER_NAME_METAMASK: 'Metamask', + PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', + PROVIDER_NAME_GENERIC: 'Injected Web3', + PROVIDER_NAME_PUBLIC: '0x Public', + ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', + SUCCESS_STATUS: 200, + UNAVAILABLE_STATUS: 503, + TAKER_FEE: new BigNumber(0), + TESTNET_NAME: 'Kovan', + TYPES_SECTION_NAME: 'types', + PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', + PROJECT_URL_RADAR_RELAY: 'https://radarrelay.com', + PROJECT_URL_PARADEX: 'https://paradex.io', + PROJECT_URL_DYDX: 'https://dydx.exchange', + PROJECT_URL_MELONPORT: 'https://melonport.com', + PROJECT_URL_DISTRICT_0X: 'https://district0x.io', + PROJECT_URL_DHARMA: 'https://dharma.io', + PROJECT_URL_LENDROID: 'https://lendroid.com', + PROJECT_URL_MAKER: 'https://makerdao.com', + PROJECT_URL_ARAGON: 'https://aragon.one', + PROJECT_URL_BLOCKNET: 'https://blocknet.co', + PROJECT_URL_0CEAN: 'http://the0cean.com', + PROJECT_URL_STATUS: 'https://status.im', + PROJECT_URL_AUGUR: 'https://augur.net', + PROJECT_URL_AUCTUS: 'https://auctus.org', + PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', + URL_ANGELLIST: 'https://angel.co/0xproject/jobs', + URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', + URL_BITLY_API: 'https://api-ssl.bitly.com', + URL_BLOG: 'https://blog.0xproject.com/latest', + URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', + URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', + URL_ETHER_FAUCET: 'https://faucet.0xproject.com', + URL_GITHUB_ORG: 'https://github.com/0xProject', + URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', + URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', + URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', + URL_PARITY_CHROME_STORE: + 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', + URL_REDDIT: 'https://reddit.com/r/0xproject', + URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', + URL_TWITTER: 'https://twitter.com/0xproject', + URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + URL_WEB3_DECODED_LOG_ENTRY_EVENT: + 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', + URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + URL_WETH_IO: 'https://weth.io/', + URL_ZEROEX_CHAT: 'https://chat.0xproject.com', }; diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 18bf276b4..1f5f75ee2 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -5,47 +5,47 @@ import { utils } from 'ts/utils/utils'; import convert = require('xml-js'); export const docUtils = { - async getVersionToFileNameAsync(s3DocJsonRoot: string): Promise<VersionToFileName> { - const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); - const versionToFileName: VersionToFileName = {}; - _.each(versionFileNames, fileName => { - const [version] = findVersions(fileName); - versionToFileName[version] = fileName; - }); - return versionToFileName; - }, - async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> { - const response = await fetch(s3DocJsonRoot); - if (response.status !== 200) { - // TODO: Show the user an error message when the docs fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); - throw new Error(errMsg); - } - const responseXML = await response.text(); - const responseJSONString = convert.xml2json(responseXML, { - compact: true, - }); - const responseObj = JSON.parse(responseJSONString); - const fileObjs: S3FileObject[] = _.isArray(responseObj.ListBucketResult.Contents) - ? (responseObj.ListBucketResult.Contents as S3FileObject[]) - : [responseObj.ListBucketResult.Contents]; + async getVersionToFileNameAsync(s3DocJsonRoot: string): Promise<VersionToFileName> { + const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); + const versionToFileName: VersionToFileName = {}; + _.each(versionFileNames, fileName => { + const [version] = findVersions(fileName); + versionToFileName[version] = fileName; + }); + return versionToFileName; + }, + async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> { + const response = await fetch(s3DocJsonRoot); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); + throw new Error(errMsg); + } + const responseXML = await response.text(); + const responseJSONString = convert.xml2json(responseXML, { + compact: true, + }); + const responseObj = JSON.parse(responseJSONString); + const fileObjs: S3FileObject[] = _.isArray(responseObj.ListBucketResult.Contents) + ? (responseObj.ListBucketResult.Contents as S3FileObject[]) + : [responseObj.ListBucketResult.Contents]; - const versionFileNames = _.map(fileObjs, fileObj => { - return fileObj.Key._text; - }); - return versionFileNames; - }, - async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { - const endpoint = `${s3DocJsonRoot}/${fileName}`; - const response = await fetch(endpoint); - if (response.status !== 200) { - // TODO: Show the user an error message when the docs fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); - throw new Error(errMsg); - } - const jsonDocObj = await response.json(); - return jsonDocObj; - }, + const versionFileNames = _.map(fileObjs, fileObj => { + return fileObj.Key._text; + }); + return versionFileNames; + }, + async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { + const endpoint = `${s3DocJsonRoot}/${fileName}`; + const response = await fetch(endpoint); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); + throw new Error(errMsg); + } + const jsonDocObj = await response.json(); + return jsonDocObj; + }, }; diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts index f26534d51..5f1d02132 100644 --- a/packages/website/ts/utils/doxity_utils.ts +++ b/packages/website/ts/utils/doxity_utils.ts @@ -1,168 +1,168 @@ import * as _ from 'lodash'; import { - AbiTypes, - DocAgnosticFormat, - DocSection, - DoxityAbiDoc, - DoxityContractObj, - DoxityDocObj, - DoxityInput, - EventArg, - Parameter, - Property, - SolidityMethod, - Type, - TypeDocTypes, + AbiTypes, + DocAgnosticFormat, + DocSection, + DoxityAbiDoc, + DoxityContractObj, + DoxityDocObj, + DoxityInput, + EventArg, + Parameter, + Property, + SolidityMethod, + Type, + TypeDocTypes, } from 'ts/types'; export const doxityUtils = { - convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { - const docAgnosticFormat: DocAgnosticFormat = {}; - _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { - const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { - return abiDoc.type === AbiTypes.Constructor; - }); - const constructors = []; - if (!_.isUndefined(doxityConstructor)) { - const constructor = { - isConstructor: true, - name: doxityContractObj.name, - comment: doxityConstructor.details, - returnComment: doxityConstructor.return, - callPath: '', - parameters: this._convertParameters(doxityConstructor.inputs), - returnType: this._convertType(doxityContractObj.name), - }; - constructors.push(constructor); - } + convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { + const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return abiDoc.type === AbiTypes.Constructor; + }); + const constructors = []; + if (!_.isUndefined(doxityConstructor)) { + const constructor = { + isConstructor: true, + name: doxityContractObj.name, + comment: doxityConstructor.details, + returnComment: doxityConstructor.return, + callPath: '', + parameters: this._convertParameters(doxityConstructor.inputs), + returnType: this._convertType(doxityContractObj.name), + }; + constructors.push(constructor); + } - const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => { - return this._isMethod(abiDoc); - }, - ); - const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>( - doxityMethods, - (doxityMethod: DoxityAbiDoc) => { - // We assume that none of our functions returns more then a single value - const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs[0] : undefined; - const returnTypeIfExists = !_.isUndefined(outputIfExists) - ? this._convertType(outputIfExists.type) - : undefined; - // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken - const callPath = - contractName !== 'ZRXToken' - ? `${contractName[0].toLowerCase()}${contractName.slice(1)}.` - : `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; - const method = { - isConstructor: false, - isConstant: doxityMethod.constant, - isPayable: doxityMethod.payable, - name: doxityMethod.name, - comment: doxityMethod.details, - returnComment: doxityMethod.return, - callPath, - parameters: this._convertParameters(doxityMethod.inputs), - returnType: returnTypeIfExists, - }; - return method; - }, - ); + const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => { + return this._isMethod(abiDoc); + }, + ); + const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>( + doxityMethods, + (doxityMethod: DoxityAbiDoc) => { + // We assume that none of our functions returns more then a single value + const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs[0] : undefined; + const returnTypeIfExists = !_.isUndefined(outputIfExists) + ? this._convertType(outputIfExists.type) + : undefined; + // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken + const callPath = + contractName !== 'ZRXToken' + ? `${contractName[0].toLowerCase()}${contractName.slice(1)}.` + : `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; + const method = { + isConstructor: false, + isConstant: doxityMethod.constant, + isPayable: doxityMethod.payable, + name: doxityMethod.name, + comment: doxityMethod.details, + returnComment: doxityMethod.return, + callPath, + parameters: this._convertParameters(doxityMethod.inputs), + returnType: returnTypeIfExists, + }; + return method; + }, + ); - const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => { - return this._isProperty(abiDoc); - }, - ); - const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => { - // We assume that none of our functions return more then a single return value - let typeName = doxityProperty.outputs[0].type; - if (!_.isEmpty(doxityProperty.inputs)) { - // Properties never have more then a single input - typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; - } - const property = { - name: doxityProperty.name, - type: this._convertType(typeName), - comment: doxityProperty.details, - }; - return property; - }); + const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => { + return this._isProperty(abiDoc); + }, + ); + const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => { + // We assume that none of our functions return more then a single return value + let typeName = doxityProperty.outputs[0].type; + if (!_.isEmpty(doxityProperty.inputs)) { + // Properties never have more then a single input + typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; + } + const property = { + name: doxityProperty.name, + type: this._convertType(typeName), + comment: doxityProperty.details, + }; + return property; + }); - const doxityEvents = _.filter( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, - ); - const events = _.map(doxityEvents, doxityEvent => { - const event = { - name: doxityEvent.name, - eventArgs: this._convertEventArgs(doxityEvent.inputs), - }; - return event; - }); + const doxityEvents = _.filter( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, + ); + const events = _.map(doxityEvents, doxityEvent => { + const event = { + name: doxityEvent.name, + eventArgs: this._convertEventArgs(doxityEvent.inputs), + }; + return event; + }); - const docSection: DocSection = { - comment: doxityContractObj.title, - constructors, - methods, - properties, - types: [], - events, - }; - docAgnosticFormat[contractName] = docSection; - }); - return docAgnosticFormat; - }, - _convertParameters(inputs: DoxityInput[]): Parameter[] { - const parameters = _.map(inputs, input => { - const parameter = { - name: input.name, - comment: input.description, - isOptional: false, - type: this._convertType(input.type), - }; - return parameter; - }); - return parameters; - }, - _convertType(typeName: string): Type { - const type = { - name: typeName, - typeDocType: TypeDocTypes.Intrinsic, - }; - return type; - }, - _isMethod(abiDoc: DoxityAbiDoc) { - if (abiDoc.type !== AbiTypes.Function) { - return false; - } - const hasInputs = !_.isEmpty(abiDoc.inputs); - const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); - const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); - const isMethod = hasNamedOutputIfExists && !isNameAllCaps; - return isMethod; - }, - _isProperty(abiDoc: DoxityAbiDoc) { - if (abiDoc.type !== AbiTypes.Function) { - return false; - } - const hasInputs = !_.isEmpty(abiDoc.inputs); - const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); - const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); - const isProperty = !hasNamedOutputIfExists || isNameAllCaps; - return isProperty; - }, - _convertEventArgs(inputs: DoxityInput[]): EventArg[] { - const eventArgs = _.map(inputs, input => { - const eventArg = { - isIndexed: input.indexed, - name: input.name, - type: this._convertType(input.type), - }; - return eventArg; - }); - return eventArgs; - }, + const docSection: DocSection = { + comment: doxityContractObj.title, + constructors, + methods, + properties, + types: [], + events, + }; + docAgnosticFormat[contractName] = docSection; + }); + return docAgnosticFormat; + }, + _convertParameters(inputs: DoxityInput[]): Parameter[] { + const parameters = _.map(inputs, input => { + const parameter = { + name: input.name, + comment: input.description, + isOptional: false, + type: this._convertType(input.type), + }; + return parameter; + }); + return parameters; + }, + _convertType(typeName: string): Type { + const type = { + name: typeName, + typeDocType: TypeDocTypes.Intrinsic, + }; + return type; + }, + _isMethod(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isMethod = hasNamedOutputIfExists && !isNameAllCaps; + return isMethod; + }, + _isProperty(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isProperty = !hasNamedOutputIfExists || isNameAllCaps; + return isProperty; + }, + _convertEventArgs(inputs: DoxityInput[]): EventArg[] { + const eventArgs = _.map(inputs, input => { + const eventArg = { + isIndexed: input.indexed, + name: input.name, + type: this._convertType(input.type), + }; + return eventArg; + }); + return eventArgs; + }, }; diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts index 08d99e405..0bd247c5b 100644 --- a/packages/website/ts/utils/error_reporter.ts +++ b/packages/website/ts/utils/error_reporter.ts @@ -6,47 +6,47 @@ import { utils } from 'ts/utils/utils'; // Suggested way to include Rollbar with Webpack // https://github.com/rollbar/rollbar.js/tree/master/examples/webpack const rollbarConfig = { - accessToken: constants.ROLLBAR_ACCESS_TOKEN, - captureUncaught: true, - captureUnhandledRejections: true, - itemsPerMinute: 10, - maxItems: 500, - payload: { - environment: configs.ENVIRONMENT, - }, - uncaughtErrorLevel: 'error', - hostWhiteList: [configs.DOMAIN_PRODUCTION, configs.DOMAIN_STAGING], - ignoredMessages: [ - // Errors from the third-party scripts - 'Script error', - // Network errors or ad-blockers - 'TypeError: Failed to fetch', - 'Exchange has not been deployed to detected network (network/artifact mismatch)', - // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE - "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", - // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging - 'SecurityError (DOM Exception 18)', - ], + accessToken: constants.ROLLBAR_ACCESS_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: configs.ENVIRONMENT, + }, + uncaughtErrorLevel: 'error', + hostWhiteList: [configs.DOMAIN_PRODUCTION, configs.DOMAIN_STAGING], + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], }; import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js'); const rollbar = Rollbar.init(rollbarConfig); export const errorReporter = { - async reportAsync(err: Error): Promise<any> { - if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { - return; // Let's not log development errors to rollbar - } + async reportAsync(err: Error): Promise<any> { + if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { + return; // Let's not log development errors to rollbar + } - return new Promise((resolve, reject) => { - rollbar.error(err, (rollbarErr: Error) => { - if (rollbarErr) { - utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); - // We never want to reject and cause the app to throw because of rollbar - resolve(); - } else { - resolve(); - } - }); - }); - }, + return new Promise((resolve, reject) => { + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + // We never want to reject and cause the app to throw because of rollbar + resolve(); + } else { + resolve(); + } + }); + }); + }, }; diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts index 565d1ae4f..d73e80606 100644 --- a/packages/website/ts/utils/mui_theme.ts +++ b/packages/website/ts/utils/mui_theme.ts @@ -2,34 +2,34 @@ import { getMuiTheme } from 'material-ui/styles'; import { colors } from 'ts/utils/colors'; export const muiTheme = getMuiTheme({ - appBar: { - height: 45, - color: colors.white, - textColor: colors.black, - }, - palette: { - pickerHeaderColor: colors.lightBlue, - primary1Color: colors.lightBlue, - primary2Color: colors.lightBlue, - textColor: colors.grey700, - }, - datePicker: { - color: colors.grey700, - textColor: colors.white, - calendarTextColor: colors.white, - selectColor: colors.darkestGrey, - selectTextColor: colors.white, - }, - timePicker: { - color: colors.grey700, - textColor: colors.white, - accentColor: colors.white, - headerColor: colors.darkestGrey, - selectColor: colors.darkestGrey, - selectTextColor: colors.darkestGrey, - }, - toggle: { - thumbOnColor: colors.limeGreen, - trackOnColor: colors.lightGreen, - }, + appBar: { + height: 45, + color: colors.white, + textColor: colors.black, + }, + palette: { + pickerHeaderColor: colors.lightBlue, + primary1Color: colors.lightBlue, + primary2Color: colors.lightBlue, + textColor: colors.grey700, + }, + datePicker: { + color: colors.grey700, + textColor: colors.white, + calendarTextColor: colors.white, + selectColor: colors.darkestGrey, + selectTextColor: colors.white, + }, + timePicker: { + color: colors.grey700, + textColor: colors.white, + accentColor: colors.white, + headerColor: colors.darkestGrey, + selectColor: colors.darkestGrey, + selectTextColor: colors.darkestGrey, + }, + toggle: { + thumbOnColor: colors.limeGreen, + trackOnColor: colors.lightGreen, + }, }); diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts index b0c152891..11ec8da58 100644 --- a/packages/website/ts/utils/typedoc_utils.ts +++ b/packages/website/ts/utils/typedoc_utils.ts @@ -1,365 +1,365 @@ import * as _ from 'lodash'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { - CustomType, - CustomTypeChild, - DocAgnosticFormat, - DocSection, - IndexSignature, - KindString, - Parameter, - Property, - SectionsMap, - Type, - TypeDocNode, - TypeDocType, - TypeParameter, - TypescriptMethod, + CustomType, + CustomTypeChild, + DocAgnosticFormat, + DocSection, + IndexSignature, + KindString, + Parameter, + Property, + SectionsMap, + Type, + TypeDocNode, + TypeDocType, + TypeParameter, + TypescriptMethod, } from 'ts/types'; import { utils } from 'ts/utils/utils'; export const typeDocUtils = { - isType(entity: TypeDocNode): boolean { - return ( - entity.kindString === KindString.Interface || - entity.kindString === KindString.Function || - entity.kindString === KindString.TypeAlias || - entity.kindString === KindString.Variable || - entity.kindString === KindString.Enumeration - ); - }, - isMethod(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Method; - }, - isConstructor(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Constructor; - }, - isProperty(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Property; - }, - isPrivateOrProtectedProperty(propertyName: string): boolean { - return _.startsWith(propertyName, '_'); - }, - getModuleDefinitionBySectionNameIfExists( - versionDocObj: TypeDocNode, - modulePaths: string[], - ): TypeDocNode | undefined { - const modules = versionDocObj.children; - for (const mod of modules) { - if (_.includes(modulePaths, mod.name)) { - const moduleWithName = mod; - return moduleWithName; - } - } - return undefined; - }, - convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat { - const subMenus = _.values(docsInfo.getMenu()); - const orderedSectionNames = _.flatten(subMenus); - const docAgnosticFormat: DocAgnosticFormat = {}; - _.each(orderedSectionNames, sectionName => { - const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName); - if (_.isUndefined(modulePathsIfExists)) { - return; // no-op - } - const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( - typeDocJson, - modulePathsIfExists, - ); - if (_.isUndefined(packageDefinitionIfExists)) { - return; // no-op - } + isType(entity: TypeDocNode): boolean { + return ( + entity.kindString === KindString.Interface || + entity.kindString === KindString.Function || + entity.kindString === KindString.TypeAlias || + entity.kindString === KindString.Variable || + entity.kindString === KindString.Enumeration + ); + }, + isMethod(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Method; + }, + isConstructor(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Constructor; + }, + isProperty(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Property; + }, + isPrivateOrProtectedProperty(propertyName: string): boolean { + return _.startsWith(propertyName, '_'); + }, + getModuleDefinitionBySectionNameIfExists( + versionDocObj: TypeDocNode, + modulePaths: string[], + ): TypeDocNode | undefined { + const modules = versionDocObj.children; + for (const mod of modules) { + if (_.includes(modulePaths, mod.name)) { + const moduleWithName = mod; + return moduleWithName; + } + } + return undefined; + }, + convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat { + const subMenus = _.values(docsInfo.getMenu()); + const orderedSectionNames = _.flatten(subMenus); + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(orderedSectionNames, sectionName => { + const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName); + if (_.isUndefined(modulePathsIfExists)) { + return; // no-op + } + const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( + typeDocJson, + modulePathsIfExists, + ); + if (_.isUndefined(packageDefinitionIfExists)) { + return; // no-op + } - // Since the `types.ts` file is the only file that does not export a module/class but - // instead has each type export itself, we do not need to go down two levels of nesting - // for it. - let entities; - let packageComment = ''; - if (sectionName === docsInfo.sections.types) { - entities = packageDefinitionIfExists.children; - } else { - entities = packageDefinitionIfExists.children[0].children; - const commentObj = packageDefinitionIfExists.children[0].comment; - packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; - } + // Since the `types.ts` file is the only file that does not export a module/class but + // instead has each type export itself, we do not need to go down two levels of nesting + // for it. + let entities; + let packageComment = ''; + if (sectionName === docsInfo.sections.types) { + entities = packageDefinitionIfExists.children; + } else { + entities = packageDefinitionIfExists.children[0].children; + const commentObj = packageDefinitionIfExists.children[0].comment; + packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; + } - const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); - docSection.comment = packageComment; - docAgnosticFormat[sectionName] = docSection; - }); - return docAgnosticFormat; - }, - _convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string) { - const docSection: DocSection = { - comment: '', - constructors: [], - methods: [], - properties: [], - types: [], - }; + const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); + docSection.comment = packageComment; + docAgnosticFormat[sectionName] = docSection; + }); + return docAgnosticFormat; + }, + _convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string) { + const docSection: DocSection = { + comment: '', + constructors: [], + methods: [], + properties: [], + types: [], + }; - let isConstructor; - _.each(entities, entity => { - switch (entity.kindString) { - case KindString.Constructor: - isConstructor = true; - const constructor = typeDocUtils._convertMethod( - entity, - isConstructor, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.constructors.push(constructor); - break; + let isConstructor; + _.each(entities, entity => { + switch (entity.kindString) { + case KindString.Constructor: + isConstructor = true; + const constructor = typeDocUtils._convertMethod( + entity, + isConstructor, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.constructors.push(constructor); + break; - case KindString.Method: - if (entity.flags.isPublic) { - isConstructor = false; - const method = typeDocUtils._convertMethod( - entity, - isConstructor, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.methods.push(method); - } - break; + case KindString.Method: + if (entity.flags.isPublic) { + isConstructor = false; + const method = typeDocUtils._convertMethod( + entity, + isConstructor, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.methods.push(method); + } + break; - case KindString.Property: - if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { - const property = typeDocUtils._convertProperty( - entity, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.properties.push(property); - } - break; + case KindString.Property: + if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { + const property = typeDocUtils._convertProperty( + entity, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.properties.push(property); + } + break; - case KindString.Interface: - case KindString.Function: - case KindString.Variable: - case KindString.Enumeration: - case KindString.TypeAlias: - if (docsInfo.isPublicType(entity.name)) { - const customType = typeDocUtils._convertCustomType( - entity, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.types.push(customType); - } - break; + case KindString.Interface: + case KindString.Function: + case KindString.Variable: + case KindString.Enumeration: + case KindString.TypeAlias: + if (docsInfo.isPublicType(entity.name)) { + const customType = typeDocUtils._convertCustomType( + entity, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.types.push(customType); + } + break; - default: - throw utils.spawnSwitchErr('kindString', entity.kindString); - } - }); - return docSection; - }, - _convertCustomType( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): CustomType { - const typeIfExists = !_.isUndefined(entity.type) - ? typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName) - : undefined; - const isConstructor = false; - const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) - : undefined; - const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) - ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, subPackageName) - : undefined; - const commentIfExists = - !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) - ? entity.comment.shortText - : undefined; + default: + throw utils.spawnSwitchErr('kindString', entity.kindString); + } + }); + return docSection; + }, + _convertCustomType( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): CustomType { + const typeIfExists = !_.isUndefined(entity.type) + ? typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName) + : undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + : undefined; + const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) + ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, subPackageName) + : undefined; + const commentIfExists = + !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) + ? entity.comment.shortText + : undefined; - const childrenIfExist = !_.isUndefined(entity.children) - ? _.map(entity.children, (child: TypeDocNode) => { - const childTypeIfExists = !_.isUndefined(child.type) - ? typeDocUtils._convertType(child.type, sections, sectionName, subPackageName) - : undefined; - const c: CustomTypeChild = { - name: child.name, - type: childTypeIfExists, - defaultValue: child.defaultValue, - }; - return c; - }) - : undefined; + const childrenIfExist = !_.isUndefined(entity.children) + ? _.map(entity.children, (child: TypeDocNode) => { + const childTypeIfExists = !_.isUndefined(child.type) + ? typeDocUtils._convertType(child.type, sections, sectionName, subPackageName) + : undefined; + const c: CustomTypeChild = { + name: child.name, + type: childTypeIfExists, + defaultValue: child.defaultValue, + }; + return c; + }) + : undefined; - const customType = { - name: entity.name, - kindString: entity.kindString, - type: typeIfExists, - method: methodIfExists, - indexSignature: indexSignatureIfExists, - defaultValue: entity.defaultValue, - comment: commentIfExists, - children: childrenIfExist, - }; - return customType; - }, - _convertIndexSignature( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): IndexSignature { - const key = entity.parameters[0]; - const indexSignature = { - keyName: key.name, - keyType: typeDocUtils._convertType(key.type, sections, sectionName, subPackageName), - valueName: entity.type.name, - }; - return indexSignature; - }, - _convertProperty( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Property { - const source = entity.sources[0]; - const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; - const property = { - name: entity.name, - type: typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName), - source: { - fileName: source.fileName, - line: source.line, - }, - comment: commentIfExists, - }; - return property; - }, - _convertMethod( - entity: TypeDocNode, - isConstructor: boolean, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): TypescriptMethod { - const signature = entity.signatures[0]; - const source = entity.sources[0]; - const hasComment = !_.isUndefined(signature.comment); - const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; + const customType = { + name: entity.name, + kindString: entity.kindString, + type: typeIfExists, + method: methodIfExists, + indexSignature: indexSignatureIfExists, + defaultValue: entity.defaultValue, + comment: commentIfExists, + children: childrenIfExist, + }; + return customType; + }, + _convertIndexSignature( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): IndexSignature { + const key = entity.parameters[0]; + const indexSignature = { + keyName: key.name, + keyType: typeDocUtils._convertType(key.type, sections, sectionName, subPackageName), + valueName: entity.type.name, + }; + return indexSignature; + }, + _convertProperty( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): Property { + const source = entity.sources[0]; + const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; + const property = { + name: entity.name, + type: typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName), + source: { + fileName: source.fileName, + line: source.line, + }, + comment: commentIfExists, + }; + return property; + }, + _convertMethod( + entity: TypeDocNode, + isConstructor: boolean, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): TypescriptMethod { + const signature = entity.signatures[0]; + const source = entity.sources[0]; + const hasComment = !_.isUndefined(signature.comment); + const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; - // HACK: we use the fact that the sectionName is the same as the property name at the top-level - // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. - let callPath; - if (isConstructor || entity.name === '__type') { - callPath = ''; - } else if (subPackageName === '0x.js') { - const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; - callPath = - !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx - ? `${topLevelInterface}${sectionName}.` - : topLevelInterface; - } else { - callPath = `${sectionName}.`; - } + // HACK: we use the fact that the sectionName is the same as the property name at the top-level + // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. + let callPath; + if (isConstructor || entity.name === '__type') { + callPath = ''; + } else if (subPackageName === '0x.js') { + const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; + callPath = + !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx + ? `${topLevelInterface}${sectionName}.` + : topLevelInterface; + } else { + callPath = `${sectionName}.`; + } - const parameters = _.map(signature.parameters, param => { - return typeDocUtils._convertParameter(param, sections, sectionName, subPackageName); - }); - const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, subPackageName); - const typeParameter = _.isUndefined(signature.typeParameter) - ? undefined - : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, subPackageName); + const parameters = _.map(signature.parameters, param => { + return typeDocUtils._convertParameter(param, sections, sectionName, subPackageName); + }); + const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, subPackageName); + const typeParameter = _.isUndefined(signature.typeParameter) + ? undefined + : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, subPackageName); - const method = { - isConstructor, - isStatic, - name: signature.name, - comment: hasComment ? signature.comment.shortText : undefined, - returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, - source: { - fileName: source.fileName, - line: source.line, - }, - callPath, - parameters, - returnType, - typeParameter, - }; - return method; - }, - _convertTypeParameter( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): TypeParameter { - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); - const parameter = { - name: entity.name, - type, - }; - return parameter; - }, - _convertParameter( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Parameter { - let comment = '<No comment>'; - if (entity.comment && entity.comment.shortText) { - comment = entity.comment.shortText; - } else if (entity.comment && entity.comment.text) { - comment = entity.comment.text; - } + const method = { + isConstructor, + isStatic, + name: signature.name, + comment: hasComment ? signature.comment.shortText : undefined, + returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, + source: { + fileName: source.fileName, + line: source.line, + }, + callPath, + parameters, + returnType, + typeParameter, + }; + return method; + }, + _convertTypeParameter( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): TypeParameter { + const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const parameter = { + name: entity.name, + type, + }; + return parameter; + }, + _convertParameter( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): Parameter { + let comment = '<No comment>'; + if (entity.comment && entity.comment.shortText) { + comment = entity.comment.shortText; + } else if (entity.comment && entity.comment.text) { + comment = entity.comment.text; + } - const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; + const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); - const parameter = { - name: entity.name, - comment, - isOptional, - type, - }; - return parameter; - }, - _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, subPackageName: string): Type { - const typeArguments = _.map(entity.typeArguments, typeArgument => { - return typeDocUtils._convertType(typeArgument, sections, sectionName, subPackageName); - }); - const types = _.map(entity.types, t => { - return typeDocUtils._convertType(t, sections, sectionName, subPackageName); - }); + const parameter = { + name: entity.name, + comment, + isOptional, + type, + }; + return parameter; + }, + _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, subPackageName: string): Type { + const typeArguments = _.map(entity.typeArguments, typeArgument => { + return typeDocUtils._convertType(typeArgument, sections, sectionName, subPackageName); + }); + const types = _.map(entity.types, t => { + return typeDocUtils._convertType(t, sections, sectionName, subPackageName); + }); - const isConstructor = false; - const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) - : undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + : undefined; - const elementTypeIfExists = !_.isUndefined(entity.elementType) - ? { - name: entity.elementType.name, - typeDocType: entity.elementType.type, - } - : undefined; + const elementTypeIfExists = !_.isUndefined(entity.elementType) + ? { + name: entity.elementType.name, + typeDocType: entity.elementType.type, + } + : undefined; - const type = { - name: entity.name, - value: entity.value, - typeDocType: entity.type, - typeArguments, - elementType: elementTypeIfExists, - types, - method: methodIfExists, - }; - return type; - }, + const type = { + name: entity.name, + value: entity.value, + typeDocType: entity.type, + typeArguments, + elementType: elementTypeIfExists, + types, + method: methodIfExists, + }; + return type; + }, }; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index ea5b689ae..13a6d6ae2 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -5,15 +5,15 @@ import isMobile = require('is-mobile'); import * as _ from 'lodash'; import * as moment from 'moment'; import { - EtherscanLinkSuffixes, - Networks, - Order, - ScreenWidths, - Side, - SideToAssetToken, - SignatureData, - Token, - TokenByAddress, + EtherscanLinkSuffixes, + Networks, + Order, + ScreenWidths, + Side, + SideToAssetToken, + SignatureData, + Token, + TokenByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -23,257 +23,257 @@ const LG_MIN_EM = 64; const MD_MIN_EM = 52; export const utils = { - assert(condition: boolean, message: string) { - if (!condition) { - throw new Error(message); - } - }, - spawnSwitchErr(name: string, value: any) { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - isNumeric(n: string) { - return !isNaN(parseFloat(n)) && isFinite(Number(n)); - }, - // This default unix timestamp is used for orders where the user does not specify an expiry date. - // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for - // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide - // with actual values a user would select. - initialOrderExpiryUnixTimestampSec(): BigNumber { - const m = moment('2050-01-01'); - return new BigNumber(m.unix()); - }, - convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { - const finalMoment = date; - if (!_.isUndefined(time)) { - finalMoment.hours(time.hours()); - finalMoment.minutes(time.minutes()); - } - return new BigNumber(finalMoment.unix()); - }, - convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { - return moment.unix(unixTimestampSec.toNumber()); - }, - convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { - const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); - const formattedDate: string = m.format('h:MMa MMMM D YYYY'); - return formattedDate; - }, - generateOrder( - networkId: number, - exchangeContract: string, - sideToAssetToken: SideToAssetToken, - orderExpiryTimestamp: BigNumber, - orderTakerAddress: string, - orderMakerAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - feeRecipient: string, - signatureData: SignatureData, - tokenByAddress: TokenByAddress, - orderSalt: BigNumber, - ): Order { - const makerToken = tokenByAddress[sideToAssetToken[Side.Deposit].address]; - const takerToken = tokenByAddress[sideToAssetToken[Side.Receive].address]; - const order = { - maker: { - address: orderMakerAddress, - token: { - name: makerToken.name, - symbol: makerToken.symbol, - decimals: makerToken.decimals, - address: makerToken.address, - }, - amount: sideToAssetToken[Side.Deposit].amount.toString(), - feeAmount: makerFee.toString(), - }, - taker: { - address: orderTakerAddress, - token: { - name: takerToken.name, - symbol: takerToken.symbol, - decimals: takerToken.decimals, - address: takerToken.address, - }, - amount: sideToAssetToken[Side.Receive].amount.toString(), - feeAmount: takerFee.toString(), - }, - expiration: orderExpiryTimestamp.toString(), - feeRecipient, - salt: orderSalt.toString(), - signature: signatureData, - exchangeContract, - networkId, - }; - return order; - }, - consoleLog(message: string) { - /* tslint:disable */ - console.log(message); - /* tslint:enable */ - }, - async sleepAsync(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - }, - deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { - return deepEqual(actual, expected, opts); - }, - getColSize(items: number) { - const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid - const colSize = bassCssGridSize / items; - if (!_.isInteger(colSize)) { - throw new Error(`Number of cols must be divisible by ${bassCssGridSize}`); - } - return colSize; - }, - getScreenWidth() { - const documentEl = document.documentElement; - const body = document.getElementsByTagName('body')[0]; - const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; - const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); - const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); + assert(condition: boolean, message: string) { + if (!condition) { + throw new Error(message); + } + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + isNumeric(n: string) { + return !isNaN(parseFloat(n)) && isFinite(Number(n)); + }, + // This default unix timestamp is used for orders where the user does not specify an expiry date. + // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for + // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide + // with actual values a user would select. + initialOrderExpiryUnixTimestampSec(): BigNumber { + const m = moment('2050-01-01'); + return new BigNumber(m.unix()); + }, + convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { + const finalMoment = date; + if (!_.isUndefined(time)) { + finalMoment.hours(time.hours()); + finalMoment.minutes(time.minutes()); + } + return new BigNumber(finalMoment.unix()); + }, + convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { + return moment.unix(unixTimestampSec.toNumber()); + }, + convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { + const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); + const formattedDate: string = m.format('h:MMa MMMM D YYYY'); + return formattedDate; + }, + generateOrder( + networkId: number, + exchangeContract: string, + sideToAssetToken: SideToAssetToken, + orderExpiryTimestamp: BigNumber, + orderTakerAddress: string, + orderMakerAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + feeRecipient: string, + signatureData: SignatureData, + tokenByAddress: TokenByAddress, + orderSalt: BigNumber, + ): Order { + const makerToken = tokenByAddress[sideToAssetToken[Side.Deposit].address]; + const takerToken = tokenByAddress[sideToAssetToken[Side.Receive].address]; + const order = { + maker: { + address: orderMakerAddress, + token: { + name: makerToken.name, + symbol: makerToken.symbol, + decimals: makerToken.decimals, + address: makerToken.address, + }, + amount: sideToAssetToken[Side.Deposit].amount.toString(), + feeAmount: makerFee.toString(), + }, + taker: { + address: orderTakerAddress, + token: { + name: takerToken.name, + symbol: takerToken.symbol, + decimals: takerToken.decimals, + address: takerToken.address, + }, + amount: sideToAssetToken[Side.Receive].amount.toString(), + feeAmount: takerFee.toString(), + }, + expiration: orderExpiryTimestamp.toString(), + feeRecipient, + salt: orderSalt.toString(), + signature: signatureData, + exchangeContract, + networkId, + }; + return order; + }, + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + async sleepAsync(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { + return deepEqual(actual, expected, opts); + }, + getColSize(items: number) { + const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid + const colSize = bassCssGridSize / items; + if (!_.isInteger(colSize)) { + throw new Error(`Number of cols must be divisible by ${bassCssGridSize}`); + } + return colSize; + }, + getScreenWidth() { + const documentEl = document.documentElement; + const body = document.getElementsByTagName('body')[0]; + const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; + const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); + const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); - // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS - // class prefixes. Do not edit these. - if (widthInEm > LG_MIN_EM) { - return ScreenWidths.Lg; - } else if (widthInEm > MD_MIN_EM) { - return ScreenWidths.Md; - } else { - return ScreenWidths.Sm; - } - }, - isUserOnMobile(): boolean { - const isUserOnMobile = isMobile(); - return isUserOnMobile; - }, - getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { - const networkName = constants.NETWORK_NAME_BY_ID[networkId]; - if (_.isUndefined(networkName)) { - return undefined; - } - const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; - return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; - }, - setUrlHash(anchorId: string) { - window.location.hash = anchorId; - }, - async isU2FSupportedAsync(): Promise<boolean> { - const w = window as any; - return new Promise((resolve: (isSupported: boolean) => void) => { - if (w.u2f && !w.u2f.getApiVersion) { - // u2f object was found (Firefox with extension) - resolve(true); - } else { - // u2f object was not found. Using Google polyfill - // HACK: u2f.getApiVersion will simply not return a version if the - // U2F call fails for any reason. Because of this, we set a hard 3sec - // timeout to the request on our end. - const getApiVersionTimeoutMs = 3000; - const intervalId = setTimeout(() => { - resolve(false); - }, getApiVersionTimeoutMs); - u2f.getApiVersion((version: number) => { - clearTimeout(intervalId); - resolve(true); - }); - } - }); - }, - // This checks the error message returned from an injected Web3 instance on the page - // after a user was prompted to sign a message or send a transaction and decided to - // reject the request. - didUserDenyWeb3Request(errMsg: string) { - const metamaskDenialErrMsg = 'User denied message'; - const paritySignerDenialErrMsg = 'Request has been rejected'; - const ledgerDenialErrMsg = 'Invalid status 6985'; - const isUserDeniedErrMsg = - _.includes(errMsg, metamaskDenialErrMsg) || - _.includes(errMsg, paritySignerDenialErrMsg) || - _.includes(errMsg, ledgerDenialErrMsg); - return isUserDeniedErrMsg; - }, - getCurrentEnvironment() { - switch (location.host) { - case configs.DOMAIN_DEVELOPMENT: - return 'development'; - case configs.DOMAIN_STAGING: - return 'staging'; - case configs.DOMAIN_PRODUCTION: - return 'production'; - default: - return 'production'; - } - }, - getIdFromName(name: string) { - const id = name.replace(/ /g, '-'); - return id; - }, - getAddressBeginAndEnd(address: string): string { - const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 - return truncatedAddress; - }, - hasUniqueNameAndSymbol(tokens: Token[], token: Token) { - if (token.isRegistered) { - return true; // Since it's registered, it is the canonical token - } - const registeredTokens = _.filter(tokens, t => t.isRegistered); - const tokenWithSameNameIfExists = _.find(registeredTokens, { - name: token.name, - }); - const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); - const tokenWithSameSymbolIfExists = _.find(registeredTokens, { - name: token.symbol, - }); - const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); - return isUniqueName && isUniqueSymbol; - }, - zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { - const ZeroExErrorToHumanReadableError: { [error: string]: string } = { - [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', - [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', - [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', - [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', - [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', - [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', - [ZeroExError.UnhandledError]: 'Unhandled error occured', - [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', - [ZeroExError.InvalidSignature]: 'Order signature is not valid', - [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', - [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', - [ZeroExError.OutOfGas]: 'Transaction ran out of gas', - [ZeroExError.NoNetworkId]: 'No network id detected', - }; - const exchangeContractErrorToHumanReadableError: { - [error: string]: string; - } = { - [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelAmountZero]: "Order cancel amount can't be 0", - [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillAmountZero]: "Order fill amount can't be 0", - [ExchangeContractErrs.OrderRemainingFillAmountZero]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', - [ExchangeContractErrs.InsufficientTakerBalance]: - 'Taker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientTakerAllowance]: - 'Taker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientMakerBalance]: - 'Maker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientMakerAllowance]: - 'Maker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientTakerFeeAllowance]: - 'Taker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeAllowance]: - 'Maker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: `This order can only be filled by ${takerAddress}`, - [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', - }; - const humanReadableErrorMsg = - exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; - return humanReadableErrorMsg; - }, + // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS + // class prefixes. Do not edit these. + if (widthInEm > LG_MIN_EM) { + return ScreenWidths.Lg; + } else if (widthInEm > MD_MIN_EM) { + return ScreenWidths.Md; + } else { + return ScreenWidths.Sm; + } + }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, + getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + async isU2FSupportedAsync(): Promise<boolean> { + const w = window as any; + return new Promise((resolve: (isSupported: boolean) => void) => { + if (w.u2f && !w.u2f.getApiVersion) { + // u2f object was found (Firefox with extension) + resolve(true); + } else { + // u2f object was not found. Using Google polyfill + // HACK: u2f.getApiVersion will simply not return a version if the + // U2F call fails for any reason. Because of this, we set a hard 3sec + // timeout to the request on our end. + const getApiVersionTimeoutMs = 3000; + const intervalId = setTimeout(() => { + resolve(false); + }, getApiVersionTimeoutMs); + u2f.getApiVersion((version: number) => { + clearTimeout(intervalId); + resolve(true); + }); + } + }); + }, + // This checks the error message returned from an injected Web3 instance on the page + // after a user was prompted to sign a message or send a transaction and decided to + // reject the request. + didUserDenyWeb3Request(errMsg: string) { + const metamaskDenialErrMsg = 'User denied message'; + const paritySignerDenialErrMsg = 'Request has been rejected'; + const ledgerDenialErrMsg = 'Invalid status 6985'; + const isUserDeniedErrMsg = + _.includes(errMsg, metamaskDenialErrMsg) || + _.includes(errMsg, paritySignerDenialErrMsg) || + _.includes(errMsg, ledgerDenialErrMsg); + return isUserDeniedErrMsg; + }, + getCurrentEnvironment() { + switch (location.host) { + case configs.DOMAIN_DEVELOPMENT: + return 'development'; + case configs.DOMAIN_STAGING: + return 'staging'; + case configs.DOMAIN_PRODUCTION: + return 'production'; + default: + return 'production'; + } + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getAddressBeginAndEnd(address: string): string { + const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 + return truncatedAddress; + }, + hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + if (token.isRegistered) { + return true; // Since it's registered, it is the canonical token + } + const registeredTokens = _.filter(tokens, t => t.isRegistered); + const tokenWithSameNameIfExists = _.find(registeredTokens, { + name: token.name, + }); + const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); + const tokenWithSameSymbolIfExists = _.find(registeredTokens, { + name: token.symbol, + }); + const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); + return isUniqueName && isUniqueSymbol; + }, + zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { + const ZeroExErrorToHumanReadableError: { [error: string]: string } = { + [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', + [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', + [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', + [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', + [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', + [ZeroExError.UnhandledError]: 'Unhandled error occured', + [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', + [ZeroExError.InvalidSignature]: 'Order signature is not valid', + [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ZeroExError.OutOfGas]: 'Transaction ran out of gas', + [ZeroExError.NoNetworkId]: 'No network id detected', + }; + const exchangeContractErrorToHumanReadableError: { + [error: string]: string; + } = { + [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelAmountZero]: "Order cancel amount can't be 0", + [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillAmountZero]: "Order fill amount can't be 0", + [ExchangeContractErrs.OrderRemainingFillAmountZero]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', + [ExchangeContractErrs.InsufficientTakerBalance]: + 'Taker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientTakerAllowance]: + 'Taker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientMakerBalance]: + 'Maker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientMakerAllowance]: + 'Maker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientTakerFeeAllowance]: + 'Taker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeAllowance]: + 'Maker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: `This order can only be filled by ${takerAddress}`, + [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', + }; + const humanReadableErrorMsg = + exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; + return humanReadableErrorMsg; + }, }; diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts index 0cd2a5cc2..415df6e8b 100644 --- a/packages/website/ts/web3_wrapper.ts +++ b/packages/website/ts/web3_wrapper.ts @@ -5,154 +5,154 @@ import { utils } from 'ts/utils/utils'; import * as Web3 from 'web3'; export class Web3Wrapper { - private _dispatcher: Dispatcher; - private _web3: Web3; - private _prevNetworkId: number; - private _shouldPollUserAddress: boolean; - private _watchNetworkAndBalanceIntervalId: NodeJS.Timer; - private _prevUserEtherBalanceInEth: BigNumber; - private _prevUserAddress: string; - constructor( - dispatcher: Dispatcher, - provider: Web3.Provider, - networkIdIfExists: number, - shouldPollUserAddress: boolean, - ) { - this._dispatcher = dispatcher; - this._prevNetworkId = networkIdIfExists; - this._shouldPollUserAddress = shouldPollUserAddress; + private _dispatcher: Dispatcher; + private _web3: Web3; + private _prevNetworkId: number; + private _shouldPollUserAddress: boolean; + private _watchNetworkAndBalanceIntervalId: NodeJS.Timer; + private _prevUserEtherBalanceInEth: BigNumber; + private _prevUserAddress: string; + constructor( + dispatcher: Dispatcher, + provider: Web3.Provider, + networkIdIfExists: number, + shouldPollUserAddress: boolean, + ) { + this._dispatcher = dispatcher; + this._prevNetworkId = networkIdIfExists; + this._shouldPollUserAddress = shouldPollUserAddress; - this._web3 = new Web3(); - this._web3.setProvider(provider); + this._web3 = new Web3(); + this._web3.setProvider(provider); - // tslint:disable-next-line:no-floating-promises - this._startEmittingNetworkConnectionAndUserBalanceStateAsync(); - } - public isAddress(address: string) { - return this._web3.isAddress(address); - } - public async getAccountsAsync(): Promise<string[]> { - const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); - return addresses; - } - public async getFirstAccountIfExistsAsync() { - const addresses = await this.getAccountsAsync(); - if (_.isEmpty(addresses)) { - return ''; - } - return addresses[0]; - } - public async getNodeVersionAsync(): Promise<string> { - const nodeVersion = await promisify<string>(this._web3.version.getNode)(); - return nodeVersion; - } - public getProviderObj() { - return this._web3.currentProvider; - } - public async getNetworkIdIfExists() { - try { - const networkId = await this._getNetworkAsync(); - return Number(networkId); - } catch (err) { - return undefined; - } - } - public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { - const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); - const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether'); - const balanceEth = new BigNumber(balanceEthOldBigNumber); - return balanceEth; - } - public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { - const code = await promisify<string>(this._web3.eth.getCode)(address); - // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients - const zeroHexAddressRegex = /^0[xX][0]*$/; - const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); - return didFindCode; - } - public async signTransactionAsync(address: string, message: string): Promise<string> { - const signData = await promisify<string>(this._web3.eth.sign)(address, message); - return signData; - } - public async getBlockTimestampAsync(blockHash: string): Promise<number> { - const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash); - return timestamp; - } - public destroy() { - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); - // HACK: stop() is only available on providerEngine instances - const provider = this._web3.currentProvider; - if (!_.isUndefined((provider as any).stop)) { - (provider as any).stop(); - } - } - // This should only be called from the LedgerConfigDialog - public updatePrevUserAddress(userAddress: string) { - this._prevUserAddress = userAddress; - } - private async _getNetworkAsync() { - const networkId = await promisify(this._web3.version.getNetwork)(); - return networkId; - } - private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() { - if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { - return; // we are already emitting the state - } + // tslint:disable-next-line:no-floating-promises + this._startEmittingNetworkConnectionAndUserBalanceStateAsync(); + } + public isAddress(address: string) { + return this._web3.isAddress(address); + } + public async getAccountsAsync(): Promise<string[]> { + const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); + return addresses; + } + public async getFirstAccountIfExistsAsync() { + const addresses = await this.getAccountsAsync(); + if (_.isEmpty(addresses)) { + return ''; + } + return addresses[0]; + } + public async getNodeVersionAsync(): Promise<string> { + const nodeVersion = await promisify<string>(this._web3.version.getNode)(); + return nodeVersion; + } + public getProviderObj() { + return this._web3.currentProvider; + } + public async getNetworkIdIfExists() { + try { + const networkId = await this._getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { + const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); + const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether'); + const balanceEth = new BigNumber(balanceEthOldBigNumber); + return balanceEth; + } + public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { + const code = await promisify<string>(this._web3.eth.getCode)(address); + // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients + const zeroHexAddressRegex = /^0[xX][0]*$/; + const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); + return didFindCode; + } + public async signTransactionAsync(address: string, message: string): Promise<string> { + const signData = await promisify<string>(this._web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise<number> { + const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash); + return timestamp; + } + public destroy() { + this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + // HACK: stop() is only available on providerEngine instances + const provider = this._web3.currentProvider; + if (!_.isUndefined((provider as any).stop)) { + (provider as any).stop(); + } + } + // This should only be called from the LedgerConfigDialog + public updatePrevUserAddress(userAddress: string) { + this._prevUserAddress = userAddress; + } + private async _getNetworkAsync() { + const networkId = await promisify(this._web3.version.getNetwork)(); + return networkId; + } + private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() { + if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { + return; // we are already emitting the state + } - let prevNodeVersion: string; - this._prevUserEtherBalanceInEth = new BigNumber(0); - this._dispatcher.updateNetworkId(this._prevNetworkId); - this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - // Check for network state changes - const currentNetworkId = await this.getNetworkIdIfExists(); - if (currentNetworkId !== this._prevNetworkId) { - this._prevNetworkId = currentNetworkId; - this._dispatcher.updateNetworkId(currentNetworkId); - } + let prevNodeVersion: string; + this._prevUserEtherBalanceInEth = new BigNumber(0); + this._dispatcher.updateNetworkId(this._prevNetworkId); + this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + // Check for network state changes + const currentNetworkId = await this.getNetworkIdIfExists(); + if (currentNetworkId !== this._prevNetworkId) { + this._prevNetworkId = currentNetworkId; + this._dispatcher.updateNetworkId(currentNetworkId); + } - // Check for node version changes - const currentNodeVersion = await this.getNodeVersionAsync(); - if (currentNodeVersion !== prevNodeVersion) { - prevNodeVersion = currentNodeVersion; - this._dispatcher.updateNodeVersion(currentNodeVersion); - } + // Check for node version changes + const currentNodeVersion = await this.getNodeVersionAsync(); + if (currentNodeVersion !== prevNodeVersion) { + prevNodeVersion = currentNodeVersion; + this._dispatcher.updateNodeVersion(currentNodeVersion); + } - if (this._shouldPollUserAddress) { - const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); - // Update makerAddress on network change - if (this._prevUserAddress !== userAddressIfExists) { - this._prevUserAddress = userAddressIfExists; - this._dispatcher.updateUserAddress(userAddressIfExists); - } + if (this._shouldPollUserAddress) { + const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); + // Update makerAddress on network change + if (this._prevUserAddress !== userAddressIfExists) { + this._prevUserAddress = userAddressIfExists; + this._dispatcher.updateUserAddress(userAddressIfExists); + } - // Check for user ether balance changes - if (userAddressIfExists !== '') { - await this._updateUserEtherBalanceAsync(userAddressIfExists); - } - } else { - // This logic is primarily for the Ledger, since we don't regularly poll for the address - // we simply update the balance for the last fetched address. - if (!_.isEmpty(this._prevUserAddress)) { - await this._updateUserEtherBalanceAsync(this._prevUserAddress); - } - } - }, - 5000, - (err: Error) => { - utils.consoleLog(`Watching network and balances failed: ${err}`); - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); - }, - ); - } - private async _updateUserEtherBalanceAsync(userAddress: string) { - const balance = await this.getBalanceInEthAsync(userAddress); - if (!balance.eq(this._prevUserEtherBalanceInEth)) { - this._prevUserEtherBalanceInEth = balance; - this._dispatcher.updateUserEtherBalance(balance); - } - } - private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { - intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); - } + // Check for user ether balance changes + if (userAddressIfExists !== '') { + await this._updateUserEtherBalanceAsync(userAddressIfExists); + } + } else { + // This logic is primarily for the Ledger, since we don't regularly poll for the address + // we simply update the balance for the last fetched address. + if (!_.isEmpty(this._prevUserAddress)) { + await this._updateUserEtherBalanceAsync(this._prevUserAddress); + } + } + }, + 5000, + (err: Error) => { + utils.consoleLog(`Watching network and balances failed: ${err}`); + this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + }, + ); + } + private async _updateUserEtherBalanceAsync(userAddress: string) { + const balance = await this.getBalanceInEthAsync(userAddress); + if (!balance.eq(this._prevUserEtherBalanceInEth)) { + this._prevUserEtherBalanceInEth = balance; + this._dispatcher.updateUserEtherBalance(balance); + } + } + private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { + intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); + } } |