aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website')
-rw-r--r--packages/website/package.json1
-rw-r--r--packages/website/ts/blockchain.ts4
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx3
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx60
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx11
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx28
-rw-r--r--packages/website/ts/components/legacy_portal/legacy_portal.tsx2
-rw-r--r--packages/website/ts/components/portal/back_button.tsx43
-rw-r--r--packages/website/ts/components/portal/drawer_menu.tsx68
-rw-r--r--packages/website/ts/components/portal/loading.tsx21
-rw-r--r--packages/website/ts/components/portal/menu.tsx116
-rw-r--r--packages/website/ts/components/portal/portal.tsx279
-rw-r--r--packages/website/ts/components/portal/section.tsx15
-rw-r--r--packages/website/ts/components/portal/text_header.tsx21
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx3
-rw-r--r--packages/website/ts/components/relayer_index/relayer_index.tsx44
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx8
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx25
-rw-r--r--packages/website/ts/components/ui/input_label.tsx4
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx92
-rw-r--r--packages/website/ts/components/wallet/wallet_disconnected_item.tsx3
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx53
-rw-r--r--packages/website/ts/index.tsx17
-rw-r--r--packages/website/ts/pages/fullscreen_message.tsx30
-rw-r--r--packages/website/ts/pages/not_found.tsx43
-rw-r--r--packages/website/ts/types.ts1
-rw-r--r--packages/website/ts/utils/colors.ts19
-rw-r--r--packages/website/ts/utils/utils.ts3
-rw-r--r--packages/website/ts/utils/wallet_item_styles.ts4
-rw-r--r--packages/website/webpack.config.js12
30 files changed, 845 insertions, 188 deletions
diff --git a/packages/website/package.json b/packages/website/package.json
index a27dd8b79..7d49581cd 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -88,6 +88,7 @@
"tslint": "5.8.0",
"tslint-config-0xproject": "^0.0.2",
"typescript": "2.7.1",
+ "uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "^3.1.0",
"webpack-dev-middleware": "^1.10.0",
"webpack-dev-server": "^2.5.0"
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index d7f7ebc8f..32acb9d43 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -787,7 +787,9 @@ export class Blockchain {
const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists);
this.networkId = !_.isUndefined(networkIdIfExists)
? networkIdIfExists
- : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
+ : configs.IS_MAINNET_ENABLED
+ ? constants.NETWORK_ID_MAINNET
+ : constants.NETWORK_ID_KOVAN;
this._dispatcher.updateNetworkId(this.networkId);
const zeroExConfigs = {
networkId: this.networkId,
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index 48c7f9f57..d61dfa87d 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -1,4 +1,4 @@
-import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared';
+import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import Toggle from 'material-ui/Toggle';
@@ -7,6 +7,7 @@ import { Blockchain } from 'ts/blockchain';
import { Dispatcher } from 'ts/redux/dispatcher';
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';
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
index 68b77cfc3..e5b502b25 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -5,7 +5,7 @@ import TextField from 'material-ui/TextField';
import * as React from 'react';
import { Link } from 'react-router-dom';
import { RequiredLabel } from 'ts/components/ui/required_label';
-import { InputErrMsg, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
+import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
import { utils } from 'ts/utils/utils';
interface BalanceBoundedInputProps {
@@ -14,18 +14,21 @@ interface BalanceBoundedInputProps {
amount?: BigNumber;
hintText?: string;
onChange: ValidatedBigNumberCallback;
+ onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
shouldShowIncompleteErrs?: boolean;
shouldCheckBalance: boolean;
- validate?: (amount: BigNumber) => InputErrMsg;
+ validate?: (amount: BigNumber) => React.ReactNode;
onVisitBalancesPageClick?: () => void;
shouldHideVisitBalancesLink?: boolean;
isDisabled?: boolean;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
+ inputStyle?: React.CSSProperties;
+ inputHintStyle?: React.CSSProperties;
}
interface BalanceBoundedInputState {
- errMsg: InputErrMsg;
+ errMsg: React.ReactNode;
amountString: string;
}
@@ -36,6 +39,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
isDisabled: false,
shouldShowErrs: true,
hintText: 'amount',
+ onErrorMsgChange: _.noop,
shouldShowUnderline: true,
};
constructor(props: BalanceBoundedInputProps) {
@@ -63,17 +67,11 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
}
if (shouldResetState) {
const amountString = nextProps.amount.toString();
- this.setState({
- errMsg: this._validate(amountString, nextProps.balance),
- amountString,
- });
+ this._setAmountState(amountString, nextProps.balance);
}
} else if (isCurrentAmountNumeric) {
const amountString = '';
- this.setState({
- errMsg: this._validate(amountString, nextProps.balance),
- amountString,
- });
+ this._setAmountState(amountString, nextProps.balance);
}
}
public render(): React.ReactNode {
@@ -99,29 +97,25 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
hintText={<span style={{ textTransform: 'capitalize' }}>{this.props.hintText}</span>}
onChange={this._onValueChange.bind(this)}
underlineStyle={{ width: 'calc(100% + 50px)' }}
+ inputStyle={this.props.inputStyle}
+ hintStyle={this.props.inputHintStyle}
underlineShow={this.props.shouldShowUnderline}
disabled={this.props.isDisabled}
/>
);
}
private _onValueChange(e: any, amountString: string): void {
- const errMsg = this._validate(amountString, this.props.balance);
- this.setState(
- {
- amountString,
- errMsg,
- },
- () => {
- const isValid = _.isUndefined(errMsg);
- if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) {
- this.props.onChange(isValid, new BigNumber(amountString));
- } else {
- this.props.onChange(isValid);
- }
- },
- );
+ this._setAmountState(amountString, this.props.balance, () => {
+ const isValid = _.isUndefined(this._validate(amountString, this.props.balance));
+ const isPositiveNumber = utils.isNumeric(amountString) && !_.includes(amountString, '-');
+ if (isPositiveNumber) {
+ this.props.onChange(isValid, new BigNumber(amountString));
+ } else {
+ this.props.onChange(isValid);
+ }
+ });
}
- private _validate(amountString: string, balance: BigNumber): InputErrMsg {
+ private _validate(amountString: string, balance: BigNumber): React.ReactNode {
if (!utils.isNumeric(amountString)) {
return amountString !== '' ? 'Must be a number' : '';
}
@@ -161,4 +155,16 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
);
}
}
+
+ private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void {
+ const errorMsg = this._validate(amount, balance);
+ this.props.onErrorMsgChange(errorMsg);
+ this.setState(
+ {
+ amountString: amount,
+ errMsg: errorMsg,
+ },
+ callback,
+ );
+ }
}
diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx
index c3822a80b..fa684d85c 100644
--- a/packages/website/ts/components/inputs/eth_amount_input.tsx
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -12,6 +12,7 @@ interface EthAmountInputProps {
amount?: BigNumber;
hintText?: string;
onChange: ValidatedBigNumberCallback;
+ onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
shouldShowIncompleteErrs: boolean;
onVisitBalancesPageClick?: () => void;
shouldCheckBalance: boolean;
@@ -19,6 +20,8 @@ interface EthAmountInputProps {
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
style?: React.CSSProperties;
+ labelStyle?: React.CSSProperties;
+ inputHintStyle?: React.CSSProperties;
}
interface EthAmountInputState {}
@@ -40,6 +43,7 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
balance={this.props.balance}
amount={amount}
onChange={this._onChange.bind(this)}
+ onErrorMsgChange={this.props.onErrorMsgChange}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
@@ -47,8 +51,10 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
shouldShowUnderline={this.props.shouldShowUnderline}
+ inputStyle={this.props.style}
+ inputHintStyle={this.props.inputHintStyle}
/>
- <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div>
+ <div style={this._getLabelStyle()}>ETH</div>
</div>
);
}
@@ -58,4 +64,7 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
: ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH);
this.props.onChange(isValid, baseUnitAmountIfExists);
}
+ private _getLabelStyle(): React.CSSProperties {
+ return this.props.labelStyle || { paddingTop: _.isUndefined(this.props.label) ? 15 : 40 };
+ }
}
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index 9a74bdd51..f040928f1 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input';
-import { InputErrMsg, Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
+import { Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
interface TokenAmountInputProps {
userAddress: string;
@@ -20,11 +20,14 @@ interface TokenAmountInputProps {
shouldCheckBalance: boolean;
shouldCheckAllowance: boolean;
onChange: ValidatedBigNumberCallback;
+ onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
onVisitBalancesPageClick?: () => void;
lastForceTokenStateRefetch: number;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
style?: React.CSSProperties;
+ labelStyle?: React.CSSProperties;
+ inputHintStyle?: React.CSSProperties;
}
interface TokenAmountInputState {
@@ -74,17 +77,14 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
const amount = this.props.amount
? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals)
: undefined;
- const hasLabel = !_.isUndefined(this.props.label);
- const style = !_.isUndefined(this.props.style)
- ? this.props.style
- : { height: hasLabel ? HEIGHT_WITH_LABEL : HEIGHT_WITHOUT_LABEL };
return (
- <div className="flex overflow-hidden" style={style}>
+ <div className="flex overflow-hidden" style={this._getStyle()}>
<BalanceBoundedInput
label={this.props.label}
amount={amount}
balance={ZeroEx.toUnitAmount(this.state.balance, this.props.token.decimals)}
onChange={this._onChange.bind(this)}
+ onErrorMsgChange={this.props.onErrorMsgChange}
validate={this._validate.bind(this)}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
@@ -93,8 +93,10 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
shouldShowUnderline={this.props.shouldShowUnderline}
+ inputStyle={this.props.style}
+ inputHintStyle={this.props.inputHintStyle}
/>
- <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
+ <div style={this._getLabelStyle()}>{this.props.token.symbol}</div>
</div>
);
}
@@ -105,7 +107,7 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
}
this.props.onChange(isValid, baseUnitAmount);
}
- private _validate(amount: BigNumber): InputErrMsg {
+ private _validate(amount: BigNumber): React.ReactNode {
if (this.props.shouldCheckAllowance && amount.gt(this.state.allowance)) {
return (
<span>
@@ -139,4 +141,14 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
});
}
}
+ private _getStyle(): React.CSSProperties {
+ const hasLabel = !_.isUndefined(this.props.label);
+ return !_.isUndefined(this.props.style)
+ ? this.props.style
+ : { height: hasLabel ? HEIGHT_WITH_LABEL : HEIGHT_WITHOUT_LABEL };
+ }
+ private _getLabelStyle(): React.CSSProperties {
+ const hasLabel = !_.isUndefined(this.props.label);
+ return this.props.labelStyle || { paddingTop: hasLabel ? 39 : 14 };
+ }
}
diff --git a/packages/website/ts/components/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
index 002b258fb..a5ea95629 100644
--- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx
+++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
@@ -217,7 +217,7 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
/>
<Route
path={`${WebsitePaths.Portal}/trades`}
- component={this._renderTradeHistory.bind(this)}
+ render={this._renderTradeHistory.bind(this)}
/>
<Route
path={`${WebsitePaths.Home}`}
diff --git a/packages/website/ts/components/portal/back_button.tsx b/packages/website/ts/components/portal/back_button.tsx
new file mode 100644
index 000000000..48858613c
--- /dev/null
+++ b/packages/website/ts/components/portal/back_button.tsx
@@ -0,0 +1,43 @@
+import { Styles } from '@0xproject/react-shared';
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+
+import { colors } from 'ts/utils/colors';
+
+export interface BackButtonProps {
+ to: string;
+ labelText: string;
+}
+
+const BACK_BUTTON_HEIGHT = 28;
+
+const styles: Styles = {
+ backButton: {
+ height: BACK_BUTTON_HEIGHT,
+ paddingTop: 10,
+ backgroundColor: colors.white,
+ borderRadius: BACK_BUTTON_HEIGHT,
+ boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
+ },
+ backButtonIcon: {
+ color: colors.mediumBlue,
+ fontSize: 20,
+ },
+};
+
+export const BackButton = (props: BackButtonProps) => {
+ return (
+ <div style={{ height: 65, paddingTop: 25 }}>
+ <Link to={props.to} style={{ textDecoration: 'none' }}>
+ <div className="flex right" style={styles.backButton}>
+ <div style={{ marginLeft: 12 }}>
+ <i style={styles.backButtonIcon} className={`zmdi zmdi-arrow-left`} />
+ </div>
+ <div style={{ marginLeft: 12, marginRight: 12 }}>
+ <div style={{ fontSize: 16, color: colors.lightGrey }}>{props.labelText}</div>
+ </div>
+ </div>
+ </Link>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx
new file mode 100644
index 000000000..75c8ac6c2
--- /dev/null
+++ b/packages/website/ts/components/portal/drawer_menu.tsx
@@ -0,0 +1,68 @@
+import { Styles } from '@0xproject/react-shared';
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { defaultMenuItemEntries, Menu } from 'ts/components/portal/menu';
+import { Identicon } from 'ts/components/ui/identicon';
+import { WebsitePaths } from 'ts/types';
+import { colors } from 'ts/utils/colors';
+import { utils } from 'ts/utils/utils';
+
+const IDENTICON_DIAMETER = 45;
+const BORDER_RADIUS = '50%';
+
+const styles: Styles = {
+ root: {
+ backgroundColor: colors.drawerMenuBackground,
+ width: '100%',
+ height: '100%',
+ },
+ identicon: {
+ borderWidth: 3,
+ borderStyle: 'solid',
+ borderColor: colors.white,
+ borderRadius: BORDER_RADIUS,
+ MozBorderRadius: BORDER_RADIUS,
+ WebkitBorderRadius: BORDER_RADIUS,
+ },
+ userAddress: {
+ color: colors.white,
+ },
+};
+
+export interface DrawerMenuProps {
+ selectedPath?: string;
+ userAddress?: string;
+}
+export const DrawerMenu = (props: DrawerMenuProps) => {
+ const relayerItemEntry = {
+ to: `${WebsitePaths.Portal}/`,
+ labelText: 'Relayer ecosystem',
+ iconName: 'zmdi-portable-wifi',
+ };
+ const menuItemEntries = _.concat(relayerItemEntry, defaultMenuItemEntries);
+ return (
+ <div style={styles.root}>
+ <Header userAddress={props.userAddress} />
+ <Menu selectedPath={props.selectedPath} menuItemEntries={menuItemEntries} />
+ </div>
+ );
+};
+
+interface HeaderProps {
+ userAddress?: string;
+}
+const Header = (props: HeaderProps) => {
+ return (
+ <div className="flex flex-center py4">
+ <div className="flex flex-column mx-auto">
+ <Identicon address={props.userAddress} diameter={IDENTICON_DIAMETER} style={styles.identicon} />
+ {!_.isUndefined(props.userAddress) && (
+ <div className="pt2" style={styles.userAddress}>
+ {utils.getAddressBeginAndEnd(props.userAddress)}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/portal/loading.tsx b/packages/website/ts/components/portal/loading.tsx
new file mode 100644
index 000000000..d804dd1b8
--- /dev/null
+++ b/packages/website/ts/components/portal/loading.tsx
@@ -0,0 +1,21 @@
+import CircularProgress from 'material-ui/CircularProgress';
+import * as React from 'react';
+
+const CIRCULAR_PROGRESS_SIZE = 40;
+const CIRCULAR_PROGRESS_THICKNESS = 5;
+
+export interface LoadingProps {
+ isLoading: boolean;
+ content: React.ReactNode;
+}
+export const Loading = (props: LoadingProps) => {
+ if (props.isLoading) {
+ return (
+ <div className="center">
+ <CircularProgress size={CIRCULAR_PROGRESS_SIZE} thickness={CIRCULAR_PROGRESS_THICKNESS} />
+ </div>
+ );
+ } else {
+ return <div>{props.content}</div>;
+ }
+};
diff --git a/packages/website/ts/components/portal/menu.tsx b/packages/website/ts/components/portal/menu.tsx
new file mode 100644
index 000000000..6a3301549
--- /dev/null
+++ b/packages/website/ts/components/portal/menu.tsx
@@ -0,0 +1,116 @@
+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';
+
+export interface MenuTheme {
+ paddingLeft: number;
+ textColor: string;
+ iconColor: string;
+ selectedIconColor: string;
+ selectedBackgroundColor: string;
+}
+
+export interface MenuItemEntry {
+ to: string;
+ labelText: string;
+ iconName: string;
+}
+
+export interface MenuProps {
+ selectedPath?: string;
+ theme?: MenuTheme;
+ menuItemEntries?: MenuItemEntry[];
+}
+
+export const defaultMenuItemEntries: MenuItemEntry[] = [
+ {
+ to: `${WebsitePaths.Portal}/account`,
+ labelText: 'Account overview',
+ iconName: 'zmdi-balance-wallet',
+ },
+ {
+ to: `${WebsitePaths.Portal}/trades`,
+ labelText: 'Trade history',
+ iconName: 'zmdi-format-list-bulleted',
+ },
+ {
+ to: `${WebsitePaths.Portal}/weth`,
+ labelText: 'Wrapped ETH',
+ iconName: 'zmdi-circle-o',
+ },
+ {
+ to: `${WebsitePaths.Portal}/direct`,
+ labelText: 'Trade direct',
+ iconName: 'zmdi-swap',
+ },
+];
+
+const DEFAULT_MENU_THEME: MenuTheme = {
+ paddingLeft: 30,
+ textColor: colors.white,
+ iconColor: colors.white,
+ selectedIconColor: colors.white,
+ selectedBackgroundColor: colors.menuItemDefaultSelectedBackground,
+};
+
+export const Menu: React.StatelessComponent<MenuProps> = (props: MenuProps) => {
+ return (
+ <div>
+ {_.map(props.menuItemEntries, entry => {
+ const isSelected = entry.to === props.selectedPath;
+ return (
+ <MenuItem key={entry.to} to={entry.to}>
+ <MenuItemLabel
+ title={entry.labelText}
+ iconName={entry.iconName}
+ selected={isSelected}
+ theme={props.theme}
+ />
+ </MenuItem>
+ );
+ })}
+ </div>
+ );
+};
+Menu.defaultProps = {
+ theme: DEFAULT_MENU_THEME,
+ menuItemEntries: defaultMenuItemEntries,
+};
+
+interface MenuItemLabelProps {
+ title: string;
+ iconName: string;
+ selected: boolean;
+ theme: MenuTheme;
+}
+const MenuItemLabel: React.StatelessComponent<MenuItemLabelProps> = (props: MenuItemLabelProps) => {
+ const styles: Styles = {
+ root: {
+ backgroundColor: props.selected ? props.theme.selectedBackgroundColor : undefined,
+ paddingLeft: props.theme.paddingLeft,
+ },
+ icon: {
+ color: props.selected ? props.theme.selectedIconColor : props.theme.iconColor,
+ fontSize: 20,
+ },
+ text: {
+ color: props.theme.textColor,
+ fontWeight: props.selected ? 'bold' : 'normal',
+ fontSize: 16,
+ },
+ };
+ return (
+ <div className="flex py2" style={styles.root}>
+ <div className="pr1">
+ <i style={styles.icon} className={`zmdi ${props.iconName}`} />
+ </div>
+ <div className="pl1" style={styles.text}>
+ {props.title}
+ </div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index e572b7911..1bd318c28 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -3,20 +3,40 @@ 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 { Blockchain } from 'ts/blockchain';
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
+import { EthWrappers } from 'ts/components/eth_wrappers';
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
+import { BackButton } from 'ts/components/portal/back_button';
+import { Loading } from 'ts/components/portal/loading';
+import { Menu, MenuTheme } from 'ts/components/portal/menu';
+import { Section } from 'ts/components/portal/section';
+import { TextHeader } from 'ts/components/portal/text_header';
import { RelayerIndex } from 'ts/components/relayer_index/relayer_index';
+import { TokenBalances } from 'ts/components/token_balances';
import { TopBar, TopBarDisplayType } 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 { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
+import { FullscreenMessage } from 'ts/pages/fullscreen_message';
import { Dispatcher } from 'ts/redux/dispatcher';
-import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, TokenVisibility } from 'ts/types';
+import {
+ BlockchainErrs,
+ HashData,
+ Order,
+ ProviderType,
+ ScreenWidths,
+ TokenByAddress,
+ TokenVisibility,
+ WebsitePaths,
+} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
@@ -54,6 +74,12 @@ interface PortalState {
tokenManagementState: TokenManagementState;
}
+interface AccountManagementItem {
+ pathName: string;
+ headerText: string;
+ render: () => React.ReactNode;
+}
+
enum TokenManagementState {
Add = 'Add',
Remove = 'Remove',
@@ -62,6 +88,8 @@ enum TokenManagementState {
const THROTTLE_TIMEOUT = 100;
const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded);
+const LEFT_COLUMN_WIDTH = 346;
+const MENU_PADDING_LEFT = 185;
const styles: Styles = {
root: {
@@ -72,15 +100,15 @@ const styles: Styles = {
body: {
height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
},
+ leftColumn: {
+ width: LEFT_COLUMN_WIDTH,
+ height: '100%',
+ },
scrollContainer: {
height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
WebkitOverflowScrolling: 'touch',
overflow: 'auto',
},
- title: {
- fontWeight: 'bold',
- fontSize: 20,
- },
};
export class Portal extends React.Component<PortalProps, PortalState> {
@@ -148,8 +176,6 @@ export class Portal extends React.Component<PortalProps, PortalState> {
const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
this.props.dispatcher,
);
- const allTokens = _.values(this.props.tokenByAddress);
- const trackedTokens = _.filter(allTokens, t => t.isTracked);
const isAssetPickerDialogOpen = this.state.tokenManagementState !== TokenManagementState.None;
const tokenVisibility =
this.state.tokenManagementState === TokenManagementState.Add
@@ -173,36 +199,14 @@ export class Portal extends React.Component<PortalProps, PortalState> {
style={{ backgroundColor: colors.lightestGrey }}
/>
<div id="portal" style={styles.body}>
- <div className="sm-flex flex-center">
- <div className="flex-last px3">
- <div className="py3" style={styles.title}>
- Your Account
- </div>
- <Wallet
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this._blockchain}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- blockchainErr={this.props.blockchainErr}
- dispatcher={this.props.dispatcher}
- tokenByAddress={this.props.tokenByAddress}
- trackedTokens={trackedTokens}
- userEtherBalanceInWei={this.props.userEtherBalanceInWei}
- lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
- injectedProviderName={this.props.injectedProviderName}
- providerType={this.props.providerType}
- onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
- onAddToken={this._onAddToken.bind(this)}
- onRemoveToken={this._onRemoveToken.bind(this)}
- />
- </div>
- <div className="flex-auto px3" style={styles.scrollContainer}>
- <div className="py3" style={styles.title}>
- Explore 0x Ecosystem
- </div>
- <RelayerIndex networkId={this.props.networkId} />
- </div>
- </div>
+ <Switch>
+ <Route path={`${WebsitePaths.Portal}/:route`} render={this._renderOtherRoutes.bind(this)} />
+ <Route
+ exact={true}
+ path={`${WebsitePaths.Portal}/`}
+ render={this._renderMainRoute.bind(this)}
+ />
+ </Switch>
<BlockchainErrDialog
blockchain={this._blockchain}
blockchainErr={this.props.blockchainErr}
@@ -241,6 +245,173 @@ export class Portal extends React.Component<PortalProps, PortalState> {
</div>
);
}
+ private _renderMainRoute(): React.ReactNode {
+ if (this._isSmallScreen()) {
+ return <SmallLayout content={this._renderRelayerIndexSection()} />;
+ } else {
+ return <LargeLayout left={this._renderWalletSection()} right={this._renderRelayerIndexSection()} />;
+ }
+ }
+ private _renderOtherRoutes(routeComponentProps: RouteComponentProps<any>): React.ReactNode {
+ if (this._isSmallScreen()) {
+ return <SmallLayout content={this._renderAccountManagement()} />;
+ } else {
+ return <LargeLayout left={this._renderMenu(routeComponentProps)} right={this._renderAccountManagement()} />;
+ }
+ }
+ private _renderMenu(routeComponentProps: RouteComponentProps<any>): React.ReactNode {
+ const menuTheme: MenuTheme = {
+ paddingLeft: MENU_PADDING_LEFT,
+ textColor: colors.darkerGrey,
+ iconColor: colors.darkerGrey,
+ selectedIconColor: colors.yellow800,
+ selectedBackgroundColor: 'transparent',
+ };
+ return (
+ <Section
+ header={<BackButton to={`${WebsitePaths.Portal}`} labelText="back to Relayers" />}
+ body={<Menu selectedPath={routeComponentProps.location.pathname} theme={menuTheme} />}
+ />
+ );
+ }
+ private _renderWallet(): React.ReactNode {
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ return (
+ <Wallet
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ tokenByAddress={this.props.tokenByAddress}
+ trackedTokens={trackedTokens}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
+ onAddToken={this._onAddToken.bind(this)}
+ onRemoveToken={this._onRemoveToken.bind(this)}
+ />
+ );
+ }
+ private _renderWalletSection(): React.ReactNode {
+ return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />;
+ }
+ private _renderAccountManagement(): React.ReactNode {
+ const accountManagementItems: AccountManagementItem[] = [
+ {
+ pathName: `${WebsitePaths.Portal}/weth`,
+ headerText: 'Wrapped ETH',
+ render: this._renderEthWrapper.bind(this),
+ },
+ {
+ pathName: `${WebsitePaths.Portal}/account`,
+ headerText: 'Your Account',
+ render: this._isSmallScreen() ? this._renderWallet.bind(this) : this._renderTokenBalances.bind(this),
+ },
+ {
+ pathName: `${WebsitePaths.Portal}/trades`,
+ headerText: 'Trade History',
+ render: this._renderTradeHistory.bind(this),
+ },
+ {
+ pathName: `${WebsitePaths.Portal}/direct`,
+ headerText: 'Trade Direct',
+ render: this._renderTradeDirect.bind(this),
+ },
+ ];
+ 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>
+ );
+ }
+ private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode {
+ return (
+ <Section
+ header={<TextHeader labelText={item.headerText} />}
+ body={<Loading isLoading={!this.props.blockchainIsLoaded} content={item.render()} />}
+ />
+ );
+ }
+ private _renderEthWrapper(): React.ReactNode {
+ return (
+ <EthWrappers
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ dispatcher={this.props.dispatcher}
+ tokenByAddress={this.props.tokenByAddress}
+ userAddress={this.props.userAddress}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ />
+ );
+ }
+ private _renderTradeHistory(): React.ReactNode {
+ return (
+ <TradeHistory
+ tokenByAddress={this.props.tokenByAddress}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ />
+ );
+ }
+ private _renderTradeDirect(match: any, location: Location, history: History): React.ReactNode {
+ return (
+ <GenerateOrderForm
+ blockchain={this._blockchain}
+ hashData={this.props.hashData}
+ dispatcher={this.props.dispatcher}
+ />
+ );
+ }
+ private _renderTokenBalances(): React.ReactNode {
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ return (
+ <TokenBalances
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ dispatcher={this.props.dispatcher}
+ screenWidth={this.props.screenWidth}
+ tokenByAddress={this.props.tokenByAddress}
+ trackedTokens={trackedTokens}
+ userAddress={this.props.userAddress}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ networkId={this.props.networkId}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ />
+ );
+ }
+ private _renderRelayerIndexSection(): React.ReactNode {
+ return (
+ <Section
+ header={<TextHeader labelText="Explore 0x Relayers" />}
+ body={<RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />}
+ />
+ );
+ }
+ private _renderNotFoundMessage(): React.ReactNode {
+ return (
+ <FullscreenMessage
+ headerText="404 Not Found"
+ bodyText="Hm... looks like we couldn't find what you are looking for."
+ />
+ );
+ }
private _onTokenChosen(tokenAddress: string): void {
if (_.isEmpty(tokenAddress)) {
this.setState({
@@ -294,4 +465,38 @@ export class Portal extends React.Component<PortalProps, PortalState> {
const newScreenWidth = utils.getScreenWidth();
this.props.dispatcher.updateScreenWidth(newScreenWidth);
}
+ private _isSmallScreen(): boolean {
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
+ return isSmallScreen;
+ }
+}
+
+interface LargeLayoutProps {
+ left: React.ReactNode;
+ right: React.ReactNode;
}
+const LargeLayout = (props: LargeLayoutProps) => {
+ return (
+ <div className="sm-flex flex-center">
+ <div className="flex-last px3">
+ <div style={styles.leftColumn}>{props.left}</div>
+ </div>
+ <div className="flex-auto px3" style={styles.scrollContainer}>
+ {props.right}
+ </div>
+ </div>
+ );
+};
+
+interface SmallLayoutProps {
+ content: React.ReactNode;
+}
+const SmallLayout = (props: SmallLayoutProps) => {
+ return (
+ <div className="sm-flex flex-center">
+ <div className="flex-auto px3" style={styles.scrollContainer}>
+ {props.content}
+ </div>
+ </div>
+ );
+}; // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/portal/section.tsx b/packages/website/ts/components/portal/section.tsx
new file mode 100644
index 000000000..9b172aae0
--- /dev/null
+++ b/packages/website/ts/components/portal/section.tsx
@@ -0,0 +1,15 @@
+import { Styles } from '@0xproject/react-shared';
+import * as React from 'react';
+
+export interface SectionProps {
+ header: React.ReactNode;
+ body: React.ReactNode;
+}
+export const Section = (props: SectionProps) => {
+ return (
+ <div className="flex flex-column" style={{ height: '100%' }}>
+ {props.header}
+ <div className="flex-auto">{props.body}</div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/components/portal/text_header.tsx b/packages/website/ts/components/portal/text_header.tsx
new file mode 100644
index 000000000..4aabd47d0
--- /dev/null
+++ b/packages/website/ts/components/portal/text_header.tsx
@@ -0,0 +1,21 @@
+import { Styles } from '@0xproject/react-shared';
+import * as React from 'react';
+
+export interface TextHeaderProps {
+ labelText: string;
+}
+
+const styles: Styles = {
+ title: {
+ fontWeight: 'bold',
+ fontSize: 20,
+ },
+};
+
+export const TextHeader = (props: TextHeaderProps) => {
+ return (
+ <div className="py3" style={styles.title}>
+ {props.labelText}
+ </div>
+ );
+};
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 5964dcd56..dc9eeb29d 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -1,4 +1,4 @@
-import { colors, Styles } from '@0xproject/react-shared';
+import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import { GridTile } from 'material-ui/GridList';
import * as React from 'react';
@@ -6,6 +6,7 @@ import * as React from 'react';
import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { Token, WebsiteBackendRelayerInfo } from 'ts/types';
+import { colors } from 'ts/utils/colors';
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 675e83c9f..9ef6eaf59 100644
--- a/packages/website/ts/components/relayer_index/relayer_index.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_index.tsx
@@ -1,4 +1,4 @@
-import { colors, Styles } from '@0xproject/react-shared';
+import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
import FlatButton from 'material-ui/FlatButton';
@@ -6,11 +6,13 @@ import { GridList } from 'material-ui/GridList';
import * as React from 'react';
import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile';
-import { WebsiteBackendRelayerInfo } from 'ts/types';
+import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types';
import { backendClient } from 'ts/utils/backend_client';
+import { colors } from 'ts/utils/colors';
export interface RelayerIndexProps {
networkId: number;
+ screenWidth: ScreenWidths;
}
interface RelayerIndexState {
@@ -35,7 +37,9 @@ const styles: Styles = {
};
const CELL_HEIGHT = 290;
-const NUMBER_OF_COLUMNS = 4;
+const NUMBER_OF_COLUMNS_LARGE = 4;
+const NUMBER_OF_COLUMNS_MEDIUM = 3;
+const NUMBER_OF_COLUMNS_SMALL = 1;
const GRID_PADDING = 20;
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
@@ -59,27 +63,22 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos);
if (!isReadyToRender) {
return (
- <div className="col col-12" style={{ ...styles.root, height: '100%' }}>
- <div
- className="relative sm-px2 sm-pt2 sm-m1"
- style={{ height: 122, top: '33%', transform: 'translateY(-50%)' }}
- >
- <div className="center pb2">
- {_.isUndefined(this.state.error) ? (
- <CircularProgress size={40} thickness={5} />
- ) : (
- <Retry onRetry={this._fetchRelayerInfosAsync.bind(this)} />
- )}
- </div>
- </div>
+ // TODO: consolidate this loading component with the one in portal
+ <div className="center">
+ {_.isUndefined(this.state.error) ? (
+ <CircularProgress size={40} thickness={5} />
+ ) : (
+ <Retry onRetry={this._fetchRelayerInfosAsync.bind(this)} />
+ )}
</div>
);
} else {
+ const numberOfColumns = this._numberOfColumnsForScreenWidth(this.props.screenWidth);
return (
<div style={styles.root}>
<GridList
cellHeight={CELL_HEIGHT}
- cols={NUMBER_OF_COLUMNS}
+ cols={numberOfColumns}
padding={GRID_PADDING}
style={styles.gridList}
>
@@ -113,6 +112,17 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
}
}
}
+ private _numberOfColumnsForScreenWidth(screenWidth: ScreenWidths): number {
+ switch (screenWidth) {
+ case ScreenWidths.Md:
+ return NUMBER_OF_COLUMNS_MEDIUM;
+ case ScreenWidths.Sm:
+ return NUMBER_OF_COLUMNS_SMALL;
+ case ScreenWidths.Lg:
+ default:
+ return NUMBER_OF_COLUMNS_LARGE;
+ }
+ }
}
interface RetryProps {
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index 9d8bd6913..fc516882a 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -1,13 +1,15 @@
-import { colors, Styles } from '@0xproject/react-shared';
+import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import RaisedButton from 'material-ui/RaisedButton';
import * as React from 'react';
+
import { Blockchain } from 'ts/blockchain';
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 { ProviderType } from 'ts/types';
+import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
@@ -41,7 +43,9 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
this.props.providerType === ProviderType.Injected && this.props.injectedProviderName !== '0x Public';
const displayAddress = isAddressAvailable
? utils.getAddressBeginAndEnd(this.props.userAddress)
- : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000';
+ : isExternallyInjectedProvider
+ ? 'Account locked'
+ : '0x0000...0000';
// If the "injected" provider is our fallback public node, then we want to
// show the "connect a wallet" message instead of the providerName
const injectedProviderName = isExternallyInjectedProvider
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 3b39d789b..f2d0c6177 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -9,6 +9,7 @@ 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';
@@ -18,6 +19,7 @@ import { Dispatcher } from 'ts/redux/dispatcher';
import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
+import { utils } from 'ts/utils/utils';
export enum TopBarDisplayType {
Default,
@@ -93,6 +95,13 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
isDrawerOpen: false,
};
}
+ public componentWillReceiveProps(nextProps: TopBarProps): void {
+ if (nextProps.location.pathname !== this.props.location.pathname) {
+ this.setState({
+ isDrawerOpen: false,
+ });
+ }
+ }
public render(): React.ReactNode {
const isNightVersion = this.props.isNightVersion;
const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded;
@@ -196,6 +205,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
);
const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
+ // TODO : Remove this once we ship portal v2
+ const shouldShowPortalV2Drawer = this._isViewingPortal() && utils.shouldShowPortalV2();
return (
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1">
<div className={parentClassNames}>
@@ -268,10 +279,22 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
</div>
</div>
- {this._renderDrawer()}
+ {shouldShowPortalV2Drawer ? this._renderPortalV2Drawer() : this._renderDrawer()}
</div>
);
}
+ private _renderPortalV2Drawer(): React.ReactNode {
+ return (
+ <Drawer
+ open={this.state.isDrawerOpen}
+ docked={false}
+ openSecondary={true}
+ onRequestChange={this._onMenuButtonClick.bind(this)}
+ >
+ <DrawerMenu selectedPath={this.props.location.pathname} userAddress={this.props.userAddress} />
+ </Drawer>
+ );
+ }
private _renderDrawer(): React.ReactNode {
return (
<Drawer
diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx
index 8137c0db6..8eda45a5d 100644
--- a/packages/website/ts/components/ui/input_label.tsx
+++ b/packages/website/ts/components/ui/input_label.tsx
@@ -1,11 +1,11 @@
-import { colors } from '@0xproject/react-shared';
+import { colors, Styles } from '@0xproject/react-shared';
import * as React from 'react';
export interface InputLabelProps {
text: string | Element | React.ReactNode;
}
-const styles = {
+const styles: Styles = {
label: {
color: colors.grey,
fontSize: 12,
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 95f31582e..dab8b7d2f 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -1,6 +1,5 @@
import { ZeroEx } from '0x.js';
import {
- colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
Styles,
@@ -18,6 +17,7 @@ import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-down
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');
@@ -38,8 +38,10 @@ import {
TokenByAddress,
TokenState,
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 { utils } from 'ts/utils/utils';
import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
@@ -80,7 +82,7 @@ interface AccessoryItemConfig {
const styles: Styles = {
root: {
- width: 346,
+ width: '100%',
backgroundColor: colors.white,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
@@ -134,6 +136,10 @@ const styles: Styles = {
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
},
+ manageYourWalletText: {
+ color: colors.mediumBlue,
+ fontWeight: 'bold',
+ },
};
const ETHER_ICON_PATH = '/images/ether.png';
@@ -237,13 +243,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
const userAddress = this.props.userAddress;
const primaryText = utils.getAddressBeginAndEnd(userAddress);
return (
- <ListItem
- key={HEADER_ITEM_KEY}
- primaryText={primaryText}
- leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
- style={{ ...styles.paddedItem, ...styles.borderedItem }}
- innerDivStyle={styles.headerItemInnerDiv}
- />
+ <Link key={HEADER_ITEM_KEY} to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}>
+ <ListItem
+ primaryText={primaryText}
+ leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
+ style={{ ...styles.paddedItem, ...styles.borderedItem }}
+ innerDivStyle={styles.headerItemInnerDiv}
+ />
+ </Link>
);
}
private _renderBody(): React.ReactElement<{}> {
@@ -275,31 +282,50 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
private _renderFooterRows(): React.ReactElement<{}> {
return (
- <ListItem
- key={FOOTER_ITEM_KEY}
- primaryText={
- <div className="flex">
- <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
- <ContentAdd />
- </FloatingActionButton>
- <FloatingActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}>
- <ContentRemove />
- </FloatingActionButton>
- <div
- style={{
- paddingLeft: 10,
- position: 'relative',
- top: '50%',
- transform: 'translateY(33%)',
- }}
- >
- add/remove tokens
+ <div key={FOOTER_ITEM_KEY}>
+ <ListItem
+ primaryText={
+ <div className="flex">
+ <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
+ <ContentAdd />
+ </FloatingActionButton>
+ <FloatingActionButton
+ mini={true}
+ zDepth={0}
+ className="px1"
+ onClick={this.props.onRemoveToken}
+ >
+ <ContentRemove />
+ </FloatingActionButton>
+ <div
+ style={{
+ paddingLeft: 10,
+ position: 'relative',
+ top: '50%',
+ transform: 'translateY(33%)',
+ }}
+ >
+ add/remove tokens
+ </div>
</div>
- </div>
- }
- disabled={true}
- innerDivStyle={styles.footerItemInnerDiv}
- />
+ }
+ disabled={true}
+ innerDivStyle={styles.footerItemInnerDiv}
+ style={styles.borderedItem}
+ />
+ <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}>
+ <ListItem
+ primaryText={
+ <div className="flex right" style={styles.manageYourWalletText}>
+ {'manage your wallet'}
+ </div>
+ // https://github.com/palantir/tslint-react/issues/140
+ // tslint:disable-next-line:jsx-curly-spacing
+ }
+ style={{ ...styles.paddedItem, ...styles.borderedItem }}
+ />
+ </Link>
+ </div>
);
}
private _renderEthRows(): React.ReactNode {
diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
index 89e32f7be..d334f1748 100644
--- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
+++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
@@ -1,9 +1,10 @@
-import { colors, Styles } from '@0xproject/react-shared';
+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 { ProviderType } from 'ts/types';
+import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
export interface WalletDisconnectedItemProps {
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
index 2ed924bcd..98b28b3ad 100644
--- a/packages/website/ts/components/wallet/wrap_ether_item.tsx
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -1,5 +1,5 @@
import { ZeroEx } from '0x.js';
-import { colors, Styles } from '@0xproject/react-shared';
+import { Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import FlatButton from 'material-ui/FlatButton';
@@ -11,6 +11,7 @@ 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 { 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';
@@ -31,8 +32,8 @@ export interface WrapEtherItemProps {
interface WrapEtherItemState {
currentInputAmount?: BigNumber;
- currentInputHasErrors: boolean;
isEthConversionHappening: boolean;
+ errorMsg: React.ReactNode;
}
const styles: Styles = {
@@ -46,13 +47,29 @@ const styles: Styles = {
padding: 4,
width: 125,
},
- ethAmountInput: { height: 32 },
- innerDiv: { paddingLeft: 60, paddingTop: 0 },
- wrapEtherConfirmationButtonContainer: { width: 128, top: 16 },
+ amountInput: { height: 34 },
+ amountInputLabel: {
+ paddingTop: 10,
+ paddingRight: 10,
+ paddingLeft: 5,
+ color: colors.grey,
+ fontSize: 14,
+ },
+ amountInputHint: {
+ bottom: 18,
+ },
+ innerDiv: { paddingLeft: 60, paddingTop: 0, paddingBottom: 10 },
+ wrapEtherConfirmationButtonContainer: { width: 128, top: 19 },
wrapEtherConfirmationButtonLabel: {
- fontSize: 10,
+ fontSize: 12,
color: colors.white,
},
+ errorMsg: {
+ fontSize: 12,
+ marginTop: 4,
+ color: colors.red,
+ minHeight: 20,
+ },
};
export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEtherItemState> {
@@ -60,8 +77,8 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
super(props);
this.state = {
currentInputAmount: undefined,
- currentInputHasErrors: false,
isEthConversionHappening: false,
+ errorMsg: null,
};
}
public render(): React.ReactNode {
@@ -84,7 +101,10 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
shouldShowIncompleteErrs={false}
shouldShowErrs={false}
shouldShowUnderline={false}
- style={styles.ethAmountInput}
+ style={styles.amountInput}
+ labelStyle={styles.amountInputLabel}
+ inputHintStyle={styles.amountInputHint}
+ onErrorMsgChange={this._onErrorMsgChange.bind(this)}
/>
) : (
<TokenAmountInput
@@ -99,12 +119,16 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
onChange={this._onValueChange.bind(this)}
amount={this.state.currentInputAmount}
hintText="0.00"
- shouldShowErrs={false} // TODO: error handling
+ shouldShowErrs={false}
shouldShowUnderline={false}
- style={styles.ethAmountInput}
+ style={styles.amountInput}
+ labelStyle={styles.amountInputLabel}
+ inputHintStyle={styles.amountInputHint}
+ onErrorMsgChange={this._onErrorMsgChange.bind(this)}
/>
)}
</div>
+ {this._renderErrorMsg()}
</div>
}
secondaryTextLines={2}
@@ -119,7 +143,11 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
private _onValueChange(isValid: boolean, amount?: BigNumber): void {
this.setState({
currentInputAmount: amount,
- currentInputHasErrors: !isValid,
+ });
+ }
+ private _onErrorMsgChange(errorMsg: React.ReactNode): void {
+ this.setState({
+ errorMsg,
});
}
private _renderIsEthConversionHappeningSpinner(): React.ReactElement<{}> {
@@ -144,6 +172,9 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
</div>
);
}
+ private _renderErrorMsg(): React.ReactNode {
+ return <div style={styles.errorMsg}>{this.state.errorMsg}</div>;
+ }
private async _wrapEtherConfirmationActionAsync(): Promise<void> {
this.setState({
isEthConversionHappening: true,
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index f255f81e7..7fc180449 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -34,14 +34,15 @@ import 'less/all.less';
// cause we only want to import the module when the user navigates to the page.
// At the same time webpack statically parses for System.import() to determine bundle chunk split points
// so each lazy import needs it's own `System.import()` declaration.
-const LazyPortal =
- utils.isDevelopment() || utils.isStaging() || utils.isDogfood()
- ? createLazyComponent('Portal', async () =>
- System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'),
- )
- : createLazyComponent('LegacyPortal', async () =>
- System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'),
- );
+
+// TODO: Remove this once we ship V2
+const LazyPortal = utils.shouldShowPortalV2()
+ ? createLazyComponent('Portal', async () =>
+ System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'),
+ )
+ : createLazyComponent('LegacyPortal', async () =>
+ System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'),
+ );
const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () =>
System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'),
);
diff --git a/packages/website/ts/pages/fullscreen_message.tsx b/packages/website/ts/pages/fullscreen_message.tsx
new file mode 100644
index 000000000..6fcf7b32c
--- /dev/null
+++ b/packages/website/ts/pages/fullscreen_message.tsx
@@ -0,0 +1,30 @@
+import { Styles } from '@0xproject/react-shared';
+import * as React from 'react';
+
+export interface FullscreenMessageProps {
+ headerText: string;
+ bodyText: string;
+}
+
+const styles: Styles = {
+ thin: {
+ fontWeight: 100,
+ },
+};
+
+export const FullscreenMessage = (props: FullscreenMessageProps) => {
+ return (
+ <div className="mx-auto max-width-4 py4">
+ <div className="center py4">
+ <div className="py4">
+ <div className="py4">
+ <h1 style={styles.thin}>{props.headerText}</h1>
+ <div className="py1">
+ <div className="py3">{props.bodyText}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx
index 96c73d4ec..674271636 100644
--- a/packages/website/ts/pages/not_found.tsx
+++ b/packages/website/ts/pages/not_found.tsx
@@ -3,6 +3,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import { Footer } from 'ts/components/footer';
import { TopBar } from 'ts/components/top_bar/top_bar';
+import { FullscreenMessage } from 'ts/pages/fullscreen_message';
import { Dispatcher } from 'ts/redux/dispatcher';
import { Translate } from 'ts/utils/translate';
@@ -12,35 +13,15 @@ export interface NotFoundProps {
dispatcher: Dispatcher;
}
-interface NotFoundState {}
-
-const styles: Styles = {
- thin: {
- fontWeight: 100,
- },
+export const NotFound = (props: NotFoundProps) => {
+ return (
+ <div>
+ <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} />
+ <FullscreenMessage
+ headerText={'404 Not Found'}
+ bodyText={"Hm... looks like we couldn't find what you are looking for."}
+ />
+ <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} />
+ </div>
+ );
};
-
-export class NotFound extends React.Component<NotFoundProps, NotFoundState> {
- public render(): React.ReactNode {
- return (
- <div>
- <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} />
- <div className="mx-auto max-width-4 py4">
- <div className="center py4">
- <div className="py4">
- <div className="py4">
- <h1 style={{ ...styles.thin }}>404 Not Found</h1>
- <div className="py1">
- <div className="py3">
- Hm... looks like we couldn't find what you are looking for.
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} />
- </div>
- );
- }
-}
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 5caf5e73c..33e4d6c30 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -207,7 +207,6 @@ export interface ContractEvent {
args: any;
}
-export type InputErrMsg = React.ReactNode | string | undefined;
export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void;
export enum ScreenWidths {
Sm = 'SM',
diff --git a/packages/website/ts/utils/colors.ts b/packages/website/ts/utils/colors.ts
new file mode 100644
index 000000000..5ffdd6ba7
--- /dev/null
+++ b/packages/website/ts/utils/colors.ts
@@ -0,0 +1,19 @@
+import { colors as sharedColors } from '@0xproject/react-shared';
+
+const appColors = {
+ walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
+ walletBorder: '#ededee',
+ walletDefaultItemBackground: '#fbfbfc',
+ walletFocusedItemBackground: '#f0f1f4',
+ allowanceToggleShadow: 'rgba(0, 0, 0, 0)',
+ allowanceToggleOffTrack: '#adadad',
+ allowanceToggleOnTrack: sharedColors.mediumBlue,
+ wrapEtherConfirmationButton: sharedColors.mediumBlue,
+ drawerMenuBackground: '#4a4a4a',
+ menuItemDefaultSelectedBackground: '#424242',
+};
+
+export const colors = {
+ ...sharedColors,
+ ...appColors,
+};
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 3c99bd2fe..c370ac90c 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -314,4 +314,7 @@ export const utils = {
return _.includes(window.location.href, configs.DOMAIN_STAGING);
},
isDogfood,
+ shouldShowPortalV2(): boolean {
+ return this.isDevelopment() || this.isStaging() || this.isDogfood();
+ },
};
diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts
index 1ad304ce1..6b038efd2 100644
--- a/packages/website/ts/utils/wallet_item_styles.ts
+++ b/packages/website/ts/utils/wallet_item_styles.ts
@@ -1,4 +1,6 @@
-import { colors, Styles } from '@0xproject/react-shared';
+import { Styles } from '@0xproject/react-shared';
+
+import { colors } from 'ts/utils/colors';
export const styles: Styles = {
focusedItem: {
diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js
index e28e9e064..f9abeb27c 100644
--- a/packages/website/webpack.config.js
+++ b/packages/website/webpack.config.js
@@ -1,5 +1,6 @@
const path = require('path');
const webpack = require('webpack');
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: ['./ts/index.tsx'],
@@ -76,9 +77,14 @@ module.exports = {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
- new webpack.optimize.UglifyJsPlugin({
- mangle: {
- except: ['BigNumber'],
+ // TODO: Revert to webpack bundled version with webpack v4.
+ // The v3 series bundled version does not support ES6 and
+ // fails to build.
+ new UglifyJsPlugin({
+ uglifyOptions: {
+ mangle: {
+ reserved: ['BigNumber'],
+ },
},
}),
]