diff options
Diffstat (limited to 'packages/website')
122 files changed, 1741 insertions, 441 deletions
diff --git a/packages/website/md/docs/ethereum_types/installation.md b/packages/website/md/docs/ethereum_types/installation.md new file mode 100644 index 000000000..5371cfa47 --- /dev/null +++ b/packages/website/md/docs/ethereum_types/installation.md @@ -0,0 +1,11 @@ +**Install** + +```bash +yarn add ethereum-types +``` + +**Import** + +```typescript +import { Provider } from 'ethereum-types'; +``` diff --git a/packages/website/md/docs/ethereum_types/introduction.md b/packages/website/md/docs/ethereum_types/introduction.md new file mode 100644 index 000000000..43484cdd1 --- /dev/null +++ b/packages/website/md/docs/ethereum_types/introduction.md @@ -0,0 +1 @@ +Welcome to the [ethereum-types](https://github.com/0xProject/0x-monorepo/packages/ethereum-types) documentation! This package provides ethereum specific (but not library-specific) types that are meant to be shared between other ethereum packages. diff --git a/packages/website/package.json b/packages/website/package.json index a17964f2b..5287414c7 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -10,7 +10,7 @@ "build": "NODE_ENV=production webpack; exit 0;", "clean": "shx rm -f public/bundle*", "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", - "watch": "webpack-dev-server --content-base public --https", + "watch_without_deps": "webpack-dev-server --content-base public --https", "deploy_dogfood": "npm run build; aws s3 sync ./public/. s3://dogfood.0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" @@ -37,6 +37,7 @@ "lodash": "^4.17.4", "material-ui": "^0.17.1", "moment": "2.21.0", + "polished": "^1.9.2", "query-string": "^6.0.0", "react": "15.6.1", "react-copy-to-clipboard": "^4.2.3", @@ -52,6 +53,7 @@ "redux": "^3.6.0", "redux-devtools-extension": "^2.13.2", "semver-sort": "0.0.4", + "styled-components": "^3.3.0", "thenby": "^1.2.3", "truffle-contract": "2.0.1", "web3": "^0.20.0", diff --git a/packages/website/public/images/jobs/location1.png b/packages/website/public/images/jobs/location1.png Binary files differnew file mode 100644 index 000000000..bfda47576 --- /dev/null +++ b/packages/website/public/images/jobs/location1.png diff --git a/packages/website/public/images/jobs/location2.png b/packages/website/public/images/jobs/location2.png Binary files differnew file mode 100644 index 000000000..c05f9403f --- /dev/null +++ b/packages/website/public/images/jobs/location2.png diff --git a/packages/website/public/images/jobs/location3.png b/packages/website/public/images/jobs/location3.png Binary files differnew file mode 100644 index 000000000..34b2e5380 --- /dev/null +++ b/packages/website/public/images/jobs/location3.png diff --git a/packages/website/public/images/jobs/map.png b/packages/website/public/images/jobs/map.png Binary files differnew file mode 100644 index 000000000..7b85ff66e --- /dev/null +++ b/packages/website/public/images/jobs/map.png diff --git a/packages/website/public/images/jobs/office1.png b/packages/website/public/images/jobs/office1.png Binary files differnew file mode 100644 index 000000000..f6e6d9163 --- /dev/null +++ b/packages/website/public/images/jobs/office1.png diff --git a/packages/website/public/images/jobs/office2.png b/packages/website/public/images/jobs/office2.png Binary files differnew file mode 100644 index 000000000..65f97dcad --- /dev/null +++ b/packages/website/public/images/jobs/office2.png diff --git a/packages/website/public/images/jobs/office3.png b/packages/website/public/images/jobs/office3.png Binary files differnew file mode 100644 index 000000000..1dfcb9c58 --- /dev/null +++ b/packages/website/public/images/jobs/office3.png diff --git a/packages/website/translations/chinese.json b/packages/website/translations/chinese.json index 966457a93..3e9f21370 100644 --- a/packages/website/translations/chinese.json +++ b/packages/website/translations/chinese.json @@ -59,6 +59,7 @@ "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", + "ETHEREUM_TYPES": "Ethereum Types", "SUBPROVIDERS": "Subproviders", "BLOG": "博客", "FORUM": "论坛", diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index f3acea3be..04fb0507a 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -60,6 +60,7 @@ "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", + "ETHEREUM_TYPES": "Ethereum Types", "SUBPROVIDERS": "Subproviders", "BLOG": "blog", "FORUM": "forum", diff --git a/packages/website/translations/korean.json b/packages/website/translations/korean.json index 7414207f7..c38de6677 100644 --- a/packages/website/translations/korean.json +++ b/packages/website/translations/korean.json @@ -59,6 +59,7 @@ "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", + "ETHEREUM_TYPES": "Ethereum Types", "SUBPROVIDERS": "Subproviders", "BLOG": "블로그", "FORUM": "포럼", diff --git a/packages/website/translations/russian.json b/packages/website/translations/russian.json index 75ab02a27..a7e06d646 100644 --- a/packages/website/translations/russian.json +++ b/packages/website/translations/russian.json @@ -59,6 +59,7 @@ "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", + "ETHEREUM_TYPES": "Ethereum Types", "SUBPROVIDERS": "Subproviders", "BLOG": "Блог", "FORUM": "Форум", diff --git a/packages/website/translations/spanish.json b/packages/website/translations/spanish.json index 8f537ea40..5a8920f61 100644 --- a/packages/website/translations/spanish.json +++ b/packages/website/translations/spanish.json @@ -60,6 +60,7 @@ "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", + "ETHEREUM_TYPES": "Ethereum Types", "SUBPROVIDERS": "Subproviders", "BLOG": "blog", "FORUM": "foro", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 6e4d03e27..c955e1033 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -132,7 +132,7 @@ export class Blockchain { return provider; } - constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { + constructor(dispatcher: Dispatcher) { this._dispatcher = dispatcher; const defaultGasPrice = GWEI_IN_WEI * 30; this._defaultGasPrice = new BigNumber(defaultGasPrice); @@ -577,13 +577,13 @@ export class Blockchain { trackedTokensByAddress[token.address] = token; }); if (!_.isUndefined(this._userAddressIfExists)) { - _.each(trackedTokensByAddress, (token: Token, address: string) => { + _.each(trackedTokensByAddress, (token: Token) => { trackedTokenStorage.addTrackedTokenToUser(this._userAddressIfExists, this.networkId, token); }); } } else { // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array - _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => { + _.each(trackedTokensByAddress, (_trackedToken: Token, address: string) => { if (!_.isUndefined(tokenRegistryTokensByAddress[address])) { tokenRegistryTokensByAddress[address].isTracked = true; } @@ -754,7 +754,7 @@ export class Blockchain { const tokenRegistryTokens = await this._contractWrappers.tokenRegistry.getTokensAsync(); const tokenByAddress: TokenByAddress = {}; - _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { + _.each(tokenRegistryTokens, (t: ZeroExToken) => { // 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]; diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts index 0d376bc74..3890a9e57 100644 --- a/packages/website/ts/blockchain_watcher.ts +++ b/packages/website/ts/blockchain_watcher.ts @@ -1,8 +1,7 @@ -import { BigNumber, intervalUtils, logUtils, promisify } from '@0xproject/utils'; +import { BigNumber, intervalUtils, logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { utils } from 'ts/utils/utils'; export class BlockchainWatcher { private _dispatcher: Dispatcher; diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 7156e700b..b968a3147 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -1,5 +1,4 @@ import { colors, Networks } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; 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 d647bba80..9ac78e80e 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -20,7 +20,7 @@ interface EthWethConversionDialogProps { onCancelled: () => void; isOpen: boolean; token: Token; - etherBalanceInWei: BigNumber; + etherBalanceInWei?: BigNumber; lastForceTokenStateRefetch: number; } @@ -60,7 +60,7 @@ export class EthWethConversionDialog extends React.Component< <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, ]; const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; - return ( + return !_.isUndefined(this.props.etherBalanceInWei) ? ( <Dialog title={title} titleStyle={{ fontWeight: 100 }} @@ -70,7 +70,7 @@ export class EthWethConversionDialog extends React.Component< > {this._renderConversionDialogBody()} </Dialog> - ); + ) : null; } private _renderConversionDialogBody(): React.ReactNode { const explanation = diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 196414407..c9727b553 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -250,7 +250,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); return true; } - private _onDerivationPathChanged(e: any, derivationPath: string): void { + private _onDerivationPathChanged(_event: any, derivationPath: string): void { let derivationErrMsg = ''; if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { derivationErrMsg = 'Must be valid Ethereum path.'; @@ -295,7 +295,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return userAddresses; } - private _onSelectedNetworkUpdated(e: any, index: number, networkId: number): void { + private _onSelectedNetworkUpdated(_event: any, _index: number, networkId: number): void { this.setState({ preferredNetworkId: networkId, }); 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 ac0b27cdc..f6594b9d5 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 2fb35cc1c..4b91a2ebd 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -18,7 +18,7 @@ interface EthWethConversionButtonProps { ethToken: Token; dispatcher: Dispatcher; blockchain: Blockchain; - userEtherBalanceInWei?: BigNumber; + userEtherBalanceInWei: BigNumber; isOutdatedWrappedEther: boolean; onConversionSuccessful?: () => void; isDisabled?: boolean; diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 1db5ff77f..20b446155 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -20,7 +20,6 @@ import { } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; const DATE_FORMAT = 'D/M/YY'; const ICON_DIMENSION = 40; @@ -35,6 +34,7 @@ interface EthWrappersProps { userAddress: string; userEtherBalanceInWei?: BigNumber; lastForceTokenStateRefetch: number; + isFullWidth?: boolean; } interface EthWrappersState { @@ -43,6 +43,9 @@ interface EthWrappersState { } export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { + public static defaultProps: Partial<EthWrappersProps> = { + isFullWidth: false, + }; private _isUnmounted: boolean; constructor(props: EthWrappersProps) { super(props); @@ -93,12 +96,12 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt EtherscanLinkSuffixes.Address, ); const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); - const userEtherBalanceInEth = Web3Wrapper.toUnitAmount( - this.props.userEtherBalanceInWei, - constants.DECIMAL_PLACES_ETH, - ); + const userEtherBalanceInEth = !_.isUndefined(this.props.userEtherBalanceInWei) + ? Web3Wrapper.toUnitAmount(this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH) + : undefined; + const rootClassName = this.props.isFullWidth ? 'clearfix' : 'clearfix lg-px4 md-px4 sm-px2'; return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <div className={rootClassName} style={{ minHeight: 600 }}> <div className="relative"> <h3>ETH Wrapper</h3> <div className="absolute" style={{ top: 0, right: 0 }}> @@ -116,7 +119,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt <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 }}> + <Table selectable={false} style={{ backgroundColor: 'transparent' }}> <TableHeader displaySelectAll={false} adjustForCheckbox={false}> <TableRow> <TableHeaderColumn>ETH Token</TableHeaderColumn> @@ -143,7 +146,11 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> </TableRowColumn> <TableRowColumn> - {userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH + {!_.isUndefined(userEtherBalanceInEth) ? ( + `${userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH` + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} </TableRowColumn> <TableRowColumn> <EthWethConversionButton @@ -157,6 +164,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} userEtherBalanceInWei={this.props.userEtherBalanceInWei} + isDisabled={_.isUndefined(userEtherBalanceInEth)} /> </TableRowColumn> </TableRow> @@ -203,7 +211,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt it to the updated WETH token. </div> <div> - <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <Table selectable={false} style={{ backgroundColor: 'transparent' }}> <TableHeader displaySelectAll={false} adjustForCheckbox={false}> <TableRow> <TableHeaderColumn>WETH Version</TableHeaderColumn> @@ -213,9 +221,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableHeaderColumn> </TableRow> </TableHeader> - <TableBody displayRowCheckbox={false}> - {this._renderOutdatedWeths(etherToken, this.state.ethTokenState)} - </TableBody> + <TableBody displayRowCheckbox={false}>{this._renderOutdatedWeths(etherToken)}</TableBody> </Table> </div> </div> @@ -241,7 +247,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> ); } - private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState): React.ReactNode { + private _renderOutdatedWeths(etherToken: Token): React.ReactNode { const rows = _.map( configs.OUTDATED_WRAPPED_ETHERS, (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 0168ec8f6..f3ea44286 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -39,6 +39,8 @@ interface FillOrderProps { initialOrder: Order; dispatcher: Dispatcher; lastForceTokenStateRefetch: number; + isFullWidth?: boolean; + shouldHideHeader?: boolean; } interface FillOrderState { @@ -61,6 +63,10 @@ interface FillOrderState { } export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { + public static defaultProps: Partial<FillOrderProps> = { + isFullWidth: false, + shouldHideHeader: false, + }; private _isUnmounted: boolean; constructor(props: FillOrderProps) { super(props); @@ -97,10 +103,15 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { this._isUnmounted = true; } public render(): React.ReactNode { + const rootClassName = this.props.isFullWidth ? 'clearfix' : 'lg-px4 md-px4 sm-px2'; return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> - <h3>Fill an order</h3> - <Divider /> + <div className={rootClassName} style={{ minHeight: 600 }}> + {!this.props.shouldHideHeader && ( + <div> + <h3>Fill an order</h3> + <Divider /> + </div> + )} <div> {!this.props.isOrderInUrl && ( <div> @@ -340,7 +351,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { this._onFillOrderClickFireAndForgetAsync(); } } - private _onFillAmountChange(isValid: boolean, amount?: BigNumber): void { + private _onFillAmountChange(_isValid: boolean, amount?: BigNumber): void { this.props.dispatcher.updateOrderFillAmount(amount); } private _onFillOrderJSONChanged(event: any): void { diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index c44e41084..9fb332a98 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -235,7 +235,7 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private _updateLanguage(e: any, index: number, value: Language): void { + private _updateLanguage(_event: any, _index: number, value: Language): void { this.setState({ selectedLanguage: value, }); diff --git a/packages/website/ts/components/forms/subscribe_form.tsx b/packages/website/ts/components/forms/subscribe_form.tsx new file mode 100644 index 000000000..8ef58328e --- /dev/null +++ b/packages/website/ts/components/forms/subscribe_form.tsx @@ -0,0 +1,123 @@ +import { colors } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; +import { Input } from 'ts/components/ui/input'; +import { Text } from 'ts/components/ui/text'; +import { backendClient } from 'ts/utils/backend_client'; + +export interface SubscribeFormProps {} + +export enum SubscribeFormStatus { + None, + Error, + Success, + Loading, + Other, +} + +export interface SubscribeFormState { + emailText: string; + lastSubmittedEmail: string; + status: SubscribeFormStatus; +} + +const FORM_FONT_SIZE = '15px'; + +// TODO: Translate visible strings. https://app.asana.com/0/628666249318202/697485674422001 +export class SubscribeForm extends React.Component<SubscribeFormProps, SubscribeFormState> { + public state = { + emailText: '', + lastSubmittedEmail: '', + status: SubscribeFormStatus.None, + }; + public render(): React.ReactNode { + return ( + <Container className="flex flex-column items-center justify-between md-mx2 sm-mx2"> + <Container marginBottom="15px"> + <Text fontFamily="Roboto Mono" fontColor={colors.grey} center={true}> + Subscribe to our newsletter for 0x relayer and dApp updates + </Text> + </Container> + <form onSubmit={this._handleFormSubmitAsync.bind(this)}> + <Container className="flex flex-wrap justify-center items-center"> + <Container marginTop="15px"> + <Input + placeholder="you@email.com" + value={this.state.emailText} + fontColor={colors.white} + fontSize={FORM_FONT_SIZE} + backgroundColor={colors.projectsGrey} + width="300px" + onChange={this._handleEmailInputChange.bind(this)} + /> + </Container> + <Container marginLeft="15px" marginTop="15px"> + <Button + type="submit" + backgroundColor={colors.darkestGrey} + fontColor={colors.white} + fontSize={FORM_FONT_SIZE} + > + Subscribe + </Button> + </Container> + </Container> + </form> + {this._renderMessage()} + </Container> + ); + } + private _renderMessage(): React.ReactNode { + let message = null; + switch (this.state.status) { + case SubscribeFormStatus.Error: + message = 'Sorry, something went wrong. Try again later.'; + break; + case SubscribeFormStatus.Loading: + message = 'One second...'; + break; + case SubscribeFormStatus.Success: + message = `Thanks! ${this.state.lastSubmittedEmail} is now on the mailing list.`; + break; + case SubscribeFormStatus.None: + break; + default: + throw new Error( + 'The SubscribeFormStatus switch statement is not exhaustive when choosing an error message.', + ); + } + return ( + <Container isHidden={!message} marginTop="30px"> + <Text center={true} fontFamily="Roboto Mono" fontColor={colors.grey}> + {message || 'spacer text (never shown to user)'} + </Text> + </Container> + ); + } + private _handleEmailInputChange(event: React.ChangeEvent<HTMLInputElement>): void { + this.setState({ emailText: event.target.value }); + } + private async _handleFormSubmitAsync(event: React.FormEvent<HTMLInputElement>): Promise<void> { + event.preventDefault(); + if (_.isUndefined(this.state.emailText) || _.isEmpty(this.state.emailText)) { + return; + } + this.setState({ + status: SubscribeFormStatus.Loading, + lastSubmittedEmail: this.state.emailText, + }); + try { + const response = await backendClient.subscribeToNewsletterAsync(this.state.emailText); + const status = response.status === 200 ? SubscribeFormStatus.Success : SubscribeFormStatus.Error; + this.setState({ status, emailText: '' }); + } catch (error) { + this._setStatus(SubscribeFormStatus.Error); + } + } + private _setStatus(status: SubscribeFormStatus): void { + this.setState({ status }); + } +} 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 5f968a5e4..d26b5c3fa 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -47,6 +47,8 @@ interface GenerateOrderFormProps { sideToAssetToken: SideToAssetToken; tokenByAddress: TokenByAddress; lastForceTokenStateRefetch: number; + isFullWidth?: boolean; + shouldHideHeader?: boolean; } interface GenerateOrderFormState { @@ -56,6 +58,10 @@ interface GenerateOrderFormState { } export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, GenerateOrderFormState> { + public static defaultProps: Partial<GenerateOrderFormProps> = { + isFullWidth: false, + shouldHideHeader: false, + }; constructor(props: GenerateOrderFormProps) { super(props); this.state = { @@ -80,10 +86,15 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); const initialTakerAddress = this.props.orderTakerAddress === constants.NULL_ADDRESS ? '' : this.props.orderTakerAddress; + const rootClassName = this.props.isFullWidth ? 'clearfix mb2' : 'clearfix mb2 lg-px4 md-px4 sm-px2'; return ( - <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> - <h3>Generate an order</h3> - <Divider /> + <div className={rootClassName}> + {!this.props.shouldHideHeader && ( + <div> + <h3>Generate an order</h3> + <Divider /> + </div> + )} <div className="mx-auto" style={{ maxWidth: 580 }}> <div className="pt3"> <div className="mx-auto clearfix"> @@ -215,7 +226,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G </div> ); } - private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber): void { + private _onTokenAmountChange(token: Token, side: Side, _isValid: boolean, amount?: BigNumber): void { this.props.dispatcher.updateChosenAssetToken(side, { address: token.address, amount, 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 a9b8e9589..176a0807b 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -152,7 +152,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor }; this.props.onNewTokenSubmitted(newToken); } - private _onTokenNameChanged(e: any, name: string): void { + private _onTokenNameChanged(_event: any, name: string): void { let nameErrText = ''; const maxLength = 30; const tokens = _.values(this.props.tokenByAddress); @@ -173,7 +173,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor nameErrText, }); } - private _onTokenSymbolChanged(e: any, symbol: string): void { + private _onTokenSymbolChanged(_event: any, symbol: string): void { let symbolErrText = ''; const maxLength = 5; const tokens = _.values(this.props.tokenByAddress); @@ -193,7 +193,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor symbolErrText, }); } - private _onTokenDecimalsChanged(e: any, decimals: string): void { + private _onTokenDecimalsChanged(_event: any, decimals: string): void { let decimalsErrText = ''; const maxLength = 2; if (decimals === '') { diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index d61dfa87d..0dd2a5aa5 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -5,10 +5,9 @@ import Toggle from 'material-ui/Toggle'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; import { BalanceErrs, Token, TokenState } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; -import { colors } from 'ts/utils/colors'; -import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; @@ -18,11 +17,11 @@ interface AllowanceToggleProps { networkId: number; blockchain: Blockchain; dispatcher: Dispatcher; - onErrorOccurred: (errType: BalanceErrs) => void; token: Token; tokenState: TokenState; userAddress: string; - isDisabled: boolean; + isDisabled?: boolean; + onErrorOccurred?: (errType: BalanceErrs) => void; refetchTokenStateAsync: () => Promise<void>; } @@ -57,6 +56,10 @@ const styles: Styles = { }; export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { + public static defaultProps = { + onErrorOccurred: _.noop, + isDisabled: false, + }; constructor(props: AllowanceToggleProps) { super(props); this.state = { diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index e5b502b25..968609030 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -104,7 +104,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp /> ); } - private _onValueChange(e: any, amountString: string): void { + private _onValueChange(_event: any, amountString: string): void { this._setAmountState(amountString, this.props.balance, () => { const isValid = _.isUndefined(this._validate(amountString, this.props.balance)); const isPositiveNumber = utils.isNumeric(amountString) && !_.includes(amountString, '-'); diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx index 5c68080fe..79dd2f568 100644 --- a/packages/website/ts/components/inputs/expiration_input.tsx +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -80,7 +80,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); this.props.updateOrderExpiry(defaultDateTime); } - private _onDateChanged(e: any, date: Date): void { + private _onDateChanged(_event: any, date: Date): void { const dateMoment = moment(date); this.setState({ dateMoment, @@ -88,7 +88,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); this.props.updateOrderExpiry(timestamp); } - private _onTimeChanged(e: any, time: Date): void { + private _onTimeChanged(_event: any, time: Date): void { const timeMoment = moment(time); this.setState({ timeMoment, diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx index a4dc01ba8..6ba7584a7 100644 --- a/packages/website/ts/components/inputs/identicon_address_input.tsx +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { AddressInput } from 'ts/components/inputs/address_input'; import { Identicon } from 'ts/components/ui/identicon'; diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index c2c4dd63b..0bd36781e 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -1,5 +1,4 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; diff --git a/packages/website/ts/components/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx index e5d152e3e..b4a174a03 100644 --- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx +++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx @@ -1,5 +1,5 @@ import { colors } from '@0xproject/react-shared'; -import { BigNumber, logUtils } from '@0xproject/utils'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import Paper from 'material-ui/Paper'; @@ -15,27 +15,14 @@ import { EthWrappers } from 'ts/components/eth_wrappers'; import { FillOrder } from 'ts/components/fill_order'; import { Footer } from 'ts/components/footer'; import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu'; -import { RelayerIndex } from 'ts/components/relayer_index/relayer_index'; import { TokenBalances } from 'ts/components/token_balances'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { FlashMessage } from 'ts/components/ui/flash_message'; -import { Wallet } from 'ts/components/wallet/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; -import { validator } from 'ts/schemas/validator'; -import { - BlockchainErrs, - Environments, - HashData, - Order, - ProviderType, - ScreenWidths, - TokenByAddress, - WebsitePaths, -} from 'ts/types'; +import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { orderParser } from 'ts/utils/order_parser'; @@ -323,7 +310,7 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta /> ); } - private _renderFillOrder(match: any, location: Location, history: History): React.ReactNode { + private _renderFillOrder(_match: any, _location: Location, _history: History): React.ReactNode { const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? this.props.userSuppliedOrderCache : this._sharedOrderIfExists; @@ -342,7 +329,7 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta /> ); } - private _renderGenerateOrderForm(match: any, location: Location, history: History): React.ReactNode { + private _renderGenerateOrderForm(_match: any, _location: Location, _history: History): React.ReactNode { return ( <GenerateOrderForm blockchain={this._blockchain} diff --git a/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx index 7469ca14e..1dd164f8b 100644 --- a/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx +++ b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx @@ -1,8 +1,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import { MenuItem } from 'ts/components/ui/menu_item'; -import { Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { WebsitePaths } from 'ts/types'; export interface LegacyPortalMenuProps { menuItemStyle: React.CSSProperties; diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 4066babaf..9879cd387 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -1,11 +1,9 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { Placement, Popper, PopperChildrenProps } from 'react-popper'; import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip'; import { Container } from 'ts/components/ui/container'; import { Overlay } from 'ts/components/ui/overlay'; -import { zIndex } from 'ts/utils/style'; export interface Step { target: string; diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index eb34a87f2..155c70c5f 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { colors } from '@0xproject/react-shared'; import { Container } from 'ts/components/ui/container'; import { Island } from 'ts/components/ui/island'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index bf52684d7..efb844cb5 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -2,11 +2,14 @@ import * as _ from 'lodash'; import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; +import { Blockchain } from 'ts/blockchain'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; -import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; +import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { utils } from 'ts/utils/utils'; export interface PortalOnboardingFlowProps { + blockchain: Blockchain; stepIndex: number; isRunning: boolean; userAddress: string; @@ -19,6 +22,7 @@ export interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { @@ -39,7 +43,6 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr /> ); } - private _getSteps(): Step[] { const steps: Step[] = [ { @@ -77,18 +80,33 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr placement: 'right', continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', }, + { + target: '.weth-row', + content: ( + <div> + Unlock your tokens for trading. You only need to do this once for each token. + <div> ETH: {this._renderEthAllowanceToggle()}</div> + <div> ZRX: {this._renderZrxAllowanceToggle()}</div> + </div> + ), + placement: 'right', + continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled', + }, + { + target: '.wallet', + content: 'Congrats! Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem.', + placement: 'right', + continueButtonDisplay: 'enabled', + }, ]; return steps; } - private _isAddressAvailable(): boolean { return !_.isEmpty(this.props.userAddress); } - private _userHasVisibleEth(): boolean { return this.props.userEtherBalanceInWei > new BigNumber(0); } - private _userHasVisibleWeth(): boolean { const ethToken = utils.getEthToken(this.props.tokenByAddress); if (!ethToken) { @@ -97,15 +115,25 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; return wethTokenState.balance > new BigNumber(0); } - + private _userHasAllowancesForWethAndZrx(): boolean { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + if (ethToken && zrxToken) { + const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance; + const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance; + return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0); + } + return false; + } private _overrideOnboardingStateIfShould(): void { this._autoStartOnboardingIfShould(); this._adjustStepIfShould(); } private _adjustStepIfShould(): void { + const stepIndex = this.props.stepIndex; if (this._isAddressAvailable()) { - if (this.props.stepIndex < 2) { + if (stepIndex < 2) { this.props.updateOnboardingStep(2); } return; @@ -115,14 +143,42 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr this.props.injectedProviderName, ); if (isExternallyInjected) { - this.props.updateOnboardingStep(1); + if (stepIndex !== 1) { + this.props.updateOnboardingStep(1); + } return; } - this.props.updateOnboardingStep(0); + if (stepIndex !== 0) { + this.props.updateOnboardingStep(0); + } } private _autoStartOnboardingIfShould(): void { if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) { this.props.updateIsRunning(true); } } + private _renderZrxAllowanceToggle(): React.ReactNode { + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(zrxToken); + } + private _renderEthAllowanceToggle(): React.ReactNode { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(ethToken); + } + private _renderAllowanceToggle(token: Token): React.ReactNode { + if (!token) { + return null; + } + const tokenState = this.props.trackedTokenStateByAddress[token.address]; + return ( + <AllowanceToggle + token={token} + tokenState={tokenState} + isDisabled={!tokenState.isLoaded} + blockchain={this.props.blockchain} + // tslint:disable-next-line:jsx-no-lambda + refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)} + /> + ); + } } diff --git a/packages/website/ts/components/portal/back_button.tsx b/packages/website/ts/components/portal/back_button.tsx index 48858613c..2d0bbefc3 100644 --- a/packages/website/ts/components/portal/back_button.tsx +++ b/packages/website/ts/components/portal/back_button.tsx @@ -2,7 +2,7 @@ import { Styles } from '@0xproject/react-shared'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; export interface BackButtonProps { to: string; diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx index ace11639a..8ac2b9091 100644 --- a/packages/website/ts/components/portal/drawer_menu.tsx +++ b/packages/website/ts/components/portal/drawer_menu.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import { defaultMenuItemEntries, Menu } from 'ts/components/portal/menu'; import { Identicon } from 'ts/components/ui/identicon'; +import { colors } from 'ts/style/colors'; import { WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; const IDENTICON_DIAMETER = 45; diff --git a/packages/website/ts/components/portal/menu.tsx b/packages/website/ts/components/portal/menu.tsx index 6e97ee37e..39dab77f5 100644 --- a/packages/website/ts/components/portal/menu.tsx +++ b/packages/website/ts/components/portal/menu.tsx @@ -2,9 +2,8 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { MenuItem } from 'ts/components/ui/menu_item'; -import { Environments, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; -import { configs } from 'ts/utils/configs'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; export interface MenuTheme { paddingLeft: number; diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9aa83546a..48486939b 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -3,7 +3,7 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; -import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; @@ -34,6 +34,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { BlockchainErrs, HashData, + ItemByAddress, Order, ProviderType, ScreenWidths, @@ -43,6 +44,7 @@ import { TokenVisibility, WebsitePaths, } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { orderParser } from 'ts/utils/order_parser'; @@ -233,7 +235,11 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <div style={styles.root}> - <PortalOnboardingFlow trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} /> + <PortalOnboardingFlow + blockchain={this._blockchain} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} + /> <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} @@ -266,10 +272,6 @@ export class Portal extends React.Component<PortalProps, PortalState> { toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} networkId={this.props.networkId} /> - <PortalDisclaimerDialog - isOpen={this.state.isDisclaimerDialogOpen} - onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} - /> <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> {this.props.blockchainIsLoaded && ( <LedgerConfigDialog @@ -394,18 +396,24 @@ export class Portal extends React.Component<PortalProps, PortalState> { }, ]; return ( - <Switch> - {_.map(accountManagementItems, item => { - return ( - <Route - key={item.pathName} - path={item.pathName} - render={this._renderAccountManagementItem.bind(this, item)} - /> - ); - })}} - <Route render={this._renderNotFoundMessage.bind(this)} /> - </Switch> + <div> + <Switch> + {_.map(accountManagementItems, item => { + return ( + <Route + key={item.pathName} + path={item.pathName} + render={this._renderAccountManagementItem.bind(this, item)} + /> + ); + })}} + <Route render={this._renderNotFoundMessage.bind(this)} /> + </Switch> + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} + /> + </div> ); } private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode { @@ -426,6 +434,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { userAddress={this.props.userAddress} userEtherBalanceInWei={this.props.userEtherBalanceInWei} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + isFullWidth={true} /> ); } @@ -435,6 +444,9 @@ export class Portal extends React.Component<PortalProps, PortalState> { tokenByAddress={this.props.tokenByAddress} userAddress={this.props.userAddress} networkId={this.props.networkId} + isFullWidth={true} + shouldHideHeader={true} + isScrollable={false} /> ); } @@ -444,6 +456,8 @@ export class Portal extends React.Component<PortalProps, PortalState> { blockchain={this._blockchain} hashData={this.props.hashData} dispatcher={this.props.dispatcher} + isFullWidth={true} + shouldHideHeader={true} /> ); } @@ -463,6 +477,8 @@ export class Portal extends React.Component<PortalProps, PortalState> { tokenByAddress={this.props.tokenByAddress} dispatcher={this.props.dispatcher} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + isFullWidth={true} + shouldHideHeader={true} /> ); } @@ -480,6 +496,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { userEtherBalanceInWei={this.props.userEtherBalanceInWei} networkId={this.props.networkId} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + isFullWidth={true} /> ); } @@ -587,6 +604,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { return this._blockchain.getTokenBalanceAndAllowanceAsync(userAddressIfExists, tokenAddress); }), ); + const priceByAddress = await this._getPriceByAddressAsync(tokenAddresses); for (let i = 0; i < tokenAddresses.length; i++) { // Order is preserved in Promise.all const [balance, allowance] = balancesAndAllowances[i]; @@ -595,6 +613,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { balance, allowance, isLoaded: true, + price: priceByAddress[tokenAddress], }; } this.setState({ @@ -602,6 +621,34 @@ export class Portal extends React.Component<PortalProps, PortalState> { }); } + private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { + if (_.isEmpty(tokenAddresses)) { + return {}; + } + // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists + // create a mapping from existing symbols -> address + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + _.each(tokenAddresses, address => { + const tokenIfExists = _.get(this.props.tokenByAddress, address); + if (!_.isUndefined(tokenIfExists)) { + const symbol = tokenIfExists.symbol; + tokenAddressBySymbol[symbol] = address; + } + }); + const tokenSymbols = _.keys(tokenAddressBySymbol); + try { + const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); + const priceByAddress = _.mapKeys(priceBySymbol, (_value, symbol) => _.get(tokenAddressBySymbol, symbol)); + const result = _.mapValues(priceByAddress, price => { + const priceBigNumber = new BigNumber(price); + return priceBigNumber; + }); + return result; + } catch (err) { + return {}; + } + } + private async _refetchTokenStateAsync(tokenAddress: string): Promise<void> { await this._fetchBalancesAndAllowancesAsync([tokenAddress]); } diff --git a/packages/website/ts/components/portal/section.tsx b/packages/website/ts/components/portal/section.tsx index 9b172aae0..455ed07c9 100644 --- a/packages/website/ts/components/portal/section.tsx +++ b/packages/website/ts/components/portal/section.tsx @@ -1,4 +1,3 @@ -import { Styles } from '@0xproject/react-shared'; import * as React from 'react'; export interface SectionProps { diff --git a/packages/website/ts/components/redirecter.tsx b/packages/website/ts/components/redirector.tsx index 629522bbb..a02693003 100644 --- a/packages/website/ts/components/redirecter.tsx +++ b/packages/website/ts/components/redirector.tsx @@ -1,10 +1,9 @@ -import * as React from 'react'; import { constants } from 'ts/utils/constants'; -interface RedirecterProps { +interface RedirectorProps { location: string; } -export function Redirecter(props: RedirecterProps): void { +export function Redirector(_props: RedirectorProps): void { window.location.href = constants.URL_ANGELLIST; } diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index ad6ab3de1..fbb634164 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -6,9 +6,8 @@ import * as React from 'react'; import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens'; import { Container } from 'ts/components/ui/container'; import { Island } from 'ts/components/ui/island'; -import { TokenIcon } from 'ts/components/ui/token_icon'; -import { Token, WebsiteBackendRelayerInfo } from 'ts/types'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; +import { WebsiteBackendRelayerInfo } from 'ts/types'; export interface RelayerGridTileProps { relayerInfo: WebsiteBackendRelayerInfo; diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index 9ef6eaf59..3c5761bcd 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -1,14 +1,14 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; -import FlatButton from 'material-ui/FlatButton'; import { GridList } from 'material-ui/GridList'; import * as React from 'react'; import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile'; +import { Retry } from 'ts/components/ui/retry'; +import { colors } from 'ts/style/colors'; import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; -import { colors } from 'ts/utils/colors'; export interface RelayerIndexProps { networkId: number; @@ -63,7 +63,8 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos); if (!isReadyToRender) { return ( - // TODO: consolidate this loading component with the one in portal + // TODO: consolidate this loading component with the one in portal and OpenPositions + // TODO: possibly refactor into a generic loading container with spinner and retry UI <div className="center"> {_.isUndefined(this.state.error) ? ( <CircularProgress size={40} thickness={5} /> @@ -124,31 +125,3 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde } } } - -interface RetryProps { - onRetry: () => void; -} -const Retry = (props: RetryProps) => ( - <div className="clearfix center" style={{ color: colors.black }}> - <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}> - <div className="h2" style={{ fontFamily: 'Roboto Mono' }}> - Something went wrong. - </div> - <div className="py3"> - <FlatButton - label={'reload'} - backgroundColor={colors.black} - labelStyle={{ - fontSize: 18, - fontFamily: 'Roboto Mono', - fontWeight: 'lighter', - color: colors.white, - textTransform: 'lowercase', - }} - style={{ width: 280, height: 62, borderRadius: 5 }} - onClick={props.onRetry} - /> - </div> - </div> - </div> -); diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx index e42f8a81a..a5754180b 100644 --- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx +++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx @@ -2,7 +2,6 @@ import { colors, EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0x import * as _ from 'lodash'; import * as React from 'react'; -import { TokenIcon } from 'ts/components/ui/token_icon'; import { WebsiteBackendTokenInfo } from 'ts/types'; export interface TopTokensProps { diff --git a/packages/website/ts/components/sidebar_header.tsx b/packages/website/ts/components/sidebar_header.tsx index bf46caad9..a14ab54f5 100644 --- a/packages/website/ts/components/sidebar_header.tsx +++ b/packages/website/ts/components/sidebar_header.tsx @@ -1,9 +1,6 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import * as React from 'react'; -const SHOW_DURATION_MS = 4000; - interface SidebarHeaderProps { title: string; iconUrl: string; diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 7a0742bbe..7af80745c 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -1,19 +1,17 @@ import { - colors, constants as sharedConstants, EtherscanLinkSuffixes, Networks, Styles, utils as sharedUtils, } from '@0xproject/react-shared'; -import { BigNumber, logUtils } from '@0xproject/utils'; +import { BigNumber, errorUtils, logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import Divider from 'material-ui/Divider'; import FlatButton from 'material-ui/FlatButton'; import FloatingActionButton from 'material-ui/FloatingActionButton'; -import RaisedButton from 'material-ui/RaisedButton'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentRemove from 'material-ui/svg-icons/content/remove'; import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; @@ -22,11 +20,11 @@ import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; import { AssetPicker } from 'ts/components/generate_order/asset_picker'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { SendButton } from 'ts/components/send_button'; import { HelpTooltip } from 'ts/components/ui/help_tooltip'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { TokenIcon } from 'ts/components/ui/token_icon'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { @@ -57,7 +55,7 @@ const TOKEN_COL_SPAN_SM = 1; const styles: Styles = { bgColor: { - backgroundColor: colors.grey50, + backgroundColor: 'transparent', }, }; @@ -73,20 +71,22 @@ interface TokenBalancesProps { userEtherBalanceInWei: BigNumber; networkId: number; lastForceTokenStateRefetch: number; + isFullWidth?: boolean; } interface TokenBalancesState { errorType: BalanceErrs; + trackedTokenStateByAddress: TokenStateByAddress; isBalanceSpinnerVisible: boolean; isZRXSpinnerVisible: boolean; isTokenPickerOpen: boolean; isAddingToken: boolean; - trackedTokenStateByAddress: TokenStateByAddress; } export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> { public static defaultProps: Partial<TokenBalancesProps> = { userEtherBalanceInWei: new BigNumber(0), + isFullWidth: false, }; private _isUnmounted: boolean; public constructor(props: TokenBalancesProps) { @@ -169,7 +169,6 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala />, ]; const isTestNetwork = utils.isTestNetwork(this.props.networkId); - const isKovanTestNetwork = this.props.networkId === constants.NETWORK_ID_KOVAN; const stubColumnStyle = { display: isTestNetwork ? 'none' : 'table-cell', }; @@ -187,8 +186,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH, ); + const rootClassName = this.props.isFullWidth ? 'pb2' : 'lg-px4 md-px4 sm-px1 pb2'; return ( - <div className="lg-px4 md-px4 sm-px1 pb2"> + <div className={rootClassName}> <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> <Divider /> <div className="pt2 pb2"> @@ -268,7 +268,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala <div className="inline-block">Allowance</div> <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> </TableHeaderColumn> - <TableHeaderColumn>Action</TableHeaderColumn> + {isTestNetwork && <TableHeaderColumn>Action</TableHeaderColumn>} {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} </TableRow> </TableHeader> @@ -362,28 +362,25 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </TableRowColumn> <TableRowColumn> <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={token} tokenState={tokenState} onErrorOccurred={this._onErrorOccurred.bind(this)} - userAddress={this.props.userAddress} isDisabled={!tokenState.isLoaded} refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)} /> </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 && - utils.isTestNetwork(this.props.networkId) && ( + {utils.isTestNetwork(this.props.networkId) && ( + <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 && ( <LifeCycleRaisedButton labelReady="Request" labelLoading="Sending..." @@ -391,7 +388,8 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} /> )} - </TableRowColumn> + </TableRowColumn> + )} {this.props.screenWidth !== ScreenWidths.Sm && ( <TableRowColumn style={{ @@ -499,7 +497,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala return null; // No error to show default: - throw utils.spawnSwitchErr('errorType', this.state.errorType); + throw errorUtils.spawnSwitchErr('errorType', this.state.errorType); } } private _onErrorOccurred(errorType: BalanceErrs): void { @@ -580,7 +578,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } return true; } - private _onErrorDialogToggle(isOpen: boolean): void { + private _onErrorDialogToggle(_isOpen: boolean): void { this.setState({ errorType: undefined, }); diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 8a337119a..cb7c9b483 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -9,10 +9,9 @@ import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { utils } from 'ts/utils/utils'; const ROOT_HEIGHT = 24; diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index 1ecb8389c..7937f2e9d 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -1,11 +1,9 @@ import { colors, constants as sharedConstants } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProviderType } from 'ts/types'; -import { constants } from 'ts/utils/constants'; interface ProviderPickerProps { networkId: number; diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index e2d791ae3..05cc6e3ad 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -6,19 +6,16 @@ import Menu from 'material-ui/Menu'; import MenuItem from 'material-ui/MenuItem'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu'; import { DrawerMenu } from 'ts/components/portal/drawer_menu'; -import { SidebarHeader } from 'ts/components/sidebar_header'; import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; -import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { zIndex } from 'ts/style/z_index'; import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; @@ -165,6 +162,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { primaryText={this.props.translate.get(Key.SolCov, Deco.CapWords)} /> </Link>, + <Link key="subMenuItem-ethereum-types" to={WebsitePaths.EthereumTypes} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.EthereumTypes, Deco.CapWords)} + /> + </Link>, <a key="subMenuItem-whitePaper" target="_blank" @@ -379,6 +382,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </MenuItem> </Link> )} + {!this._isViewingEthereumTypesDocs() && ( + <Link to={WebsitePaths.EthereumTypes} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.EthereumTypes, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} {!this._isViewingPortal() && ( <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> <MenuItem className="py2"> @@ -418,8 +429,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { ) { return undefined; } - - const sectionTitle = `${this.props.docsInfo.displayName} Docs`; return ( <div className="lg-hide md-hide"> <NestedSidebarMenu @@ -507,6 +516,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { private _isViewingSubprovidersDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Subproviders); } + private _isViewingEthereumTypesDocs(): boolean { + return _.includes(this.props.location.pathname, WebsitePaths.EthereumTypes); + } private _isViewingWiki(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Wiki); } diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx index 1ca9d866f..84c0f70a8 100644 --- a/packages/website/ts/components/trade_history/trade_history.tsx +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -13,6 +13,9 @@ interface TradeHistoryProps { tokenByAddress: TokenByAddress; userAddress: string; networkId: number; + isFullWidth?: boolean; + shouldHideHeader?: boolean; + isScrollable?: boolean; } interface TradeHistoryState { @@ -20,6 +23,11 @@ interface TradeHistoryState { } export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> { + public static defaultProps: Partial<TradeHistoryProps> = { + isFullWidth: false, + shouldHideHeader: false, + isScrollable: true, + }; private _fillPollingIntervalId: number; public constructor(props: TradeHistoryProps) { super(props); @@ -38,13 +46,22 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor window.scrollTo(0, 0); } public render(): React.ReactNode { + const rootClassName = !this.props.isFullWidth ? 'lg-px4 md-px4 sm-px2' : undefined; 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 className={rootClassName}> + {!this.props.shouldHideHeader && ( + <div> + <h3>Trade history</h3> + <Divider /> + </div> + )} + {this.props.isScrollable ? ( + <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> + {this._renderTrades()} + </div> + ) : ( + this._renderTrades() + )} </div> ); } diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx new file mode 100644 index 000000000..1f88297de --- /dev/null +++ b/packages/website/ts/components/ui/button.tsx @@ -0,0 +1,81 @@ +import { colors } from '@0xproject/react-shared'; +import { darken } from 'polished'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export interface ButtonProps { + className?: string; + fontSize?: string; + fontColor?: string; + fontFamily?: string; + backgroundColor?: string; + borderColor?: string; + width?: string; + type?: string; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; +} + +const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, onClick, type, className }) => ( + <button type={type} className={className} onClick={onClick}> + {children} + </button> +); + +export const Button = styled(PlainButton)` + cursor: pointer; + font-size: ${props => props.fontSize}; + color: ${props => props.fontColor}; + padding: 0.8em 2.2em; + border-radius: 6px; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); + font-weight: 500; + font-family: ${props => props.fontFamily}; + width: ${props => props.width}; + background-color: ${props => props.backgroundColor}; + border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; + &:hover { + background-color: ${props => darken(0.1, props.backgroundColor)}; + } + &:active { + background-color: ${props => darken(0.2, props.backgroundColor)}; + } +`; + +Button.defaultProps = { + fontSize: '12px', + backgroundColor: colors.white, + width: 'auto', + fontFamily: 'Roboto', +}; + +Button.displayName = 'Button'; + +type CallToActionType = 'light' | 'dark'; + +export interface CallToActionProps { + type?: CallToActionType; + fontSize?: string; + width?: string; +} + +export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ children, type, fontSize, width }) => { + const isLight = type === 'light'; + const backgroundColor = isLight ? colors.white : colors.heroGrey; + const fontColor = isLight ? colors.heroGrey : colors.white; + const borderColor = isLight ? undefined : colors.white; + return ( + <Button + fontSize={fontSize} + backgroundColor={backgroundColor} + fontColor={fontColor} + width={width} + borderColor={borderColor} + > + {children} + </Button> + ); +}; + +CallToAction.defaultProps = { + type: 'dark', +}; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index d577447b0..c6a78e181 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -11,13 +11,20 @@ export interface ContainerProps { paddingBottom?: StringOrNum; paddingRight?: StringOrNum; paddingLeft?: StringOrNum; + backgroundColor?: string; + borderRadius?: StringOrNum; maxWidth?: StringOrNum; - children?: React.ReactNode; + isHidden?: boolean; + className?: string; } -export const Container: React.StatelessComponent<ContainerProps> = (props: ContainerProps) => { - const { children, ...style } = props; - return <div style={style}>{children}</div>; +export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => { + const visibility = isHidden ? 'hidden' : undefined; + return ( + <div style={{ ...style, visibility }} className={className}> + {children} + </div> + ); }; Container.displayName = 'Container'; diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index 2c2941067..0330d1843 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -1,5 +1,4 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import * as React from 'react'; import * as CopyToClipboard from 'react-copy-to-clipboard'; import * as ReactDOM from 'react-dom'; diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx index 98a495581..22cb942f8 100644 --- a/packages/website/ts/components/ui/drop_down.tsx +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'; import * as React from 'react'; import { MaterialUIPosition } from 'ts/types'; @@ -43,7 +42,7 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { public componentWillUnmount(): void { window.clearInterval(this._popoverCloseCheckIntervalId); } - public componentWillReceiveProps(nextProps: DropDownProps): void { + public componentWillReceiveProps(_nextProps: DropDownProps): void { // HACK: If the popoverContent is updated to a different dimension and the users // mouse is no longer above it, the dropdown can enter an inconsistent state where // it believes the user is still hovering over it. In order to remedy this, we diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index 1b032c112..0beb69123 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -2,7 +2,6 @@ import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/ import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { addressOrTxHash: string; diff --git a/packages/website/ts/components/ui/filled_image.tsx b/packages/website/ts/components/ui/filled_image.tsx new file mode 100644 index 000000000..7f58ee5b9 --- /dev/null +++ b/packages/website/ts/components/ui/filled_image.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +export interface FilledImageProps { + src: string; +} +export const FilledImage = (props: FilledImageProps) => ( + <div + style={{ + width: '100%', + height: '100%', + objectFit: 'cover', + backgroundImage: `url(${props.src})`, + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + backgroundSize: 'cover', + }} + /> +); diff --git a/packages/website/ts/components/ui/input.tsx b/packages/website/ts/components/ui/input.tsx new file mode 100644 index 000000000..e01a71a53 --- /dev/null +++ b/packages/website/ts/components/ui/input.tsx @@ -0,0 +1,43 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export interface InputProps { + className?: string; + value?: string; + width?: string; + fontSize?: string; + fontColor?: string; + placeholderColor?: string; + placeholder?: string; + backgroundColor?: string; + onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; +} + +const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => ( + <input className={className} value={value} onChange={onChange} placeholder={placeholder} /> +); + +export const Input = styled(PlainInput)` + font-size: ${props => props.fontSize}; + width: ${props => props.width}; + padding: 0.8em 1.2em; + border-radius: 3px; + font-family: 'Roboto Mono'; + color: ${props => props.fontColor}; + border: none; + background-color: ${props => props.backgroundColor}; + &::placeholder { + color: ${props => props.placeholderColor}; + } +`; + +Input.defaultProps = { + width: 'auto', + backgroundColor: colors.white, + fontColor: colors.darkestGrey, + placeholderColor: colors.darkGrey, + fontSize: '12px', +}; + +Input.displayName = 'Input'; diff --git a/packages/website/ts/components/ui/island.tsx b/packages/website/ts/components/ui/island.tsx index f5480c9c9..de90b664f 100644 --- a/packages/website/ts/components/ui/island.tsx +++ b/packages/website/ts/components/ui/island.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { Styleable } from 'ts/types'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; export interface IslandProps { style?: React.CSSProperties; diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index b06978f16..380fbc77d 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -1,8 +1,7 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; +import { errorUtils } from '@0xproject/utils'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; -import { utils } from 'ts/utils/utils'; const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; @@ -63,7 +62,7 @@ export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButton label = this.props.labelComplete; break; default: - throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); + throw errorUtils.spawnSwitchErr('ButtonState', this.state.buttonState); } return ( <RaisedButton diff --git a/packages/website/ts/components/ui/overlay.tsx b/packages/website/ts/components/ui/overlay.tsx index bb2ed8e59..8b126a6d5 100644 --- a/packages/website/ts/components/ui/overlay.tsx +++ b/packages/website/ts/components/ui/overlay.tsx @@ -1,8 +1,7 @@ -import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { zIndex } from 'ts/utils/style'; +import { zIndex } from 'ts/style/z_index'; export interface OverlayProps { children?: React.ReactNode; diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index 0d86a6db5..a25550475 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { EthereumAddress } from 'ts/components/ui/ethereum_address'; import { Identicon } from 'ts/components/ui/identicon'; -import { utils } from 'ts/utils/utils'; const IMAGE_DIMENSION = 100; const IDENTICON_DIAMETER = 95; diff --git a/packages/website/ts/components/ui/retry.tsx b/packages/website/ts/components/ui/retry.tsx new file mode 100644 index 000000000..543b7df4b --- /dev/null +++ b/packages/website/ts/components/ui/retry.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { colors } from 'ts/style/colors'; + +const BUTTON_TEXT = 'reload'; + +export interface RetryProps { + onRetry: () => void; +} +export const Retry = (props: RetryProps) => ( + <div className="clearfix center" style={{ color: colors.black }}> + <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}> + <div className="h2" style={{ fontFamily: 'Roboto Mono' }}> + Something went wrong. + </div> + <div className="py3"> + <Button + type="button" + backgroundColor={colors.black} + width="290px" + fontColor={colors.white} + fontSize="18px" + fontFamily="Roboto Mono" + onClick={props.onRetry} + > + {BUTTON_TEXT} + </Button> + </div> + </div> + </div> +); diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index 4a6000d1b..f1d1ae7d4 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -1,5 +1,4 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import * as React from 'react'; interface SwapIconProps { diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx new file mode 100644 index 000000000..7e47f1d09 --- /dev/null +++ b/packages/website/ts/components/ui/text.tsx @@ -0,0 +1,41 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export type TextTag = 'p' | 'div' | 'span' | 'label'; + +export interface TextProps { + className?: string; + Tag?: TextTag; + fontSize?: string; + fontFamily?: string; + fontColor?: string; + lineHeight?: string; + minHeight?: string; + center?: boolean; + fontWeight?: number | string; +} + +const PlainText: React.StatelessComponent<TextProps> = ({ children, className, Tag }) => ( + <Tag className={className}>{children}</Tag> +); + +export const Text = styled(PlainText)` + font-family: ${props => props.fontFamily}; + font-weight: ${props => props.fontWeight}; + font-size: ${props => props.fontSize}; + ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')}; + ${props => (props.center ? 'text-align: center' : '')}; + color: ${props => props.fontColor}; + ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')}; +`; + +Text.defaultProps = { + fontFamily: 'Roboto', + fontWeight: 400, + fontColor: colors.white, + fontSize: '14px', + Tag: 'div', +}; + +Text.displayName = 'Text'; diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 7059ca350..4523b0ac9 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -1,29 +1,18 @@ -import { - constants as sharedConstants, - EtherscanLinkSuffixes, - Styles, - utils as sharedUtils, -} from '@0xproject/react-shared'; -import { BigNumber } from '@0xproject/utils'; +import { EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0xproject/react-shared'; +import { BigNumber, errorUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; -import FlatButton from 'material-ui/FlatButton'; import FloatingActionButton from 'material-ui/FloatingActionButton'; import { ListItem } from 'material-ui/List'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentRemove from 'material-ui/svg-icons/content/remove'; -import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward'; -import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'; -import Close from 'material-ui/svg-icons/navigation/close'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { Container } from 'ts/components/ui/container'; import { IconButton } from 'ts/components/ui/icon_button'; import { Identicon } from 'ts/components/ui/identicon'; @@ -31,11 +20,12 @@ import { Island } from 'ts/components/ui/island'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item'; import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; +import { zIndex } from 'ts/style/z_index'; import { - BalanceErrs, BlockchainErrs, - ItemByAddress, ProviderType, ScreenWidths, Side, @@ -45,10 +35,7 @@ import { TokenStateByAddress, WebsitePaths, } from 'ts/types'; -import { backendClient } from 'ts/utils/backend_client'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { utils } from 'ts/utils/utils'; import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles'; @@ -137,7 +124,7 @@ const styles: Styles = { const ETHER_ICON_PATH = '/images/ether.png'; const ICON_DIMENSION = 28; -const TOKEN_AMOUNT_DISPLAY_PRECISION = 3; +const TOKEN_AMOUNT_DISPLAY_PRECISION = 5; const BODY_ITEM_KEY = 'BODY'; const HEADER_ITEM_KEY = 'HEADER'; const FOOTER_ITEM_KEY = 'FOOTER'; @@ -148,10 +135,8 @@ const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56; const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`; export class Wallet extends React.Component<WalletProps, WalletState> { - private _isUnmounted: boolean; constructor(props: WalletProps) { super(props); - this._isUnmounted = false; this.state = { wrappedEtherDirection: undefined, isHoveringSidebar: false, @@ -185,7 +170,6 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderDisconnectedHeaderRows(): React.ReactElement<{}> { - const userAddress = this.props.userAddress; const primaryText = 'wallet'; return ( <StandardIconRow @@ -238,7 +222,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> ); } - private _onSidebarHover(event: React.FormEvent<HTMLInputElement>): void { + private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void { this.setState({ isHoveringSidebar: true, }); @@ -330,7 +314,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); } - private _renderTokenRow(token: Token, index: number): React.ReactNode { + private _renderTokenRow(token: Token, _index: number): React.ReactNode { const tokenState = this.props.trackedTokenStateByAddress[token.address]; const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, @@ -430,15 +414,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode { + // TODO: Error handling return ( <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={config.token} tokenState={config.tokenState} - onErrorOccurred={_.noop} // TODO: Error handling - userAddress={this.props.userAddress} isDisabled={!config.tokenState.isLoaded} refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)} /> @@ -450,14 +431,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> { symbol: string, isLoading: boolean = false, ): React.ReactNode { - const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION); - const result = `${formattedAmount} ${symbol}`; - return ( - <PlaceHolder hideChildren={isLoading}> - <div style={styles.amountLabel}>{result}</div> - </PlaceHolder> - ); + if (isLoading) { + return ( + <PlaceHolder hideChildren={isLoading}> + <div style={styles.amountLabel}>0.00 XXX</div> + </PlaceHolder> + ); + } else { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + const precision = Math.min(TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); + const formattedAmount = unitAmount.toFixed(precision); + const result = `${formattedAmount} ${symbol}`; + return <div style={styles.amountLabel}>{result}</div>; + } } private _renderValue( amount: BigNumber, @@ -502,7 +488,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { buttonIconName = 'zmdi-long-arrow-up'; break; default: - throw utils.spawnSwitchErr('wrappedEtherDirection', wrappedEtherDirection); + throw errorUtils.spawnSwitchErr('wrappedEtherDirection', wrappedEtherDirection); } } const onClick = isWrappedEtherDirectionOpen @@ -512,45 +498,6 @@ export class Wallet extends React.Component<WalletProps, WalletState> { <IconButton iconName={buttonIconName} labelText={buttonLabel} onClick={onClick} color={colors.mediumBlue} /> ); } - private _getInitialTrackedTokenStateByAddress(tokenAddresses: string[]): TokenStateByAddress { - const trackedTokenStateByAddress: TokenStateByAddress = {}; - _.each(tokenAddresses, tokenAddress => { - trackedTokenStateByAddress[tokenAddress] = { - balance: new BigNumber(0), - allowance: new BigNumber(0), - isLoaded: false, - }; - }); - return trackedTokenStateByAddress; - } - - private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { - if (_.isEmpty(tokenAddresses)) { - return {}; - } - // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists - // create a mapping from existing symbols -> address - const tokenAddressBySymbol: { [symbol: string]: string } = {}; - _.each(tokenAddresses, address => { - const tokenIfExists = _.get(this.props.tokenByAddress, address); - if (!_.isUndefined(tokenIfExists)) { - const symbol = tokenIfExists.symbol; - tokenAddressBySymbol[symbol] = address; - } - }); - const tokenSymbols = _.keys(tokenAddressBySymbol); - try { - const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); - const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol)); - const result = _.mapValues(priceByAddress, price => { - const priceBigNumber = new BigNumber(price); - return priceBigNumber; - }); - return result; - } catch (err) { - return {}; - } - } private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void { this.setState({ wrappedEtherDirection, diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx index 17fd8a19e..1015dce29 100644 --- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx +++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx @@ -1,10 +1,9 @@ import { Styles } from '@0xproject/react-shared'; import FlatButton from 'material-ui/FlatButton'; -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; +import { colors } from 'ts/style/colors'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx index 1dfcffadf..f65257142 100644 --- a/packages/website/ts/components/wallet/wrap_ether_item.tsx +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -3,15 +3,14 @@ import { BigNumber, logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import FlatButton from 'material-ui/FlatButton'; -import { ListItem } from 'material-ui/List'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; import { BlockchainCallErrs, Side, Token } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; @@ -146,7 +145,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther </div> ); } - private _onValueChange(isValid: boolean, amount?: BigNumber): void { + private _onValueChange(_isValid: boolean, amount?: BigNumber): void { this.setState({ currentInputAmount: amount, }); diff --git a/packages/website/ts/containers/about.ts b/packages/website/ts/containers/about.ts index ce8fd3afb..3b1c99d79 100644 --- a/packages/website/ts/containers/about.ts +++ b/packages/website/ts/containers/about.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -15,7 +14,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: AboutProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: AboutProps): ConnectedState => ({ translate: state.translate, }); diff --git a/packages/website/ts/containers/connect_documentation.ts b/packages/website/ts/containers/connect_documentation.ts index 698d605c9..f939ef0df 100644 --- a/packages/website/ts/containers/connect_documentation.ts +++ b/packages/website/ts/containers/connect_documentation.ts @@ -1,13 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { DocPackages } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -91,7 +89,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/ethereum_types_documentation.ts b/packages/website/ts/containers/ethereum_types_documentation.ts new file mode 100644 index 000000000..285438835 --- /dev/null +++ b/packages/website/ts/containers/ethereum_types_documentation.ts @@ -0,0 +1,122 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/ethereum_types/introduction'); +const InstallationMarkdown = require('md/docs/ethereum_types/installation'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + types: docConstants.TYPES_SECTION_NAME, +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.EthereumTypes, + type: SupportedDocJson.TypeDoc, + displayName: 'Ethereum Types', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/ethereum-types', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + }, + sectionNameToModulePath: { + [docSections.types]: ['"index"'], + }, + visibleConstructors: [], + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + typeConfigs: { + // 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: [ + 'Provider', + 'JSONRPCErrorCallback', + 'Provider', + 'ContractAbi', + 'AbiDefinition', + 'FunctionAbi', + 'ConstructorStateMutability', + 'StateMutability', + 'MethodAbi', + 'ConstructorAbi', + 'FallbackAbi', + 'EventParameter', + 'EventAbi', + 'DataItem', + 'OpCode', + // 'StructLog', // TODO: This type breaks the docs so we don't render it for now + 'TransactionTrace', + 'Unit', + 'JSONRPCRequestPayload', + 'JSONRPCResponsePayload', + 'BlockWithoutTransactionData', + 'BlockWithTransactionData', + 'Transaction', + 'TxData', + 'CallData', + 'FilterObject', + 'LogTopic', + 'DecodedLogEntry', + 'DecodedLogEntryEvent', + 'LogEntryEvent', + 'LogEntry', + 'TxDataPayable', + 'TransactionReceipt', + 'AbiType', + 'ContractEventArg', + 'DecodedLogArgs', + 'LogWithDecodedArgs', + 'RawLog', + 'BlockParamLiteral', + 'BlockParam', + 'RawLogEntry', + 'SolidityTypes', + 'TransactionReceiptWithDecodedLogs', + ], + typeNameToExternalLink: { + BigNumber: constants.URL_BIGNUMBERJS_GITHUB, + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/faq.ts b/packages/website/ts/containers/faq.ts index b539e33c9..a2b5735f6 100644 --- a/packages/website/ts/containers/faq.ts +++ b/packages/website/ts/containers/faq.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -15,7 +14,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: FAQProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: FAQProps): ConnectedState => ({ translate: state.translate, }); diff --git a/packages/website/ts/containers/generate_order_form.ts b/packages/website/ts/containers/generate_order_form.ts index 98c9b8cd6..92296dbab 100644 --- a/packages/website/ts/containers/generate_order_form.ts +++ b/packages/website/ts/containers/generate_order_form.ts @@ -1,6 +1,5 @@ import { ECSignature } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Blockchain } from 'ts/blockchain'; @@ -13,6 +12,8 @@ interface GenerateOrderFormProps { blockchain: Blockchain; hashData: HashData; dispatcher: Dispatcher; + isFullWidth?: boolean; + shouldHideHeader?: boolean; } interface ConnectedState { @@ -29,7 +30,7 @@ interface ConnectedState { lastForceTokenStateRefetch: number; } -const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: GenerateOrderFormProps): ConnectedState => ({ blockchainErr: state.blockchainErr, blockchainIsLoaded: state.blockchainIsLoaded, orderExpiryTimestamp: state.orderExpiryTimestamp, diff --git a/packages/website/ts/containers/inputs/allowance_toggle.ts b/packages/website/ts/containers/inputs/allowance_toggle.ts new file mode 100644 index 000000000..545708f92 --- /dev/null +++ b/packages/website/ts/containers/inputs/allowance_toggle.ts @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; +import { State } from 'ts/redux/reducer'; +import { BalanceErrs, Token, TokenState } from 'ts/types'; + +import { AllowanceToggle as AllowanceToggleComponent } from 'ts/components/inputs/allowance_toggle'; +import { Dispatcher } from 'ts/redux/dispatcher'; + +interface AllowanceToggleProps { + blockchain: Blockchain; + onErrorOccurred?: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + isDisabled?: boolean; + refetchTokenStateAsync: () => Promise<void>; +} + +interface ConnectedState { + networkId: number; + userAddress: string; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: AllowanceToggleProps): ConnectedState => ({ + networkId: state.networkId, + userAddress: state.userAddress, +}); + +const mapDispatchTopProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const AllowanceToggle: React.ComponentClass<AllowanceToggleProps> = connect( + mapStateToProps, + mapDispatchTopProps, +)(AllowanceToggleComponent); diff --git a/packages/website/ts/containers/jobs.ts b/packages/website/ts/containers/jobs.ts new file mode 100644 index 000000000..b18485882 --- /dev/null +++ b/packages/website/ts/containers/jobs.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Jobs as JobsComponent, JobsProps } from 'ts/pages/jobs/jobs'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; + screenWidth: ScreenWidths; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: JobsProps): ConnectedState => ({ + translate: state.translate, + screenWidth: state.screenWidth, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Jobs: React.ComponentClass<JobsProps> = connect(mapStateToProps, mapDispatchToProps)(JobsComponent); diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts index 154c65ffc..67740d4c6 100644 --- a/packages/website/ts/containers/json_schemas_documentation.ts +++ b/packages/website/ts/containers/json_schemas_documentation.ts @@ -1,14 +1,11 @@ -import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; +import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; -import { constants } from 'ts/utils/constants'; +import { DocPackages } from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ @@ -73,7 +70,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/landing.ts b/packages/website/ts/containers/landing.ts index a620bb12e..972ed4c23 100644 --- a/packages/website/ts/containers/landing.ts +++ b/packages/website/ts/containers/landing.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -15,7 +14,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: LandingProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: LandingProps): ConnectedState => ({ translate: state.translate, }); diff --git a/packages/website/ts/containers/legacy_portal.ts b/packages/website/ts/containers/legacy_portal.ts index eae450c21..e99f47fb7 100644 --- a/packages/website/ts/containers/legacy_portal.ts +++ b/packages/website/ts/containers/legacy_portal.ts @@ -37,7 +37,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: LegacyPortalComponentProps): ConnectedState => { +const mapStateToProps = (state: State, _ownProps: LegacyPortalComponentProps): ConnectedState => { const receiveAssetToken = state.sideToAssetToken[Side.Receive]; const depositAssetToken = state.sideToAssetToken[Side.Deposit]; const receiveAddress = !_.isUndefined(receiveAssetToken.address) diff --git a/packages/website/ts/containers/not_found.ts b/packages/website/ts/containers/not_found.ts index dd151e2c8..f384dab89 100644 --- a/packages/website/ts/containers/not_found.ts +++ b/packages/website/ts/containers/not_found.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -15,7 +14,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: NotFoundProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: NotFoundProps): ConnectedState => ({ translate: state.translate, }); diff --git a/packages/website/ts/containers/order_utils_documentation.ts b/packages/website/ts/containers/order_utils_documentation.ts index 64aa7300f..37b7f2273 100644 --- a/packages/website/ts/containers/order_utils_documentation.ts +++ b/packages/website/ts/containers/order_utils_documentation.ts @@ -1,13 +1,11 @@ -import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; +import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { DocPackages } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -83,7 +81,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/portal.ts b/packages/website/ts/containers/portal.ts index b8c8fb999..5876e65f5 100644 --- a/packages/website/ts/containers/portal.ts +++ b/packages/website/ts/containers/portal.ts @@ -34,7 +34,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: PortalComponentProps): ConnectedState => { +const mapStateToProps = (state: State, _ownProps: PortalComponentProps): ConnectedState => { const receiveAssetToken = state.sideToAssetToken[Side.Receive]; const depositAssetToken = state.sideToAssetToken[Side.Deposit]; const receiveAddress = !_.isUndefined(receiveAssetToken.address) diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts index 0ad9aef13..746adf0ba 100644 --- a/packages/website/ts/containers/portal_onboarding_flow.ts +++ b/packages/website/ts/containers/portal_onboarding_flow.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; import { ActionTypes, ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow'; @@ -9,6 +10,8 @@ import { State } from 'ts/redux/reducer'; interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; + blockchain: Blockchain; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } interface ConnectedState { @@ -28,7 +31,7 @@ interface ConnectedDispatch { updateOnboardingStep: (stepIndex: number) => void; } -const mapStateToProps = (state: State, ownProps: PortalOnboardingFlowProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): ConnectedState => ({ stepIndex: state.portalOnboardingStep, isRunning: state.isPortalOnboardingShowing, userAddress: state.userAddress, diff --git a/packages/website/ts/containers/smart_contracts_documentation.ts b/packages/website/ts/containers/smart_contracts_documentation.ts index b1b2ea922..c88c3b365 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.ts +++ b/packages/website/ts/containers/smart_contracts_documentation.ts @@ -1,13 +1,12 @@ import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; import { Networks } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, SmartContractDocSections as Sections, WebsitePaths } from 'ts/types'; +import { DocPackages, SmartContractDocSections as Sections } from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ @@ -76,7 +75,7 @@ interface ConnectedDispatch { docsInfo: DocsInfo; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/sol_compiler_documentation.ts b/packages/website/ts/containers/sol_compiler_documentation.ts index 2f6486146..8720e2c1d 100644 --- a/packages/website/ts/containers/sol_compiler_documentation.ts +++ b/packages/website/ts/containers/sol_compiler_documentation.ts @@ -1,14 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; -import { constants } from 'ts/utils/constants'; +import { DocPackages } from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ @@ -70,7 +67,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/sol_cov_documentation.ts b/packages/website/ts/containers/sol_cov_documentation.ts index bc05b6854..a8009071f 100644 --- a/packages/website/ts/containers/sol_cov_documentation.ts +++ b/packages/website/ts/containers/sol_cov_documentation.ts @@ -1,14 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; -import { constants } from 'ts/utils/constants'; +import { DocPackages } from 'ts/types'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ @@ -99,7 +96,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts index 2178baea8..6d4230e53 100644 --- a/packages/website/ts/containers/subproviders_documentation.ts +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -1,13 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { DocPackages } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -130,7 +128,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/web3_wrapper_documentation.ts b/packages/website/ts/containers/web3_wrapper_documentation.ts index 17754ca66..b04a83ac4 100644 --- a/packages/website/ts/containers/web3_wrapper_documentation.ts +++ b/packages/website/ts/containers/web3_wrapper_documentation.ts @@ -1,13 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { DocPackages } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -107,7 +105,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, translate: state.translate, diff --git a/packages/website/ts/containers/wiki.ts b/packages/website/ts/containers/wiki.ts index 2cb87d0a1..357f8bbf4 100644 --- a/packages/website/ts/containers/wiki.ts +++ b/packages/website/ts/containers/wiki.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -15,7 +14,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: WikiProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: WikiProps): ConnectedState => ({ translate: state.translate, }); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index f68e2335f..a8890a07a 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -1,13 +1,11 @@ import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; -import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { DocPackages, Environments, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { DocPackages } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -103,6 +101,9 @@ const docsInfoConfig: DocsInfoConfig = { '"0x.js/src/contract_wrappers/generated/ether_token"', '"0x.js/src/contract_wrappers/generated/token"', '"0x.js/src/contract_wrappers/generated/exchange"', + '"0x.js/src/generated_contract_wrappers/ether_token"', + '"0x.js/src/generated_contract_wrappers/token"', + '"0x.js/src/generated_contract_wrappers/exchange"', ], }, menuSubsectionToVersionWhenIntroduced: { @@ -208,7 +209,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ +const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, docsInfo, diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index a6bfe7dd2..249b4fdc9 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -4,9 +4,10 @@ import { render } from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'; import * as injectTapEventPlugin from 'react-tap-event-plugin'; -import { Redirecter } from 'ts/components/redirecter'; +import { Redirector } from 'ts/components/redirector'; import { About } from 'ts/containers/about'; import { FAQ } from 'ts/containers/faq'; +import { Jobs } from 'ts/containers/jobs'; import { Landing } from 'ts/containers/landing'; import { NotFound } from 'ts/containers/not_found'; import { Wiki } from 'ts/containers/wiki'; @@ -69,6 +70,9 @@ const LazySubprovidersDocumentation = createLazyComponent('Documentation', async const LazyOrderUtilsDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "orderUtilsDocs" */ 'ts/containers/order_utils_documentation'), ); +const LazyEthereumTypesDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "ethereumTypesDocs" */ 'ts/containers/ethereum_types_documentation'), +); analytics.init(); // tslint:disable-next-line:no-floating-promises @@ -83,8 +87,12 @@ render( <Switch> <Route exact={true} path="/" component={Landing as any} /> <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> - - <Route path={WebsitePaths.Jobs} component={Redirecter as any} /> + {/* TODO: Remove this once we ship the jobs page*/} + {utils.shouldShowJobsPage() ? ( + <Route path={WebsitePaths.Jobs} component={Jobs as any} /> + ) : ( + <Route path={WebsitePaths.Jobs} component={Redirector as any} /> + )} <Route path={WebsitePaths.Portal} component={LazyPortal} /> <Route path={WebsitePaths.FAQ} component={FAQ as any} /> <Route path={WebsitePaths.About} component={About as any} /> @@ -116,6 +124,10 @@ render( path={`${WebsitePaths.SmartContracts}/:version?`} component={LazySmartContractsDocumentation} /> + <Route + path={`${WebsitePaths.EthereumTypes}/:version?`} + component={LazyEthereumTypesDocumentation} + /> {/* Legacy endpoints */} <Route diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index cc764d98e..2e2f4e64e 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -57,7 +57,7 @@ export const tradeHistoryStorage = { return {}; } const userFillsByHash = JSON.parse(userFillsJSONString); - _.each(userFillsByHash, (fill, hash) => { + _.each(userFillsByHash, fill => { fill.paidMakerFee = new BigNumber(fill.paidMakerFee); fill.paidTakerFee = new BigNumber(fill.paidTakerFee); fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index ac67ca968..3136dbca3 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -8,7 +8,6 @@ import { TopBar } from 'ts/components/top_bar/top_bar'; import { Profile } from 'ts/pages/about/profile'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProfileInfo, WebsitePaths } from 'ts/types'; -import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 17efc56ed..8159bbd49 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -1,5 +1,4 @@ -import { DocAgnosticFormat, DocsInfo, Documentation, DoxityDocObj } from '@0xproject/react-docs'; -import { MenuSubsectionsBySection } from '@0xproject/react-shared'; +import { DocAgnosticFormat, DocsInfo, Documentation } from '@0xproject/react-docs'; import findVersions = require('find-versions'); import * as _ from 'lodash'; import * as React from 'react'; @@ -9,7 +8,6 @@ import { SidebarHeader } from 'ts/components/sidebar_header'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; import { DocPackages } from 'ts/types'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { docUtils } from 'ts/utils/doc_utils'; import { Translate } from 'ts/utils/translate'; @@ -35,6 +33,7 @@ const docIdToSubpackageName: { [id: string]: string } = { [DocPackages.SolCov]: 'sol-cov', [DocPackages.Subproviders]: 'subproviders', [DocPackages.OrderUtils]: 'order-utils', + [DocPackages.EthereumTypes]: 'ethereum-types', }; export interface DocPageProps { diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx index 28ea6881a..f80985257 100644 --- a/packages/website/ts/pages/faq/question.tsx +++ b/packages/website/ts/pages/faq/question.tsx @@ -1,5 +1,4 @@ import { colors } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import { Card, CardHeader, CardText } from 'material-ui/Card'; import * as React from 'react'; diff --git a/packages/website/ts/pages/jobs/benefits.tsx b/packages/website/ts/pages/jobs/benefits.tsx new file mode 100644 index 000000000..006facc83 --- /dev/null +++ b/packages/website/ts/pages/jobs/benefits.tsx @@ -0,0 +1,109 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { FilledImage } from 'ts/components/ui/filled_image'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +const IMAGE_PATHS = ['/images/jobs/location1.png', '/images/jobs/location2.png', '/images/jobs/location3.png']; +const BENEFIT_ITEM_PROPS_LIST: BenefitItemProps[] = [ + { + bulletColor: '#6FCF97', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#56CCF2', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#6FCF97', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#56CCF2', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const LARGE_LAYOUT_HEIGHT = 937; +const LARGE_LAYOUT_BENEFITS_LIST_PADDING_LEFT = 205; +const HEADER_TEXT = 'Benefits'; +const BENEFIT_ITEM_MIN_HEIGHT = 150; + +export interface BenefitsProps { + screenWidth: ScreenWidths; +} + +export const Benefits = (props: BenefitsProps) => ( + <div style={{ backgroundColor: colors.jobsPageBackground }}> + {props.screenWidth === ScreenWidths.Sm ? <SmallLayout /> : <LargeLayout />} + </div> +); + +const LargeLayout = () => ( + <div className="flex" style={{ height: LARGE_LAYOUT_HEIGHT }}> + <div style={{ width: '43%', height: '100%' }}> + <ImageGrid /> + </div> + <div + className="pr4" + style={{ paddingLeft: LARGE_LAYOUT_BENEFITS_LIST_PADDING_LEFT, width: '57%', height: '100%' }} + > + <BenefitsList /> + </div> + </div> +); + +const SmallLayout = () => ( + <div> + <FilledImage src={_.head(IMAGE_PATHS)} /> + <BenefitsList /> + </div> +); + +export const BenefitsList = () => { + return ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(BENEFIT_ITEM_PROPS_LIST, valueItemProps => <BenefitItem {...valueItemProps} />)} + </div> + ); +}; +interface BenefitItemProps { + bulletColor: string; + description: string; +} + +const BenefitItem: React.StatelessComponent<BenefitItemProps> = ({ bulletColor, description }) => ( + <div style={{ minHeight: BENEFIT_ITEM_MIN_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <div style={{ fontSize: 16, lineHeight: 1.5 }}>{description}</div> + </ListItem> + </div> +); + +const ImageGrid = () => ( + <div style={{ width: '100%', height: '100%' }}> + <div className="flex" style={{ height: '67%' }}> + <FilledImage src={IMAGE_PATHS[0]} /> + </div> + <div className="clearfix" style={{ height: '33%' }}> + <div className="col lg-col-6 md-col-6 col-12" style={{ height: '100%' }}> + <FilledImage src={IMAGE_PATHS[1]} /> + </div> + <div className="col lg-col-6 md-col-6 col-12" style={{ height: '100%' }}> + <FilledImage src={IMAGE_PATHS[2]} /> + </div> + </div> + </div> +); diff --git a/packages/website/ts/pages/jobs/jobs.tsx b/packages/website/ts/pages/jobs/jobs.tsx new file mode 100644 index 000000000..314669ee9 --- /dev/null +++ b/packages/website/ts/pages/jobs/jobs.tsx @@ -0,0 +1,81 @@ +import { colors, utils as sharedUtils } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; + +import { Footer } from 'ts/components/footer'; +import { TopBar } from 'ts/components/top_bar/top_bar'; +import { FilledImage } from 'ts/components/ui/filled_image'; +import { Benefits } from 'ts/pages/jobs/benefits'; +import { Join0x } from 'ts/pages/jobs/join_0x'; +import { Mission } from 'ts/pages/jobs/mission'; +import { OpenPositions } from 'ts/pages/jobs/open_positions'; +import { PhotoRail } from 'ts/pages/jobs/photo_rail'; +import { Teams } from 'ts/pages/jobs/teams'; +import { Values } from 'ts/pages/jobs/values'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; +import { utils } from 'ts/utils/utils'; + +const OPEN_POSITIONS_HASH = 'positions'; +const THROTTLE_TIMEOUT = 100; +const PHOTO_RAIL_IMAGES = ['/images/jobs/office1.png', '/images/jobs/office2.png', '/images/jobs/office3.png']; + +export interface JobsProps { + location: Location; + translate: Translate; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; +} + +export interface JobsState {} + +export class Jobs extends React.Component<JobsProps, JobsState> { + // TODO: consolidate this small screen scaffolding into one place (its being used in portal and docs as well) + private _throttledScreenWidthUpdate: () => void; + public constructor(props: JobsProps) { + super(props); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount(): void { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public render(): React.ReactNode { + return ( + <div> + <DocumentTitle title="Jobs" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + style={{ backgroundColor: colors.white, position: 'relative' }} + translate={this.props.translate} + /> + <Join0x onCallToActionClick={this._onJoin0xCallToActionClick.bind(this)} /> + <Mission screenWidth={this.props.screenWidth} /> + {this._isSmallScreen() ? ( + <FilledImage src={_.head(PHOTO_RAIL_IMAGES)} /> + ) : ( + <PhotoRail images={PHOTO_RAIL_IMAGES} /> + )} + <Values /> + <Benefits screenWidth={this.props.screenWidth} /> + <Teams screenWidth={this.props.screenWidth} /> + <OpenPositions hash={OPEN_POSITIONS_HASH} screenWidth={this.props.screenWidth} /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> + </div> + ); + } + private _onJoin0xCallToActionClick(): void { + sharedUtils.setUrlHash(OPEN_POSITIONS_HASH); + } + private _updateScreenWidth(): void { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private _isSmallScreen(): boolean { + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + return isSmallScreen; + } +} diff --git a/packages/website/ts/pages/jobs/join_0x.tsx b/packages/website/ts/pages/jobs/join_0x.tsx new file mode 100644 index 000000000..72cce3b89 --- /dev/null +++ b/packages/website/ts/pages/jobs/join_0x.tsx @@ -0,0 +1,41 @@ +import { colors } from '@0xproject/react-shared'; + +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; + +const BUTTON_TEXT = 'view open positions'; + +export interface Join0xProps { + onCallToActionClick: () => void; +} + +export const Join0x = (props: Join0xProps) => ( + <div className="clearfix center lg-py4 md-py4" style={{ backgroundColor: colors.white, color: colors.black }}> + <div className="mx-auto inline-block align-middle py4" style={{ lineHeight: '44px', textAlign: 'center' }}> + <div className="h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + Join 0x + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h4 sm-center" + style={{ fontFamily: 'Roboto', lineHeight: 2, maxWidth: 537 }} + > + 0x is transforming the way that value is exchanged on a global scale. Come join us in San Francisco or + work remotely anywhere in the world to help create the infrastructure of a new tokenized economy. + </div> + <div className="py3"> + <Button + type="button" + backgroundColor={colors.black} + width="290px" + fontColor={colors.white} + fontSize="18px" + fontFamily="Roboto Mono" + onClick={props.onCallToActionClick} + > + {BUTTON_TEXT} + </Button> + </div> + </div> + </div> +); diff --git a/packages/website/ts/pages/jobs/list/header_item.tsx b/packages/website/ts/pages/jobs/list/header_item.tsx new file mode 100644 index 000000000..ac214904c --- /dev/null +++ b/packages/website/ts/pages/jobs/list/header_item.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; + +export interface HeaderItemProps { + headerText?: string; +} +export const HeaderItem: React.StatelessComponent<HeaderItemProps> = ({ headerText }) => { + return ( + <div className="h2 lg-py4 md-py4 sm-py3"> + <ListItem> + <Text + fontFamily="Roboto Mono" + fontSize="24px" + lineHeight="1.25" + minHeight="1.25em" + fontColor={colors.black} + > + {headerText} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/list/list_item.tsx b/packages/website/ts/pages/jobs/list/list_item.tsx new file mode 100644 index 000000000..d7838bc01 --- /dev/null +++ b/packages/website/ts/pages/jobs/list/list_item.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface ListItemProps { + bulletColor?: string; +} +export const ListItem: React.StatelessComponent<ListItemProps> = ({ bulletColor, children }) => { + return ( + <div className="flex items-center"> + <svg className="flex-none lg-px2 md-px2 sm-pl2" height="26" width="26"> + <circle cx="13" cy="13" r="13" fill={bulletColor || 'transparent'} /> + </svg> + <div className="flex-auto px2">{children}</div> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/mission.tsx b/packages/website/ts/pages/jobs/mission.tsx new file mode 100644 index 000000000..f7f874e04 --- /dev/null +++ b/packages/website/ts/pages/jobs/mission.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; + +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +export interface MissionProps { + screenWidth: ScreenWidths; +} +export const Mission = (props: MissionProps) => { + const isSmallScreen = props.screenWidth === ScreenWidths.Sm; + const image = ( + <div className="col lg-col-6 md-col-6 col-12 sm-py2 px2 center"> + <img src="/images/jobs/map.png" style={{ width: '100%' }} /> + </div> + ); + const missionStatementStyle = !isSmallScreen ? { height: 364, lineHeight: '364px' } : undefined; + const missionStatement = ( + <div className="col lg-col-6 md-col-6 col-12 center" style={missionStatementStyle}> + <div + className="mx-auto inline-block align-middle" + style={{ maxWidth: 385, lineHeight: '44px', textAlign: 'center' }} + > + <div className="h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + Our Mission + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h4 sm-center" + style={{ fontFamily: 'Roboto', lineHeight: 2, maxWidth: 537 }} + > + We believe a system can exist in which all world value is accessible to anyone, anywhere, regardless + of where you happen to be born. + </div> + </div> + </div> + ); + return ( + <div + className="container lg-py4 md-py4" + style={{ backgroundColor: colors.jobsPageBackground, color: colors.black }} + > + <div className="mx-auto clearfix sm-py4"> + {isSmallScreen ? ( + <div> + {missionStatement} + {image} + </div> + ) : ( + <div> + {image} + {missionStatement} + </div> + )} + </div> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx new file mode 100644 index 000000000..f3d980315 --- /dev/null +++ b/packages/website/ts/pages/jobs/open_positions.tsx @@ -0,0 +1,192 @@ +import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; +import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; +import * as React from 'react'; + +import { Retry } from 'ts/components/ui/retry'; +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; +import { ScreenWidths, WebsiteBackendJobInfo } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; + +const labelStyle = { fontFamily: 'Roboto Mono', fontSize: 18 }; +const HEADER_TEXT = 'Open Positions'; +const TABLE_ROW_MIN_HEIGHT = 100; + +export interface OpenPositionsProps { + hash: string; + screenWidth: ScreenWidths; +} +export interface OpenPositionsState { + jobInfos?: WebsiteBackendJobInfo[]; + error?: Error; +} + +export class OpenPositions extends React.Component<OpenPositionsProps, OpenPositionsState> { + private _isUnmounted: boolean; + constructor(props: OpenPositionsProps) { + super(props); + this._isUnmounted = false; + this.state = { + jobInfos: undefined, + error: undefined, + }; + } + public componentWillMount(): void { + // tslint:disable-next-line:no-floating-promises + this._fetchJobInfosAsync(); + } + public componentWillUnmount(): void { + this._isUnmounted = true; + } + public render(): React.ReactNode { + const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.jobInfos); + return ( + <div id={this.props.hash} className="mx-auto max-width-4"> + {isReadyToRender ? this._renderBody() : this._renderLoading()} + </div> + ); + } + private _renderBody(): React.ReactNode { + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + return isSmallScreen ? this._renderList() : this._renderTable(); + } + private _renderLoading(): React.ReactNode { + return ( + // TODO: consolidate this loading component with the one in portal and RelayerIndex + // TODO: possibly refactor into a generic loading container with spinner and retry UI + <div className="center"> + {_.isUndefined(this.state.error) ? ( + <CircularProgress size={40} thickness={5} /> + ) : ( + <Retry onRetry={this._fetchJobInfosAsync.bind(this)} /> + )} + </div> + ); + } + private _renderList(): React.ReactNode { + return ( + <div style={{ backgroundColor: colors.jobsPageBackground }}> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(this.state.jobInfos, jobInfo => ( + <JobInfoListItem + key={jobInfo.id} + title={jobInfo.title} + description={jobInfo.department} + onClick={this._openJobInfoUrl.bind(this, jobInfo)} + /> + ))} + </div> + ); + } + private _renderTable(): React.ReactNode { + return ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + <Table selectable={false} onCellClick={this._onCellClick.bind(this)}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn colSpan={5} style={labelStyle}> + Position + </TableHeaderColumn> + <TableHeaderColumn colSpan={3} style={labelStyle}> + Department + </TableHeaderColumn> + <TableHeaderColumn colSpan={4} style={labelStyle}> + Office + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false} showRowHover={true}> + {_.map(this.state.jobInfos, jobInfo => { + return this._renderJobInfoTableRow(jobInfo); + })} + </TableBody> + </Table> + </div> + ); + } + private _renderJobInfoTableRow(jobInfo: WebsiteBackendJobInfo): React.ReactNode { + return ( + <TableRow + key={jobInfo.id} + hoverable={true} + displayBorder={false} + style={{ height: TABLE_ROW_MIN_HEIGHT, border: 2 }} + > + <TableRowColumn colSpan={5} style={labelStyle}> + {jobInfo.title} + </TableRowColumn> + <TableRowColumn colSpan={3} style={labelStyle}> + {jobInfo.department} + </TableRowColumn> + <TableRowColumn colSpan={4} style={labelStyle}> + {jobInfo.office} + </TableRowColumn> + </TableRow> + ); + } + private async _fetchJobInfosAsync(): Promise<void> { + try { + if (!this._isUnmounted) { + this.setState({ + jobInfos: undefined, + error: undefined, + }); + } + const jobInfos = await backendClient.getJobInfosAsync(); + if (!this._isUnmounted) { + this.setState({ + jobInfos, + }); + } + } catch (error) { + if (!this._isUnmounted) { + this.setState({ + error, + }); + } + } + } + private _onCellClick(rowNumber: number): void { + if (_.isUndefined(this.state.jobInfos)) { + return; + } + const jobInfo = this.state.jobInfos[rowNumber]; + this._openJobInfoUrl(jobInfo); + } + + private _openJobInfoUrl(jobInfo: WebsiteBackendJobInfo): void { + const url = jobInfo.url; + window.open(url, '_blank'); + } +} + +export interface JobInfoListItemProps { + title?: string; + description?: string; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; +} + +const PlainJobInfoListItem: React.StatelessComponent<JobInfoListItemProps> = ({ title, description, onClick }) => ( + <div className="mb3" onClick={onClick}> + <ListItem> + <Text fontWeight="bold" fontSize="16px" fontColor={colors.mediumBlue}> + {title + ' ›'} + </Text> + <Text className="pt1" fontSize="16px" fontColor={colors.darkGrey}> + {description} + </Text> + </ListItem> + </div> +); + +export const JobInfoListItem = styled(PlainJobInfoListItem)` + cursor: pointer; + &:hover { + opacity: 0.5; + } +`; diff --git a/packages/website/ts/pages/jobs/photo_rail.tsx b/packages/website/ts/pages/jobs/photo_rail.tsx new file mode 100644 index 000000000..acc9dcb91 --- /dev/null +++ b/packages/website/ts/pages/jobs/photo_rail.tsx @@ -0,0 +1,22 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { FilledImage } from 'ts/components/ui/filled_image'; + +export interface PhotoRailProps { + images: string[]; +} + +export const PhotoRail = (props: PhotoRailProps) => { + return ( + <div className="clearfix" style={{ height: 490 }}> + {_.map(props.images, (image: string) => { + return ( + <div key={image} className="col lg-col-4 md-col-4 col-12 center" style={{ height: '100%' }}> + <FilledImage src={image} /> + </div> + ); + })} + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/teams.tsx b/packages/website/ts/pages/jobs/teams.tsx new file mode 100644 index 000000000..80b036396 --- /dev/null +++ b/packages/website/ts/pages/jobs/teams.tsx @@ -0,0 +1,90 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +const TEAM_ITEM_PROPS_COLUMN1: TeamItemProps[] = [ + { + bulletColor: '#EB5757', + title: 'User Growth', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + title: 'Governance', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const TEAM_ITEM_PROPS_COLUMN2: TeamItemProps[] = [ + { + bulletColor: '#EB5757', + title: 'Developer Tools', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + title: 'Marketing', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const HEADER_TEXT = 'Our Teams'; +const MINIMUM_ITEM_HEIGHT = 240; + +export interface TeamsProps { + screenWidth: ScreenWidths; +} + +export const Teams = (props: TeamsProps) => (props.screenWidth === ScreenWidths.Sm ? <SmallLayout /> : <LargeLayout />); + +const LargeLayout = () => ( + <div className="mx-auto max-width-4 clearfix pb4"> + <div className="col lg-col-6 md-col-6 col-12"> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(TEAM_ITEM_PROPS_COLUMN1, teamItemProps => <TeamItem {...teamItemProps} />)} + </div> + <div className="col lg-col-6 md-col-6 col-12"> + <HeaderItem headerText=" " /> + {_.map(TEAM_ITEM_PROPS_COLUMN2, teamItemProps => <TeamItem {...teamItemProps} />)} + </div> + </div> +); + +const SmallLayout = () => ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(_.concat(TEAM_ITEM_PROPS_COLUMN1, TEAM_ITEM_PROPS_COLUMN2), teamItemProps => ( + <TeamItem {...teamItemProps} /> + ))} + </div> +); + +interface TeamItemProps { + bulletColor: string; + title: string; + description: string; +} + +export const TeamItem: React.StatelessComponent<TeamItemProps> = ({ bulletColor, title, description }) => { + return ( + <div style={{ minHeight: MINIMUM_ITEM_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <Text fontWeight="bold" fontSize="16px" fontColor={colors.black}> + {title} + </Text> + </ListItem> + <ListItem> + <Text className="pt1" fontSize="16px" lineHeight="2em" fontColor={colors.black}> + {description} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/values.tsx b/packages/website/ts/pages/jobs/values.tsx new file mode 100644 index 000000000..c7c4d5726 --- /dev/null +++ b/packages/website/ts/pages/jobs/values.tsx @@ -0,0 +1,60 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; + +const VALUE_ITEM_PROPS_LIST: ValueItemProps[] = [ + { + bulletColor: '#6FCF97', + title: 'Ethics/Doing the right thing', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + bulletColor: '#56CCF2', + title: 'Consistently ship', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + bulletColor: '#EB5757', + title: 'Focus on long term impact', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, +]; + +const HEADER_TEXT = 'Our Values'; +const VALUE_ITEM_MIN_HEIGHT = 150; + +export const Values = () => { + return ( + <div className="mx-auto max-width-4"> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(VALUE_ITEM_PROPS_LIST, valueItemProps => <ValueItem {...valueItemProps} />)} + </div> + ); +}; + +interface ValueItemProps { + bulletColor: string; + title: string; + description: string; +} + +export const ValueItem: React.StatelessComponent<ValueItemProps> = ({ bulletColor, title, description }) => { + return ( + <div style={{ minHeight: VALUE_ITEM_MIN_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <Text fontWeight="bold" fontSize="16x" fontColor={colors.black}> + {title} + </Text> + </ListItem> + <ListItem> + <Text className="pt1" fontSize="16x" lineHeight="2em" fontColor={colors.black}> + {description} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 02ecfa117..f091778f4 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -1,11 +1,13 @@ import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; -import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { Link } from 'react-router-dom'; import { Footer } from 'ts/components/footer'; +import { SubscribeForm } from 'ts/components/forms/subscribe_form'; import { TopBar } from 'ts/components/top_bar/top_bar'; +import { CallToAction } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; @@ -220,23 +222,12 @@ export class Landing extends React.Component<LandingProps, LandingState> { } private _renderHero(): React.ReactNode { 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-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"> {this._renderWhatsNew()} - <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> + <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-mt4 md-mt4 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> @@ -268,40 +259,31 @@ export class Landing extends React.Component<LandingProps, LandingState> { > {this.props.translate.get(Key.TopTagline)} </div> - <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 389 }}> - <div className="lg-pr2 md-pr2 col col-6 sm-center"> + <Container className="pt3 clearfix sm-mx-auto" maxWidth="390px"> + <div className="lg-pr2 md-pr2 lg-col lg-col-6 sm-center sm-col sm-col-12 mb2"> <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 157.36 }} - buttonStyle={{ borderRadius: 6 }} - labelStyle={buttonLabelStyle} - label={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction width="175px" type="light"> + {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + </CallToAction> </Link> </div> - <div className="col col-6 sm-center"> + <div className="lg-col lg-col-6 sm-center sm-col sm-col-12"> <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={this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction width="175px"> + {this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} + </CallToAction> </a> </div> - </div> + </Container> </div> </div> </div> </div> + {this.props.translate.getLanguage() === Language.English && <SubscribeForm />} </div> ); } @@ -349,6 +331,9 @@ export class Landing extends React.Component<LandingProps, LandingState> { case ScreenWidths.Lg: colWidth = isRelayersOnly ? 2 : 2 - i % 2; break; + + default: + throw new Error(`Encountered unknown ScreenWidths value: ${this.state.screenWidth}`); } return ( <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> @@ -753,17 +738,6 @@ export class Landing extends React.Component<LandingProps, LandingState> { } private _renderCallToAction(): React.ReactNode { 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 ${colors.grey500}`, - lineHeight: '33px', - height: 49, - }; const callToActionClassNames = 'lg-pr3 md-pr3 lg-right-align md-right-align sm-center sm-px3 h4 lg-table-cell md-table-cell'; return ( @@ -782,15 +756,9 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> <div className="sm-center sm-pt2 lg-table-cell md-table-cell"> <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={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction fontSize="15px"> + {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + </CallToAction> </Link> </div> </div> @@ -806,7 +774,4 @@ export class Landing extends React.Component<LandingProps, LandingState> { }); } } - private _onLanguageSelected(language: Language): void { - this.props.dispatcher.updateSelectedLanguage(language); - } } // 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 674271636..2fe3b1f45 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -1,5 +1,3 @@ -import { Styles } from '@0xproject/react-shared'; -import * as _ from 'lodash'; import * as React from 'react'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; @@ -13,7 +11,7 @@ export interface NotFoundProps { dispatcher: Dispatcher; } -export const NotFound = (props: NotFoundProps) => { +export const NotFound = (_props: NotFoundProps) => { return ( <div> <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} /> diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index 720c1cc37..9659900be 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -4,23 +4,19 @@ import { HeaderSizes, MarkdownSection, NestedSidebarMenu, - SectionHeader, Styles, utils as sharedUtils, } from '@0xproject/react-shared'; -import { logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); -import { scroller } from 'react-scroll'; import { SidebarHeader } from 'ts/components/sidebar_header'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Article, ArticlesBySection, WebsitePaths } from 'ts/types'; +import { Article, ArticlesBySection } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; @@ -237,7 +233,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { } return menuSubsectionsBySection; } - private _onSidebarHover(event: React.FormEvent<HTMLInputElement>): void { + private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void { this.setState({ isHoveringSidebar: true, }); @@ -247,7 +243,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { isHoveringSidebar: false, }); } - private _onHashChanged(event: any): void { + private _onHashChanged(_event: any): void { const hash = window.location.hash.slice(1); sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); } diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts index dac0f0098..3fd013002 100644 --- a/packages/website/ts/schemas/validator.ts +++ b/packages/website/ts/schemas/validator.ts @@ -1,5 +1,4 @@ import { SchemaValidator } from '@0xproject/json-schemas'; -import { Schema as JSONSchema, Validator } from 'jsonschema'; import { orderMetadataSchema } from 'ts/schemas/metadata_schema'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { portalTokenMetadataSchema } from 'ts/schemas/portal_token_metadata'; diff --git a/packages/website/ts/utils/colors.ts b/packages/website/ts/style/colors.ts index 5ffdd6ba7..b15000d7a 100644 --- a/packages/website/ts/utils/colors.ts +++ b/packages/website/ts/style/colors.ts @@ -11,6 +11,8 @@ const appColors = { wrapEtherConfirmationButton: sharedColors.mediumBlue, drawerMenuBackground: '#4a4a4a', menuItemDefaultSelectedBackground: '#424242', + jobsPageBackground: sharedColors.grey50, + jobsPageOpenPositionRow: sharedColors.grey100, }; export const colors = { diff --git a/packages/website/ts/style/theme.ts b/packages/website/ts/style/theme.ts new file mode 100644 index 000000000..ce7d6975d --- /dev/null +++ b/packages/website/ts/style/theme.ts @@ -0,0 +1,17 @@ +import * as styledComponents from 'styled-components'; + +// tslint:disable:no-unnecessary-type-assertion +const { + default: styled, + css, + injectGlobal, + keyframes, + ThemeProvider, +} = styledComponents as styledComponents.ThemedStyledComponentsModule<IThemeInterface>; +// tslint:enable:no-unnecessary-type-assertion + +export interface IThemeInterface {} + +export const theme = {}; + +export { styled, css, injectGlobal, keyframes, ThemeProvider }; diff --git a/packages/website/ts/utils/style.ts b/packages/website/ts/style/z_index.ts index 0411cdd91..0411cdd91 100644 --- a/packages/website/ts/utils/style.ts +++ b/packages/website/ts/style/z_index.ts diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 9567e129b..24e86a901 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -1,6 +1,5 @@ import { ECSignature } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; import * as React from 'react'; export enum Side { @@ -370,6 +369,7 @@ export enum WebsitePaths { SolCov = '/docs/sol-cov', Subproviders = '/docs/subproviders', OrderUtils = '/docs/order-utils', + EthereumTypes = '/docs/ethereum-types', Jobs = '/jobs', } @@ -383,6 +383,7 @@ export enum DocPackages { SolCov = 'SOL_COV', Subproviders = 'SUBPROVIDERS', OrderUtils = 'ORDER_UTILS', + EthereumTypes = 'ETHEREUM_TYPES', } export enum Key { @@ -434,6 +435,7 @@ export enum Key { SolCompiler = 'SOL_COMPILER', JsonSchemas = 'JSON_SCHEMAS', SolCov = 'SOL_COV', + EthereumTypes = 'ETHEREUM_TYPES', Subproviders = 'SUBPROVIDERS', Blog = 'BLOG', Forum = 'FORUM', @@ -534,4 +536,12 @@ export interface WebsiteBackendTokenInfo { export interface WebsiteBackendGasInfo { average: number; } + +export interface WebsiteBackendJobInfo { + id: number; + title: string; + department: string; + office: string; + url: string; +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts index c440b1604..835a6ef4d 100644 --- a/packages/website/ts/utils/backend_client.ts +++ b/packages/website/ts/utils/backend_client.ts @@ -1,19 +1,31 @@ import * as _ from 'lodash'; -import { ArticlesBySection, WebsiteBackendGasInfo, WebsiteBackendPriceInfo, WebsiteBackendRelayerInfo } from 'ts/types'; +import { + ArticlesBySection, + WebsiteBackendGasInfo, + WebsiteBackendJobInfo, + WebsiteBackendPriceInfo, + WebsiteBackendRelayerInfo, +} from 'ts/types'; import { fetchUtils } from 'ts/utils/fetch_utils'; import { utils } from 'ts/utils/utils'; const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station'; +const JOBS_ENDPOINT = '/jobs'; const PRICES_ENDPOINT = '/prices'; const RELAYERS_ENDPOINT = '/relayers'; const WIKI_ENDPOINT = '/wiki'; +const SUBSCRIBE_SUBSTACK_NEWSLETTER_ENDPOINT = '/newsletter_subscriber/substack'; export const backendClient = { async getGasInfoAsync(): Promise<WebsiteBackendGasInfo> { const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), ETH_GAS_STATION_ENDPOINT); return result; }, + async getJobInfosAsync(): Promise<WebsiteBackendJobInfo[]> { + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), JOBS_ENDPOINT); + return result; + }, async getPriceInfoAsync(tokenSymbols: string[]): Promise<WebsiteBackendPriceInfo> { if (_.isEmpty(tokenSymbols)) { return {}; @@ -33,4 +45,11 @@ export const backendClient = { const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), WIKI_ENDPOINT); return result; }, + async subscribeToNewsletterAsync(email: string): Promise<Response> { + const result = await fetchUtils.postAsync(utils.getBackendBaseUrl(), SUBSCRIBE_SUBSTACK_NEWSLETTER_ENDPOINT, { + email, + referrer: window.location.href, + }); + return result; + }, }; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index e72a7f201..ace8a5ba0 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,6 +1,5 @@ import * as _ from 'lodash'; import { Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; -import { utils } from 'ts/utils/utils'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 9dc1d492c..d281c5738 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -1,4 +1,3 @@ -import { Networks } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; export const constants = { diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 2a599bcbe..7768835fb 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -3,12 +3,11 @@ import { logUtils } from '@0xproject/utils'; import findVersions = require('find-versions'); import * as _ from 'lodash'; import { S3FileObject, VersionToFilePath } from 'ts/types'; -import { utils } from 'ts/utils/utils'; import convert = require('xml-js'); export const docUtils = { async getVersionToFilePathAsync(s3DocJsonRoot: string, folderName: string): Promise<VersionToFilePath> { - const versionFilePaths = await this.getVersionFileNamesAsync(s3DocJsonRoot, folderName); + const versionFilePaths = await docUtils.getVersionFileNamesAsync(s3DocJsonRoot, folderName); const versionToFilePath: VersionToFilePath = {}; _.each(versionFilePaths, filePath => { const [version] = findVersions(filePath); diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts index 19f563880..f875141fe 100644 --- a/packages/website/ts/utils/error_reporter.ts +++ b/packages/website/ts/utils/error_reporter.ts @@ -2,7 +2,6 @@ import { logUtils } from '@0xproject/utils'; import { Environments } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; // Suggested way to include Rollbar with Webpack // https://github.com/rollbar/rollbar.js/tree/master/examples/webpack @@ -38,7 +37,7 @@ export const errorReporter = { return; // Let's not log development errors to rollbar } - return new Promise((resolve, reject) => { + return new Promise((resolve, _reject) => { rollbar.error(err, (rollbarErr: Error) => { if (rollbarErr) { logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`); diff --git a/packages/website/ts/utils/fetch_utils.ts b/packages/website/ts/utils/fetch_utils.ts index d2e902db5..513f7e479 100644 --- a/packages/website/ts/utils/fetch_utils.ts +++ b/packages/website/ts/utils/fetch_utils.ts @@ -4,22 +4,38 @@ import * as queryString from 'query-string'; import { errorReporter } from 'ts/utils/error_reporter'; +const logErrorIfPresent = (response: Response, requestedURL: string) => { + if (response.status !== 200) { + const errorText = `Error requesting url: ${requestedURL}, ${response.status}: ${response.statusText}`; + logUtils.log(errorText); + const error = Error(errorText); + // tslint:disable-next-line:no-floating-promises + errorReporter.reportAsync(error); + throw error; + } +}; + export const fetchUtils = { async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> { const query = queryStringFromQueryParams(queryParams); const url = `${baseUrl}${path}${query}`; const response = await fetch(url); - if (response.status !== 200) { - const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`; - logUtils.log(errorText); - const error = Error(errorText); - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(error); - throw error; - } + logErrorIfPresent(response, url); const result = await response.json(); return result; }, + async postAsync(baseUrl: string, path: string, body: object): Promise<Response> { + const url = `${baseUrl}${path}`; + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + logErrorIfPresent(response, url); + return response; + }, }; function queryStringFromQueryParams(queryParams?: object): string { diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index b9d962b75..fdee264b2 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -1,6 +1,6 @@ import { ContractWrappersError, ExchangeContractErrs } from '@0xproject/contract-wrappers'; import { OrderError } from '@0xproject/order-utils'; -import { constants as sharedConstants, EtherscanLinkSuffixes, Networks } from '@0xproject/react-shared'; +import { constants as sharedConstants, Networks } from '@0xproject/react-shared'; import { ECSignature, Provider } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import deepEqual = require('deep-equal'); @@ -33,9 +33,6 @@ export const utils = { throw new Error(message); } }, - spawnSwitchErr(name: string, value: any): Error { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, isNumeric(n: string): boolean { return !isNaN(parseFloat(n)) && isFinite(Number(n)); }, @@ -155,7 +152,7 @@ export const utils = { const intervalId = setTimeout(() => { resolve(false); }, getApiVersionTimeoutMs); - u2f.getApiVersion((version: number) => { + u2f.getApiVersion((_version: number) => { clearTimeout(intervalId); resolve(true); }); @@ -282,7 +279,7 @@ export const utils = { if (document.readyState === 'complete') { return; // Already loaded } - return new Promise<void>((resolve, reject) => { + return new Promise<void>((resolve, _reject) => { window.onload = () => resolve(); }); }, @@ -321,9 +318,18 @@ export const utils = { shouldShowPortalV2(): boolean { return this.isDevelopment() || this.isStaging() || this.isDogfood(); }, + shouldShowJobsPage(): boolean { + return this.isDevelopment() || this.isStaging() || this.isDogfood(); + }, getEthToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress); + }, + getZrxToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ZRX_TOKEN_SYMBOL, tokenByAddress); + }, + getTokenBySymbol(symbol: string, tokenByAddress: TokenByAddress): Token { const tokens = _.values(tokenByAddress); - const etherToken = _.find(tokens, { symbol: constants.ETHER_TOKEN_SYMBOL }); - return etherToken; + const token = _.find(tokens, { symbol }); + return token; }, }; diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts index 6b038efd2..9d6033d74 100644 --- a/packages/website/ts/utils/wallet_item_styles.ts +++ b/packages/website/ts/utils/wallet_item_styles.ts @@ -1,6 +1,6 @@ import { Styles } from '@0xproject/react-shared'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; export const styles: Styles = { focusedItem: { |