aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts')
-rw-r--r--packages/website/ts/blockchain.ts9
-rw-r--r--packages/website/ts/components/dialogs/ledger_config_dialog.tsx4
-rw-r--r--packages/website/ts/components/eth_wrappers.tsx6
-rw-r--r--packages/website/ts/components/fill_order.tsx2
-rw-r--r--packages/website/ts/components/footer.tsx2
-rw-r--r--packages/website/ts/components/generate_order/generate_order_form.tsx2
-rw-r--r--packages/website/ts/components/generate_order/new_token_form.tsx6
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx9
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx2
-rw-r--r--packages/website/ts/components/inputs/expiration_input.tsx4
-rw-r--r--packages/website/ts/components/legacy_portal/legacy_portal.tsx4
-rw-r--r--packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx18
-rw-r--r--packages/website/ts/components/onboarding/congrats_onboarding_step.tsx15
-rw-r--r--packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx18
-rw-r--r--packages/website/ts/components/onboarding/intro_onboarding_step.tsx23
-rw-r--r--packages/website/ts/components/onboarding/onboarding_flow.tsx12
-rw-r--r--packages/website/ts/components/onboarding/onboarding_tooltip.tsx103
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx135
-rw-r--r--packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx27
-rw-r--r--packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx16
-rw-r--r--packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx73
-rw-r--r--packages/website/ts/components/portal/portal.tsx42
-rw-r--r--packages/website/ts/components/redirector.tsx (renamed from packages/website/ts/components/redirecter.tsx)4
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx54
-rw-r--r--packages/website/ts/components/relayer_index/relayer_index.tsx39
-rw-r--r--packages/website/ts/components/token_balances.tsx7
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx3
-rw-r--r--packages/website/ts/components/ui/button.tsx21
-rw-r--r--packages/website/ts/components/ui/container.tsx7
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx2
-rw-r--r--packages/website/ts/components/ui/filled_image.tsx18
-rw-r--r--packages/website/ts/components/ui/icon_button.tsx9
-rw-r--r--packages/website/ts/components/ui/image.tsx37
-rw-r--r--packages/website/ts/components/ui/pointer.tsx67
-rw-r--r--packages/website/ts/components/ui/retry.tsx32
-rw-r--r--packages/website/ts/components/ui/text.tsx35
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx17
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx2
-rw-r--r--packages/website/ts/containers/about.ts2
-rw-r--r--packages/website/ts/containers/connect_documentation.ts2
-rw-r--r--packages/website/ts/containers/ethereum_types_documentation.ts2
-rw-r--r--packages/website/ts/containers/faq.ts2
-rw-r--r--packages/website/ts/containers/generate_order_form.ts2
-rw-r--r--packages/website/ts/containers/inputs/allowance_toggle.ts41
-rw-r--r--packages/website/ts/containers/jobs.ts28
-rw-r--r--packages/website/ts/containers/json_schemas_documentation.ts2
-rw-r--r--packages/website/ts/containers/landing.ts2
-rw-r--r--packages/website/ts/containers/legacy_portal.ts2
-rw-r--r--packages/website/ts/containers/not_found.ts2
-rw-r--r--packages/website/ts/containers/order_utils_documentation.ts2
-rw-r--r--packages/website/ts/containers/portal.ts2
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts5
-rw-r--r--packages/website/ts/containers/smart_contracts_documentation.ts2
-rw-r--r--packages/website/ts/containers/sol_compiler_documentation.ts2
-rw-r--r--packages/website/ts/containers/sol_cov_documentation.ts2
-rw-r--r--packages/website/ts/containers/subproviders_documentation.ts2
-rw-r--r--packages/website/ts/containers/web3_wrapper_documentation.ts2
-rw-r--r--packages/website/ts/containers/wiki.ts2
-rw-r--r--packages/website/ts/containers/zero_ex_js_documentation.ts3
-rw-r--r--packages/website/ts/index.tsx11
-rw-r--r--packages/website/ts/local_storage/trade_history_storage.tsx2
-rw-r--r--packages/website/ts/pages/jobs/benefits.tsx109
-rw-r--r--packages/website/ts/pages/jobs/jobs.tsx81
-rw-r--r--packages/website/ts/pages/jobs/join_0x.tsx41
-rw-r--r--packages/website/ts/pages/jobs/list/header_item.tsx26
-rw-r--r--packages/website/ts/pages/jobs/list/list_item.tsx15
-rw-r--r--packages/website/ts/pages/jobs/mission.tsx56
-rw-r--r--packages/website/ts/pages/jobs/open_positions.tsx192
-rw-r--r--packages/website/ts/pages/jobs/photo_rail.tsx22
-rw-r--r--packages/website/ts/pages/jobs/teams.tsx90
-rw-r--r--packages/website/ts/pages/jobs/values.tsx60
-rw-r--r--packages/website/ts/pages/not_found.tsx2
-rw-r--r--packages/website/ts/pages/wiki/wiki.tsx4
-rw-r--r--packages/website/ts/style/colors.ts2
-rw-r--r--packages/website/ts/types.ts10
-rw-r--r--packages/website/ts/utils/backend_client.ts13
-rw-r--r--packages/website/ts/utils/constants.ts1
-rw-r--r--packages/website/ts/utils/error_reporter.ts2
-rw-r--r--packages/website/ts/utils/utils.ts28
79 files changed, 1528 insertions, 236 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 6e4d03e27..3ebdd1dee 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;
}
@@ -625,6 +625,7 @@ export class Blockchain {
);
const provider = this._contractWrappers.getProvider();
const web3Wrapper = new Web3Wrapper(provider);
+ web3Wrapper.abiDecoder.addABI(this._contractWrappers.exchange.abi);
const receipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
return receipt;
}
@@ -754,7 +755,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/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/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx
index ca8634693..20b446155 100644
--- a/packages/website/ts/components/eth_wrappers.tsx
+++ b/packages/website/ts/components/eth_wrappers.tsx
@@ -221,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>
@@ -249,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 b6b52993e..f3ea44286 100644
--- a/packages/website/ts/components/fill_order.tsx
+++ b/packages/website/ts/components/fill_order.tsx
@@ -351,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/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx
index 52e3b73cd..d26b5c3fa 100644
--- a/packages/website/ts/components/generate_order/generate_order_form.tsx
+++ b/packages/website/ts/components/generate_order/generate_order_form.tsx
@@ -226,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 09791f125..0dd2a5aa5 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -1,5 +1,6 @@
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';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
@@ -16,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>;
}
@@ -55,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/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
index 35e917eec..b4a174a03 100644
--- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx
+++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
@@ -310,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;
@@ -329,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/onboarding/add_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
new file mode 100644
index 000000000..31ce99d31
--- /dev/null
+++ b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface AddEthOnboardingStepProps {}
+
+export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStepProps> = () => (
+ <div className="flex items-center flex-column">
+ <Text> Before you begin you will need to send some ETH to your metamask wallet.</Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <img src="/images/ether_alt.svg" height="50px" width="50px" />
+ </Container>
+ <Text>
+ Click on the <img src="/images/metamask_icon.png" height="20px" width="20px" /> metamask extension in your
+ browser and click either <b>BUY</b> or <b>DEPOSIT</b>.
+ </Text>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
new file mode 100644
index 000000000..3a8db8c36
--- /dev/null
+++ b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface CongratsOnboardingStepProps {}
+
+export const CongratsOnboardingStep: React.StatelessComponent<CongratsOnboardingStepProps> = () => (
+ <div className="flex items-center flex-column">
+ <Text>Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem.</Text>
+ <Container marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <img src="/images/zrx_ecosystem.svg" height="150px" />
+ </Container>
+ <Text>No need to log in. Each relayer automatically detects and connects to your metamask wallet.</Text>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
new file mode 100644
index 000000000..a54496186
--- /dev/null
+++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
@@ -0,0 +1,18 @@
+import { colors } from '@0xproject/react-shared';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface InstallWalletOnboardingStepProps {}
+
+export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => (
+ <div className="flex items-center flex-column">
+ <Container marginTop="15px" marginBottom="15px">
+ <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} />
+ </Container>
+ <Text>
+ Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps.
+ </Text>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
new file mode 100644
index 000000000..548839218
--- /dev/null
+++ b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface IntroOnboardingStepProps {}
+
+export const IntroOnboardingStep: React.StatelessComponent<IntroOnboardingStepProps> = () => (
+ <div className="flex items-center flex-column">
+ <Text>
+ In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps.
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-around">
+ <div className="flex flex-column items-center">
+ <img src="/images/eth_token.svg" height="50px" width="50x" />
+ <Text> Wrap ETH </Text>
+ </div>
+ <div className="flex flex-column items-center">
+ <img src="/images/fake_toggle.svg" height="50px" width="50px" />
+ <Text> Unlock tokens </Text>
+ </div>
+ </Container>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx
index 9879cd387..34aeace82 100644
--- a/packages/website/ts/components/onboarding/onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx
@@ -10,9 +10,10 @@ export interface Step {
title?: string;
content: React.ReactNode;
placement?: Placement;
- hideBackButton?: boolean;
- hideNextButton?: boolean;
+ shouldHideBackButton?: boolean;
+ shouldHideNextButton?: boolean;
continueButtonDisplay?: ContinueButtonDisplay;
+ continueButtonText?: string;
}
export interface OnboardingFlowProps {
@@ -54,17 +55,18 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
return (
- <Container marginLeft="15px">
+ <Container marginLeft="30px">
<OnboardingTooltip
title={step.title}
content={step.content}
isLastStep={isLastStep}
- hideBackButton={step.hideBackButton}
- hideNextButton={step.hideNextButton}
+ shouldHideBackButton={step.shouldHideBackButton}
+ shouldHideNextButton={step.shouldHideNextButton}
onClose={this.props.onClose}
onClickNext={this._goToNextStep.bind(this)}
onClickBack={this._goToPrevStep.bind(this)}
continueButtonDisplay={step.continueButtonDisplay}
+ continueButtonText={step.continueButtonText}
/>
</Container>
);
diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
index 155c70c5f..45851b4de 100644
--- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
@@ -1,7 +1,12 @@
+import { colors } from '@0xproject/react-shared';
import * as React from 'react';
+import { Button } from 'ts/components/ui/button';
import { Container } from 'ts/components/ui/container';
+import { IconButton } from 'ts/components/ui/icon_button';
import { Island } from 'ts/components/ui/island';
+import { Pointer, PointerDirection } from 'ts/components/ui/pointer';
+import { Text, Title } from 'ts/components/ui/text';
export type ContinueButtonDisplay = 'enabled' | 'disabled';
@@ -13,43 +18,73 @@ export interface OnboardingTooltipProps {
onClickNext: () => void;
onClickBack: () => void;
continueButtonDisplay?: ContinueButtonDisplay;
- hideBackButton?: boolean;
- hideNextButton?: boolean;
+ shouldHideBackButton?: boolean;
+ shouldHideNextButton?: boolean;
+ pointerDirection?: PointerDirection;
+ continueButtonText?: string;
+ className?: string;
}
-// TODO: Make this more general button.
-export interface ContinueButtonProps {
- display: ContinueButtonDisplay;
- children?: string;
- onClick: () => void;
-}
+export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = ({
+ title,
+ content,
+ continueButtonDisplay,
+ continueButtonText,
+ onClickNext,
+ onClickBack,
+ onClose,
+ shouldHideBackButton,
+ shouldHideNextButton,
+ pointerDirection,
+ className,
+}) => (
+ <Pointer className={className} direction={pointerDirection}>
+ <Island>
+ <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px">
+ <div className="flex flex-column">
+ <div className="flex justify-between">
+ <Title>{title}</Title>
+ <Container position="relative" bottom="20px" left="15px">
+ <IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}>
+ Close
+ </IconButton>
+ </Container>
+ </div>
+ <Container marginBottom="15px">
+ <Text>{content}</Text>
+ </Container>
+ {continueButtonDisplay && (
+ <Button
+ isDisabled={continueButtonDisplay === 'disabled'}
+ onClick={onClickNext}
+ fontColor={colors.white}
+ fontSize="15px"
+ backgroundColor={colors.mediumBlue}
+ >
+ {continueButtonText}
+ </Button>
+ )}
+ <Container className="flex justify-between" marginTop="15px">
+ {!shouldHideBackButton && (
+ <Text fontColor={colors.grey} onClick={onClickBack}>
+ Back
+ </Text>
+ )}
+ {!shouldHideNextButton && (
+ <Text fontColor={colors.grey} onClick={onClickNext}>
+ Skip
+ </Text>
+ )}
+ </Container>
+ </div>
+ </Container>
+ </Island>
+ </Pointer>
+);
-export const ContinueButton: React.StatelessComponent<ContinueButtonProps> = (props: ContinueButtonProps) => {
- const isDisabled = props.display === 'disabled';
- return (
- <button disabled={isDisabled} onClick={isDisabled ? undefined : props.onClick}>
- {props.children}
- </button>
- );
+OnboardingTooltip.defaultProps = {
+ pointerDirection: 'left',
+ continueButtonText: 'Continue',
};
-export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = (props: OnboardingTooltipProps) => (
- <Island>
- <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px">
- <div className="flex flex-column">
- {props.title}
- {props.content}
- {props.continueButtonDisplay && (
- <ContinueButton onClick={props.onClickNext} display={props.continueButtonDisplay}>
- Continue
- </ContinueButton>
- )}
- {!props.hideBackButton && <button onClick={props.onClickBack}>Back</button>}
- {!props.hideNextButton && <button onClick={props.onClickNext}>Skip</button>}
- <button onClick={props.onClose}>Close</button>
- </div>
- </Container>
- </Island>
-);
-
OnboardingTooltip.displayName = 'OnboardingTooltip';
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index bf52684d7..4283022e2 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -2,11 +2,21 @@ import * as _ from 'lodash';
import * as React from 'react';
import { BigNumber } from '@0xproject/utils';
+import { Blockchain } from 'ts/blockchain';
+import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboarding_step';
+import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step';
+import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step';
+import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step';
import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow';
-import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types';
+import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step';
+import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step';
+import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step';
+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 +29,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,73 +50,117 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
/>
);
}
-
private _getSteps(): Step[] {
const steps: Step[] = [
{
target: '.wallet',
- content:
- 'Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps',
+ title: '0x Ecosystem Setup',
+ content: <InstallWalletOnboardingStep />,
placement: 'right',
- hideBackButton: true,
- hideNextButton: true,
+ shouldHideBackButton: true,
+ shouldHideNextButton: true,
},
{
target: '.wallet',
- content: 'Unlock your metamask extension to begin',
+ title: '0x Ecosystem Setup',
+ content: <UnlockWalletOnboardingStep />,
placement: 'right',
- hideBackButton: true,
- hideNextButton: true,
+ shouldHideBackButton: true,
+ shouldHideNextButton: true,
},
{
target: '.wallet',
- content:
- 'In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps',
+ title: '0x Ecosystem Account Setup',
+ content: <IntroOnboardingStep />,
placement: 'right',
- hideBackButton: true,
+ shouldHideBackButton: true,
continueButtonDisplay: 'enabled',
},
{
target: '.eth-row',
- content: 'Before you begin you will need to send some ETH to your metamask wallet',
+ title: 'Add ETH',
+ content: <AddEthOnboardingStep />,
placement: 'right',
continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled',
},
{
target: '.weth-row',
- content: 'You need to convert some of your ETH into tradeable Wrapped ETH (WETH)',
+ title: 'Step 1/2',
+ content: (
+ <WrapEthOnboardingStep
+ formattedEthBalanceIfExists={
+ this._userHasVisibleWeth() ? this._getFormattedWethBalance() : undefined
+ }
+ />
+ ),
+ placement: 'right',
+ continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : undefined,
+ },
+ {
+ target: '.weth-row',
+ title: 'Step 2/2',
+ content: (
+ <SetAllowancesOnboardingStep
+ zrxAllowanceToggle={this._renderZrxAllowanceToggle()}
+ ethAllowanceToggle={this._renderEthAllowanceToggle()}
+ />
+ ),
+ placement: 'right',
+ continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
+ },
+ {
+ target: '.wallet',
+ title: '🎉 Congrats! The ecosystem awaits.',
+ content: <CongratsOnboardingStep />,
placement: 'right',
- continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
+ continueButtonDisplay: 'enabled',
+ shouldHideNextButton: true,
+ continueButtonText: 'Enter the 0x Ecosystem',
},
];
return steps;
}
-
private _isAddressAvailable(): boolean {
return !_.isEmpty(this.props.userAddress);
}
-
private _userHasVisibleEth(): boolean {
return this.props.userEtherBalanceInWei > new BigNumber(0);
}
-
- private _userHasVisibleWeth(): boolean {
+ private _getWethBalance(): BigNumber {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
if (!ethToken) {
- return false;
+ return new BigNumber(0);
}
- const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
- return wethTokenState.balance > new BigNumber(0);
+ const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
+ return ethTokenState.balance;
+ }
+ private _getFormattedWethBalance(): string {
+ const ethToken = utils.getEthToken(this.props.tokenByAddress);
+ const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
+ return utils.getFormattedAmountFromToken(ethToken, ethTokenState);
+ }
+ private _userHasVisibleWeth(): boolean {
+ return this._getWethBalance() > 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 +170,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/onboarding/set_allowances_onboarding_step.tsx b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
new file mode 100644
index 000000000..1ff248c40
--- /dev/null
+++ b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface SetAllowancesOnboardingStepProps {
+ zrxAllowanceToggle: React.ReactNode;
+ ethAllowanceToggle: React.ReactNode;
+}
+
+export const SetAllowancesOnboardingStep: React.StatelessComponent<SetAllowancesOnboardingStepProps> = ({
+ ethAllowanceToggle,
+ zrxAllowanceToggle,
+}) => (
+ <div className="flex items-center flex-column">
+ <Text>Unlock your tokens for trading. You only need to do this once for each token.</Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-around">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> Enable WETH </Text>
+ <Container marginTop="10px">{ethAllowanceToggle}</Container>
+ </div>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> Enable ZRX </Text>
+ <Container marginTop="10px">{zrxAllowanceToggle}</Container>
+ </div>
+ </Container>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
new file mode 100644
index 000000000..6e6a74a06
--- /dev/null
+++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+
+export interface UnlockWalletOnboardingStepProps {}
+
+export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOnboardingStepProps> = () => (
+ <div className="flex items-center flex-column">
+ <div className="flex items-center flex-column">
+ <Container marginTop="15px" marginBottom="15px">
+ <img src="/images/metamask_icon.png" height="50px" width="50px" />
+ </Container>
+ <Text>Unlock your metamask extension to begin.</Text>
+ </div>
+ </div>
+);
diff --git a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
new file mode 100644
index 000000000..b21b39341
--- /dev/null
+++ b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
@@ -0,0 +1,73 @@
+import { colors } from '@0xproject/react-shared';
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { IconButton } from 'ts/components/ui/icon_button';
+import { Text } from 'ts/components/ui/text';
+
+export interface WrapEthOnboardingStepProps {
+ formattedEthBalanceIfExists?: string;
+}
+
+export const WrapEthOnboardingStep: React.StatelessComponent<WrapEthOnboardingStepProps> = ({
+ formattedEthBalanceIfExists,
+}) => {
+ if (formattedEthBalanceIfExists) {
+ return (
+ <div className="flex items-center flex-column">
+ <Text>Congrats you now have {formattedEthBalanceIfExists} in your wallet.</Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
+ </div>
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="25px">
+ <i className="zmdi zmdi-long-arrow-right" />
+ </Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
+ </div>
+ </Container>
+ </div>
+ );
+ } else {
+ return (
+ <div className="flex items-center flex-column">
+ <Text>
+ You need to convert some of your ETH into tradeable <b>Wrapped ETH (WETH)</b>.
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
+ </div>
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="36px">=</Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
+ </div>
+ </Container>
+ <Text>
+ Think of it like the coin version of a paper note. It has the same value, but some machines only
+ take coins.
+ </Text>
+ <Text>
+ Click
+ <Container display="inline-block" marginLeft="10px" marginRight="10px">
+ <IconButton
+ iconName="zmdi-long-arrow-down"
+ color={colors.mediumBlue}
+ labelText="wrap"
+ display="inline-flex"
+ />
+ </Container>
+ to wrap your ETH.
+ </Text>
+ </div>
+ );
+ }
+};
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index fb40a9580..28a303793 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -1,6 +1,7 @@
import { colors, Styles } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
@@ -24,6 +25,7 @@ import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { Container } from 'ts/components/ui/container';
import { FlashMessage } from 'ts/components/ui/flash_message';
import { Island } from 'ts/components/ui/island';
+import { Text } from 'ts/components/ui/text';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { PortalOnboardingFlow } from 'ts/containers/portal_onboarding_flow';
@@ -100,6 +102,7 @@ const THROTTLE_TIMEOUT = 100;
const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded);
const LEFT_COLUMN_WIDTH = 346;
const MENU_PADDING_LEFT = 185;
+const LARGE_LAYOUT_MAX_WIDTH = 1200;
const styles: Styles = {
root: {
@@ -235,7 +238,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}
@@ -250,6 +257,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
translate={this.props.translate}
displayType={TopBarDisplayType.Expanded}
style={{ backgroundColor: colors.lightestGrey }}
+ maxWidth={LARGE_LAYOUT_MAX_WIDTH}
/>
<div id="portal" style={styles.body}>
<Switch>
@@ -349,8 +357,26 @@ export class Portal extends React.Component<PortalProps, PortalState> {
/>
<Container marginTop="15px">
<Island>
- {/** TODO: Implement real styles. */}
- <p onClick={this._startOnboarding.bind(this)}>Start onboarding flow.</p>
+ <Container
+ marginTop="30px"
+ marginBottom="30px"
+ marginLeft="30px"
+ marginRight="30px"
+ className="flex justify-around items-center"
+ >
+ <ActionAccountBalanceWallet
+ style={{ width: '30px', height: '30px' }}
+ color={colors.orange}
+ />
+ <Text
+ fontColor={colors.grey}
+ fontSize="16px"
+ center={true}
+ onClick={this._startOnboarding.bind(this)}
+ >
+ Learn how to set up your account
+ </Text>
+ </Container>
</Island>
</Container>
</div>
@@ -634,7 +660,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
const tokenSymbols = _.keys(tokenAddressBySymbol);
try {
const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols);
- const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol));
+ const priceByAddress = _.mapKeys(priceBySymbol, (_value, symbol) => _.get(tokenAddressBySymbol, symbol));
const result = _.mapValues(priceByAddress, price => {
const priceBigNumber = new BigNumber(price);
return priceBigNumber;
@@ -656,11 +682,11 @@ interface LargeLayoutProps {
}
const LargeLayout = (props: LargeLayoutProps) => {
return (
- <div className="sm-flex flex-center">
- <div className="flex-last px3">
+ <div className="mx-auto flex flex-center" style={{ maxWidth: LARGE_LAYOUT_MAX_WIDTH }}>
+ <div className="flex-last px2">
<div style={styles.leftColumn}>{props.left}</div>
</div>
- <div className="flex-auto px3" style={styles.scrollContainer}>
+ <div className="flex-auto px2" style={styles.scrollContainer}>
{props.right}
</div>
</div>
@@ -672,7 +698,7 @@ interface SmallLayoutProps {
}
const SmallLayout = (props: SmallLayoutProps) => {
return (
- <div className="sm-flex flex-center">
+ <div className="flex flex-center">
<div className="flex-auto px3" style={styles.scrollContainer}>
{props.content}
</div>
diff --git a/packages/website/ts/components/redirecter.tsx b/packages/website/ts/components/redirector.tsx
index 07432a926..a02693003 100644
--- a/packages/website/ts/components/redirecter.tsx
+++ b/packages/website/ts/components/redirector.tsx
@@ -1,9 +1,9 @@
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 fbb634164..98d6dc0b3 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens';
import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import { colors } from 'ts/style/colors';
import { WebsiteBackendRelayerInfo } from 'ts/types';
@@ -26,7 +27,6 @@ const styles: Styles = {
header: {
height: '50%',
width: '100%',
- objectFit: 'cover',
borderBottomRightRadius: 4,
borderBottomLeftRadius: 4,
borderTopRightRadius: 4,
@@ -58,21 +58,34 @@ const styles: Styles = {
};
const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png';
+const FALLBACK_PRIMARY_COLOR = colors.grey200;
const NO_CONTENT_MESSAGE = '--';
+const RELAYER_ICON_HEIGHT = '110px';
export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => {
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
const topTokens = props.relayerInfo.topTokens;
const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
+ const headerImageUrl = props.relayerInfo.logoImgUrl;
+ const headerBackgroundColor =
+ !_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor)
+ ? props.relayerInfo.primaryColor
+ : FALLBACK_PRIMARY_COLOR;
return (
<Island style={styles.root} Component={GridTile}>
<div style={styles.innerDiv}>
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
- <ImgWithFallback
- src={props.relayerInfo.headerImgUrl}
- fallbackSrc={FALLBACK_IMG_SRC}
- style={styles.header}
- />
+ <div
+ className="flex items-center"
+ style={{ ...styles.header, backgroundColor: headerBackgroundColor }}
+ >
+ <Image
+ className="mx-auto"
+ src={props.relayerInfo.logoImgUrl}
+ fallbackSrc={FALLBACK_IMG_SRC}
+ height={RELAYER_ICON_HEIGHT}
+ />
+ </div>
</a>
<div style={styles.body}>
<div className="py1" style={styles.relayerNameLabel}>
@@ -108,32 +121,3 @@ const Section = (props: SectionProps) => {
};
const NoContent = () => <div style={styles.subLabel}>{NO_CONTENT_MESSAGE}</div>;
-
-interface ImgWithFallbackProps {
- src?: string;
- fallbackSrc: string;
- style: React.CSSProperties;
-}
-interface ImgWithFallbackState {
- imageLoadFailed: boolean;
-}
-class ImgWithFallback extends React.Component<ImgWithFallbackProps, ImgWithFallbackState> {
- constructor(props: ImgWithFallbackProps) {
- super(props);
- this.state = {
- imageLoadFailed: false,
- };
- }
- public render(): React.ReactNode {
- if (this.state.imageLoadFailed || _.isUndefined(this.props.src)) {
- return <img src={this.props.fallbackSrc} style={this.props.style} />;
- } else {
- return <img src={this.props.src} onError={this._onError.bind(this)} style={this.props.style} />;
- }
- }
- private _onError(): void {
- this.setState({
- imageLoadFailed: true,
- });
- }
-}
diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx
index 683f7084b..d565eb608 100644
--- a/packages/website/ts/components/relayer_index/relayer_index.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_index.tsx
@@ -1,11 +1,11 @@
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';
@@ -37,9 +37,9 @@ const styles: Styles = {
};
const CELL_HEIGHT = 290;
-const NUMBER_OF_COLUMNS_LARGE = 4;
-const NUMBER_OF_COLUMNS_MEDIUM = 3;
-const NUMBER_OF_COLUMNS_SMALL = 1;
+const NUMBER_OF_COLUMNS_LARGE = 3;
+const NUMBER_OF_COLUMNS_MEDIUM = 2;
+const NUMBER_OF_COLUMNS_SMALL = 2;
const GRID_PADDING = 20;
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
@@ -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/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index 555a59830..7af80745c 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -20,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 {
@@ -362,13 +362,10 @@ 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)}
/>
@@ -581,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/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 05cc6e3ad..1a69827a4 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -45,6 +45,7 @@ export interface TopBarProps {
isNightVersion?: boolean;
onVersionSelected?: (semver: string) => void;
sidebarHeader?: React.ReactNode;
+ maxWidth?: number;
}
interface TopBarState {
@@ -213,7 +214,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
const shouldShowPortalV2Drawer = this._isViewingPortal() && utils.shouldShowPortalV2();
return (
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1">
- <div className={parentClassNames}>
+ <div className={parentClassNames} style={{ maxWidth: this.props.maxWidth }}>
<div className="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}>
<Link to={`${WebsitePaths.Home}`} className="text-decoration-none">
<img src={logoUrl} height="30" />
diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx
index 4c7d59839..cb542a386 100644
--- a/packages/website/ts/components/ui/button.tsx
+++ b/packages/website/ts/components/ui/button.tsx
@@ -1,5 +1,5 @@
import { colors } from '@0xproject/react-shared';
-import { darken } from 'polished';
+import { darken, grayscale } from 'polished';
import * as React from 'react';
import { styled } from 'ts/style/theme';
@@ -7,36 +7,39 @@ export interface ButtonProps {
className?: string;
fontSize?: string;
fontColor?: string;
+ fontFamily?: string;
backgroundColor?: string;
borderColor?: string;
width?: string;
type?: string;
+ isDisabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
}
-const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, onClick, type, className }) => (
- <button type={type} className={className} onClick={onClick}>
+const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => (
+ <button type={type} className={className} onClick={isDisabled ? undefined : onClick}>
{children}
</button>
);
export const Button = styled(PlainButton)`
- cursor: pointer;
+ cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
font-size: ${props => props.fontSize};
color: ${props => props.fontColor};
+ transition: background-color 0.5s ease;
padding: 0.8em 2.2em;
border-radius: 6px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
font-weight: 500;
- font-family: 'Roboto';
+ font-family: ${props => props.fontFamily};
width: ${props => props.width};
- background-color: ${props => props.backgroundColor};
+ background-color: ${props => (props.isDisabled ? grayscale(props.backgroundColor) : props.backgroundColor)};
border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')};
&:hover {
- background-color: ${props => darken(0.1, props.backgroundColor)};
+ background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')};
}
&:active {
- background-color: ${props => darken(0.2, props.backgroundColor)};
+ background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')};
}
`;
@@ -44,6 +47,8 @@ Button.defaultProps = {
fontSize: '12px',
backgroundColor: colors.white,
width: 'auto',
+ fontFamily: 'Roboto',
+ isDisabled: false,
};
Button.displayName = 'Button';
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index c6a78e181..1776345da 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -14,8 +14,15 @@ export interface ContainerProps {
backgroundColor?: string;
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
+ width?: StringOrNum;
isHidden?: boolean;
className?: string;
+ position?: 'absolute' | 'fixed' | 'relative' | 'unset';
+ display?: 'inline-block' | 'block' | 'inline-flex' | 'inline';
+ top?: string;
+ left?: string;
+ right?: string;
+ bottom?: string;
}
export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => {
diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx
index db79ca1df..22cb942f8 100644
--- a/packages/website/ts/components/ui/drop_down.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -42,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/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/icon_button.tsx b/packages/website/ts/components/ui/icon_button.tsx
index 2f5172f05..13cd239da 100644
--- a/packages/website/ts/components/ui/icon_button.tsx
+++ b/packages/website/ts/components/ui/icon_button.tsx
@@ -5,15 +5,15 @@ import * as React from 'react';
export interface IconButtonProps {
iconName: string;
labelText?: string;
- onClick: () => void;
+ onClick?: () => void;
color?: string;
+ display?: string;
}
interface IconButtonState {
isHovering: boolean;
}
export class IconButton extends React.Component<IconButtonProps, IconButtonState> {
public static defaultProps: Partial<IconButtonProps> = {
- onClick: _.noop,
labelText: '',
color: colors.mediumBlue,
};
@@ -26,8 +26,9 @@ export class IconButton extends React.Component<IconButtonProps, IconButtonState
public render(): React.ReactNode {
const styles: Styles = {
root: {
- cursor: 'pointer',
- opacity: this.state.isHovering ? 0.5 : 1,
+ cursor: this.props.onClick ? 'pointer' : 'undefined',
+ opacity: this.state.isHovering && this.props.onClick ? 0.5 : 1,
+ display: this.props.display,
},
icon: {
color: this.props.color,
diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx
new file mode 100644
index 000000000..0958d2e5e
--- /dev/null
+++ b/packages/website/ts/components/ui/image.tsx
@@ -0,0 +1,37 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+export interface ImageProps {
+ className?: string;
+ src?: string;
+ fallbackSrc?: string;
+ height?: string;
+}
+interface ImageState {
+ imageLoadFailed: boolean;
+}
+export class Image extends React.Component<ImageProps, ImageState> {
+ constructor(props: ImageProps) {
+ super(props);
+ this.state = {
+ imageLoadFailed: false,
+ };
+ }
+ public render(): React.ReactNode {
+ const src =
+ this.state.imageLoadFailed || _.isUndefined(this.props.src) ? this.props.fallbackSrc : this.props.src;
+ return (
+ <img
+ className={this.props.className}
+ onError={this._onError.bind(this)}
+ src={src}
+ height={this.props.height}
+ />
+ );
+ }
+ private _onError(): void {
+ this.setState({
+ imageLoadFailed: true,
+ });
+ }
+}
diff --git a/packages/website/ts/components/ui/pointer.tsx b/packages/website/ts/components/ui/pointer.tsx
new file mode 100644
index 000000000..448786bb4
--- /dev/null
+++ b/packages/website/ts/components/ui/pointer.tsx
@@ -0,0 +1,67 @@
+import { colors } from '@0xproject/react-shared';
+import * as React from 'react';
+import { styled } from 'ts/style/theme';
+
+export type PointerDirection = 'top' | 'right' | 'bottom' | 'left';
+
+export interface PointerProps {
+ className?: string;
+ color?: string;
+ size?: number;
+ direction: PointerDirection;
+}
+
+const PlainPointer: React.StatelessComponent<PointerProps> = props => <div {...props} />;
+
+const positionToCss = (props: PointerProps) => {
+ const position = {
+ top: `bottom: 100%; left: 50%;`,
+ right: `left: 100%; top: 50%;`,
+ bottom: `top: 100%; left: 50%;`,
+ left: `right: 100%; top: 50%;`,
+ }[props.direction];
+
+ const borderColorSide = {
+ top: 'border-bottom-color',
+ right: 'border-left-color',
+ bottom: 'border-top-color',
+ left: 'border-right-color',
+ }[props.direction];
+ const border = `${borderColorSide}: ${props.color};`;
+ const marginSide = {
+ top: 'margin-left',
+ right: 'margin-top',
+ bottom: 'margin-left',
+ left: 'margin-top',
+ }[props.direction];
+ const margin = `${marginSide}: -${props.size}px`;
+ return {
+ position,
+ border,
+ margin,
+ };
+};
+
+export const Pointer = styled(PlainPointer)`
+ position: relative;
+ &:after {
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-color: rgba(136, 183, 213, 0);
+ border-width: ${props => `${props.size}px`};
+ ${props => positionToCss(props).position}
+ ${props => positionToCss(props).border}
+ ${props => positionToCss(props).margin}
+ }
+`;
+
+Pointer.defaultProps = {
+ color: colors.white,
+ size: 16,
+};
+
+Pointer.displayName = 'Pointer';
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/text.tsx b/packages/website/ts/components/ui/text.tsx
index e90c1707d..1e2a123b7 100644
--- a/packages/website/ts/components/ui/text.tsx
+++ b/packages/website/ts/components/ui/text.tsx
@@ -1,8 +1,9 @@
import { colors } from '@0xproject/react-shared';
+import { darken } from 'polished';
import * as React from 'react';
import { styled } from 'ts/style/theme';
-export type TextTag = 'p' | 'div' | 'span' | 'label';
+export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4';
export interface TextProps {
className?: string;
@@ -11,12 +12,16 @@ export interface TextProps {
fontFamily?: string;
fontColor?: string;
lineHeight?: string;
+ minHeight?: string;
center?: boolean;
- fontWeight?: number;
+ fontWeight?: number | string;
+ onClick?: () => void;
}
-const PlainText: React.StatelessComponent<TextProps> = ({ children, className, Tag }) => (
- <Tag className={className}>{children}</Tag>
+const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
+ <Tag className={className} onClick={onClick}>
+ {children}
+ </Tag>
);
export const Text = styled(PlainText)`
@@ -26,14 +31,32 @@ export const Text = styled(PlainText)`
${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
${props => (props.center ? 'text-align: center' : '')};
color: ${props => props.fontColor};
+ ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
+ ${props => (props.onClick ? 'cursor: pointer' : '')};
+ transition: color 0.5s ease;
+ &:hover {
+ ${props => (props.onClick ? `color: ${darken(0.1, props.fontColor)}` : '')};
+ }
`;
Text.defaultProps = {
fontFamily: 'Roboto',
fontWeight: 400,
- fontColor: colors.white,
- fontSize: '14px',
+ fontColor: colors.black,
+ fontSize: '15px',
+ lineHeight: '1.5em',
Tag: 'div',
};
Text.displayName = 'Text';
+
+export const Title: React.StatelessComponent<TextProps> = props => <Text {...props} />;
+
+Title.defaultProps = {
+ Tag: 'h2',
+ fontSize: '20px',
+ fontWeight: 600,
+ fontColor: colors.black,
+};
+
+Title.displayName = 'Title';
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index bc2ee227d..3a6d9942d 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -13,7 +13,6 @@ import { Link } from 'react-router-dom';
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';
@@ -21,6 +20,7 @@ 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';
@@ -124,7 +124,6 @@ const styles: Styles = {
const ETHER_ICON_PATH = '/images/ether.png';
const ICON_DIMENSION = 28;
-const TOKEN_AMOUNT_DISPLAY_PRECISION = 5;
const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
const FOOTER_ITEM_KEY = 'FOOTER';
@@ -222,7 +221,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,
});
@@ -314,7 +313,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,
@@ -414,15 +413,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)}
/>
@@ -441,10 +437,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</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}`;
+ const result = utils.getFormattedAmount(amount, decimals, symbol);
return <div style={styles.amountLabel}>{result}</div>;
}
}
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
index a5052735b..f65257142 100644
--- a/packages/website/ts/components/wallet/wrap_ether_item.tsx
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -145,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 3dbdcd16b..3b1c99d79 100644
--- a/packages/website/ts/containers/about.ts
+++ b/packages/website/ts/containers/about.ts
@@ -14,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 2ecd8107a..f939ef0df 100644
--- a/packages/website/ts/containers/connect_documentation.ts
+++ b/packages/website/ts/containers/connect_documentation.ts
@@ -89,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
index f6d614779..285438835 100644
--- a/packages/website/ts/containers/ethereum_types_documentation.ts
+++ b/packages/website/ts/containers/ethereum_types_documentation.ts
@@ -106,7 +106,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/faq.ts b/packages/website/ts/containers/faq.ts
index b91c47889..a2b5735f6 100644
--- a/packages/website/ts/containers/faq.ts
+++ b/packages/website/ts/containers/faq.ts
@@ -14,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 44979b104..92296dbab 100644
--- a/packages/website/ts/containers/generate_order_form.ts
+++ b/packages/website/ts/containers/generate_order_form.ts
@@ -30,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 4df7ddecb..67740d4c6 100644
--- a/packages/website/ts/containers/json_schemas_documentation.ts
+++ b/packages/website/ts/containers/json_schemas_documentation.ts
@@ -70,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 a9fc1d9d1..972ed4c23 100644
--- a/packages/website/ts/containers/landing.ts
+++ b/packages/website/ts/containers/landing.ts
@@ -14,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 4fdc325ea..f384dab89 100644
--- a/packages/website/ts/containers/not_found.ts
+++ b/packages/website/ts/containers/not_found.ts
@@ -14,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 cdf97e5c8..37b7f2273 100644
--- a/packages/website/ts/containers/order_utils_documentation.ts
+++ b/packages/website/ts/containers/order_utils_documentation.ts
@@ -81,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 27328909c..c88c3b365 100644
--- a/packages/website/ts/containers/smart_contracts_documentation.ts
+++ b/packages/website/ts/containers/smart_contracts_documentation.ts
@@ -75,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 4ccc1850f..8720e2c1d 100644
--- a/packages/website/ts/containers/sol_compiler_documentation.ts
+++ b/packages/website/ts/containers/sol_compiler_documentation.ts
@@ -67,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 73cc99a8c..a8009071f 100644
--- a/packages/website/ts/containers/sol_cov_documentation.ts
+++ b/packages/website/ts/containers/sol_cov_documentation.ts
@@ -96,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 0c7e73f38..6d4230e53 100644
--- a/packages/website/ts/containers/subproviders_documentation.ts
+++ b/packages/website/ts/containers/subproviders_documentation.ts
@@ -128,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 34633f14d..b04a83ac4 100644
--- a/packages/website/ts/containers/web3_wrapper_documentation.ts
+++ b/packages/website/ts/containers/web3_wrapper_documentation.ts
@@ -105,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 af7228dbe..357f8bbf4 100644
--- a/packages/website/ts/containers/wiki.ts
+++ b/packages/website/ts/containers/wiki.ts
@@ -14,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 9c3f447fb..bd0d1732a 100644
--- a/packages/website/ts/containers/zero_ex_js_documentation.ts
+++ b/packages/website/ts/containers/zero_ex_js_documentation.ts
@@ -173,6 +173,7 @@ const docsInfoConfig: DocsInfoConfig = {
'OrderStateInvalid',
'OrderState',
'OrderStateWatcherConfig',
+ 'OrderWatcherConfig',
'FilterObject',
'OrderRelevantState',
'JSONRPCRequestPayload',
@@ -209,7 +210,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 719604c02..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';
@@ -86,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} />
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/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/not_found.tsx b/packages/website/ts/pages/not_found.tsx
index a94ba5863..2fe3b1f45 100644
--- a/packages/website/ts/pages/not_found.tsx
+++ b/packages/website/ts/pages/not_found.tsx
@@ -11,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 bdefe0fda..9659900be 100644
--- a/packages/website/ts/pages/wiki/wiki.tsx
+++ b/packages/website/ts/pages/wiki/wiki.tsx
@@ -233,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,
});
@@ -243,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/style/colors.ts b/packages/website/ts/style/colors.ts
index 5ffdd6ba7..b15000d7a 100644
--- a/packages/website/ts/style/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/types.ts b/packages/website/ts/types.ts
index 15444e517..d00154652 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -519,6 +519,8 @@ export interface WebsiteBackendRelayerInfo {
url: string;
appUrl?: string;
headerImgUrl?: string;
+ logoImgUrl?: string;
+ primaryColor?: string;
topTokens: WebsiteBackendTokenInfo[];
}
@@ -536,4 +538,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 6b16aea6b..835a6ef4d 100644
--- a/packages/website/ts/utils/backend_client.ts
+++ b/packages/website/ts/utils/backend_client.ts
@@ -1,10 +1,17 @@
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';
@@ -15,6 +22,10 @@ export const backendClient = {
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 {};
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index d281c5738..25670ef27 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -6,6 +6,7 @@ export const constants = {
ETHER_TOKEN_SYMBOL: 'WETH',
ZRX_TOKEN_SYMBOL: 'ZRX',
ETHER_SYMBOL: 'ETH',
+ TOKEN_AMOUNT_DISPLAY_PRECISION: 5,
GENESIS_ORDER_BLOCK_BY_NETWORK_ID: {
1: 4145578,
42: 3117574,
diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts
index 548d4d41d..f875141fe 100644
--- a/packages/website/ts/utils/error_reporter.ts
+++ b/packages/website/ts/utils/error_reporter.ts
@@ -37,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/utils.ts b/packages/website/ts/utils/utils.ts
index 6961784c6..414361c1b 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -3,6 +3,7 @@ import { OrderError } from '@0xproject/order-utils';
import { constants as sharedConstants, Networks } from '@0xproject/react-shared';
import { ECSignature, Provider } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
import deepEqual = require('deep-equal');
import * as _ from 'lodash';
import * as moment from 'moment';
@@ -17,6 +18,7 @@ import {
SideToAssetToken,
Token,
TokenByAddress,
+ TokenState,
} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -152,7 +154,7 @@ export const utils = {
const intervalId = setTimeout(() => {
resolve(false);
}, getApiVersionTimeoutMs);
- u2f.getApiVersion((version: number) => {
+ u2f.getApiVersion((_version: number) => {
clearTimeout(intervalId);
resolve(true);
});
@@ -279,7 +281,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();
});
},
@@ -318,9 +320,27 @@ 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;
+ },
+ getFormattedAmountFromToken(token: Token, tokenState: TokenState): string {
+ return utils.getFormattedAmount(tokenState.balance, token.decimals, token.symbol);
+ },
+ getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string {
+ const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
+ const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces());
+ const formattedAmount = unitAmount.toFixed(precision);
+ return `${formattedAmount} ${symbol}`;
},
};