aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts')
-rw-r--r--packages/website/ts/components/footer.tsx2
-rw-r--r--packages/website/ts/components/inputs/allowance_state_toggle.tsx160
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx140
-rw-r--r--packages/website/ts/components/meta_tags.tsx25
-rw-r--r--packages/website/ts/components/onboarding/onboarding_tooltip.tsx2
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx19
-rw-r--r--packages/website/ts/components/portal/portal.tsx10
-rw-r--r--packages/website/ts/components/token_balances.tsx19
-rw-r--r--packages/website/ts/components/ui/allowance_state_view.tsx51
-rw-r--r--packages/website/ts/components/ui/container.tsx9
-rw-r--r--packages/website/ts/components/ui/image.tsx6
-rw-r--r--packages/website/ts/components/ui/pointer.tsx7
-rw-r--r--packages/website/ts/components/ui/spinner.tsx54
-rw-r--r--packages/website/ts/components/ui/text.tsx6
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx31
-rw-r--r--packages/website/ts/containers/inputs/allowance_state_toggle.ts (renamed from packages/website/ts/containers/inputs/allowance_toggle.ts)13
-rw-r--r--packages/website/ts/index.tsx152
-rw-r--r--packages/website/ts/pages/about/about.tsx2
-rw-r--r--packages/website/ts/pages/jobs/benefits.tsx205
-rw-r--r--packages/website/ts/pages/jobs/jobs.tsx28
-rw-r--r--packages/website/ts/pages/jobs/join_0x.tsx43
-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.tsx67
-rw-r--r--packages/website/ts/pages/jobs/open_positions.tsx174
-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/style/keyframes.ts22
-rw-r--r--packages/website/ts/types.ts3
-rw-r--r--packages/website/ts/utils/constants.ts2
-rw-r--r--packages/website/ts/utils/utils.ts3
31 files changed, 753 insertions, 693 deletions
diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx
index 9fb332a98..6dcb5a0e9 100644
--- a/packages/website/ts/components/footer.tsx
+++ b/packages/website/ts/components/footer.tsx
@@ -115,7 +115,7 @@ export class Footer extends React.Component<FooterProps, FooterState> {
{
title: this.props.translate.get(Key.Careers, Deco.Cap),
isExternal: false,
- path: WebsitePaths.Jobs,
+ path: WebsitePaths.Careers,
},
{
title: this.props.translate.get(Key.Contact, Deco.Cap),
diff --git a/packages/website/ts/components/inputs/allowance_state_toggle.tsx b/packages/website/ts/components/inputs/allowance_state_toggle.tsx
new file mode 100644
index 000000000..39d2e3030
--- /dev/null
+++ b/packages/website/ts/components/inputs/allowance_state_toggle.tsx
@@ -0,0 +1,160 @@
+import { colors } from '@0xproject/react-shared';
+import { BigNumber, logUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import ReactTooltip = require('react-tooltip');
+import { Blockchain } from 'ts/blockchain';
+import { AllowanceState, AllowanceStateView } from 'ts/components/ui/allowance_state_view';
+import { Container } from 'ts/components/ui/container';
+import { PointerDirection } from 'ts/components/ui/pointer';
+import { Text } from 'ts/components/ui/text';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { BalanceErrs, Token, TokenState } from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
+import { errorReporter } from 'ts/utils/error_reporter';
+import { utils } from 'ts/utils/utils';
+
+export interface AllowanceStateToggleProps {
+ networkId: number;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ token: Token;
+ tokenState: TokenState;
+ userAddress: string;
+ onErrorOccurred?: (errType: BalanceErrs) => void;
+ refetchTokenStateAsync: () => Promise<void>;
+ tooltipDirection?: PointerDirection;
+}
+
+export interface AllowanceStateToggleState {
+ allowanceState: AllowanceState;
+ prevTokenState: TokenState;
+ loadingMessage?: string;
+}
+
+const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1);
+
+export class AllowanceStateToggle extends React.Component<AllowanceStateToggleProps, AllowanceStateToggleState> {
+ public static defaultProps = {
+ onErrorOccurred: _.noop.bind(_),
+ tooltipDirection: PointerDirection.Right,
+ };
+ private static _getAllowanceState(tokenState: TokenState): AllowanceState {
+ if (!tokenState.isLoaded) {
+ return AllowanceState.Loading;
+ }
+ if (tokenState.allowance.gt(0)) {
+ return AllowanceState.Unlocked;
+ }
+ return AllowanceState.Locked;
+ }
+ constructor(props: AllowanceStateToggleProps) {
+ super(props);
+ const tokenState = props.tokenState;
+ this.state = {
+ allowanceState: AllowanceStateToggle._getAllowanceState(tokenState),
+ prevTokenState: tokenState,
+ };
+ }
+
+ public render(): React.ReactNode {
+ const tooltipId = `tooltip-id-${this.props.token.symbol}`;
+ return (
+ <Container cursor="pointer">
+ <ReactTooltip id={tooltipId} effect="solid" offset={{ top: 3 }}>
+ {this._getTooltipContent()}
+ </ReactTooltip>
+ <div
+ data-tip={true}
+ data-for={tooltipId}
+ data-place={this.props.tooltipDirection}
+ onClick={this._onToggleAllowanceAsync.bind(this)}
+ >
+ <AllowanceStateView allowanceState={this.state.allowanceState} />
+ </div>
+ </Container>
+ );
+ }
+ public componentWillReceiveProps(nextProps: AllowanceStateToggleProps): void {
+ const nextTokenState = nextProps.tokenState;
+ const prevTokenState = this.state.prevTokenState;
+ if (
+ !nextTokenState.allowance.eq(prevTokenState.allowance) ||
+ nextTokenState.isLoaded !== prevTokenState.isLoaded
+ ) {
+ const tokenState = nextProps.tokenState;
+ this.setState({
+ prevTokenState: tokenState,
+ allowanceState: AllowanceStateToggle._getAllowanceState(nextTokenState),
+ });
+ }
+ }
+ private _getTooltipContent(): React.ReactNode {
+ const symbol = this.props.token.symbol;
+ switch (this.state.allowanceState) {
+ case AllowanceState.Loading:
+ return (
+ <Text noWrap={true} fontColor={colors.white}>
+ {this.state.loadingMessage || 'Loading...'}
+ </Text>
+ );
+ case AllowanceState.Locked:
+ return (
+ <Text noWrap={true} fontColor={colors.white}>
+ Click to enable <b>{symbol}</b> for trading
+ </Text>
+ );
+ case AllowanceState.Unlocked:
+ return (
+ <Text noWrap={true} fontColor={colors.white}>
+ <b>{symbol}</b> is available for trading
+ </Text>
+ );
+ default:
+ return null;
+ }
+ }
+ private async _onToggleAllowanceAsync(): Promise<void> {
+ // Close all tooltips
+ ReactTooltip.hide();
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
+
+ let newAllowanceAmountInBaseUnits = new BigNumber(0);
+ if (!this._isAllowanceSet()) {
+ newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
+ }
+ const isUnlockingToken = newAllowanceAmountInBaseUnits.gt(0);
+ this.setState({
+ allowanceState: AllowanceState.Loading,
+ loadingMessage: `${isUnlockingToken ? 'Unlocking' : 'Locking'} ${this.props.token.symbol}`,
+ });
+ const logData = {
+ tokenSymbol: this.props.token.symbol,
+ newAllowance: newAllowanceAmountInBaseUnits.toNumber(),
+ };
+ try {
+ await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
+ analytics.track('Set Allowances Success', logData);
+ await this.props.refetchTokenStateAsync();
+ } catch (err) {
+ analytics.track('Set Allowance Failure', logData);
+ this.setState({
+ allowanceState: AllowanceStateToggle._getAllowanceState(this.state.prevTokenState),
+ });
+ const errMsg = `${err}`;
+ if (utils.didUserDenyWeb3Request(errMsg)) {
+ return;
+ }
+ logUtils.log(`Unexpected error encountered: ${err}`);
+ logUtils.log(err.stack);
+ this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
+ errorReporter.report(err);
+ }
+ }
+ private _isAllowanceSet(): boolean {
+ return !this.props.tokenState.allowance.eq(0);
+ }
+}
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
deleted file mode 100644
index 05dce134a..000000000
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { 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';
-import { Dispatcher } from 'ts/redux/dispatcher';
-import { colors } from 'ts/style/colors';
-import { BalanceErrs, Token, TokenState } from 'ts/types';
-import { analytics } from 'ts/utils/analytics';
-import { errorReporter } from 'ts/utils/error_reporter';
-import { utils } from 'ts/utils/utils';
-
-const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1);
-
-interface AllowanceToggleProps {
- networkId: number;
- blockchain: Blockchain;
- dispatcher: Dispatcher;
- token: Token;
- tokenState: TokenState;
- userAddress: string;
- isDisabled?: boolean;
- onErrorOccurred?: (errType: BalanceErrs) => void;
- refetchTokenStateAsync: () => Promise<void>;
-}
-
-interface AllowanceToggleState {
- isSpinnerVisible: boolean;
- prevAllowance: BigNumber;
-}
-
-const styles: Styles = {
- baseThumbStyle: {
- height: 10,
- width: 10,
- top: 6,
- backgroundColor: colors.white,
- boxShadow: `0px 0px 0px ${colors.allowanceToggleShadow}`,
- },
- offThumbStyle: {
- left: 4,
- },
- onThumbStyle: {
- left: 25,
- },
- baseTrackStyle: {
- width: 25,
- },
- offTrackStyle: {
- backgroundColor: colors.grey300,
- },
- onTrackStyle: {
- backgroundColor: colors.mediumBlue,
- },
-};
-
-export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> {
- public static defaultProps = {
- onErrorOccurred: _.noop.bind(_),
- isDisabled: false,
- };
- constructor(props: AllowanceToggleProps) {
- super(props);
- this.state = {
- isSpinnerVisible: false,
- prevAllowance: props.tokenState.allowance,
- };
- }
- public componentWillReceiveProps(nextProps: AllowanceToggleProps): void {
- if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) {
- this.setState({
- isSpinnerVisible: false,
- prevAllowance: nextProps.tokenState.allowance,
- });
- }
- }
- public render(): React.ReactNode {
- return (
- <div className="flex">
- <div>
- <Toggle
- disabled={this.state.isSpinnerVisible || this.props.isDisabled}
- toggled={this._isAllowanceSet()}
- onToggle={this._onToggleAllowanceAsync.bind(this)}
- thumbStyle={{ ...styles.baseThumbStyle, ...styles.offThumbStyle }}
- thumbSwitchedStyle={{ ...styles.baseThumbStyle, ...styles.onThumbStyle }}
- trackStyle={{ ...styles.baseTrackStyle, ...styles.offTrackStyle }}
- trackSwitchedStyle={{ ...styles.baseTrackStyle, ...styles.onTrackStyle }}
- />
- </div>
- {this.state.isSpinnerVisible && (
- <div className="pl1" style={{ paddingTop: 3 }}>
- <i className="zmdi zmdi-spinner zmdi-hc-spin" />
- </div>
- )}
- </div>
- );
- }
- private async _onToggleAllowanceAsync(): Promise<void> {
- if (this.props.userAddress === '') {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return;
- }
-
- this.setState({
- isSpinnerVisible: true,
- });
-
- let newAllowanceAmountInBaseUnits = new BigNumber(0);
- if (!this._isAllowanceSet()) {
- newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
- }
- const logData = {
- tokenSymbol: this.props.token.symbol,
- newAllowance: newAllowanceAmountInBaseUnits.toNumber(),
- };
- try {
- await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
- analytics.track('Set Allowances Success', logData);
- await this.props.refetchTokenStateAsync();
- } catch (err) {
- analytics.track('Set Allowance Failure', logData);
- this.setState({
- isSpinnerVisible: false,
- });
- const errMsg = `${err}`;
- if (utils.didUserDenyWeb3Request(errMsg)) {
- return;
- }
- logUtils.log(`Unexpected error encountered: ${err}`);
- logUtils.log(err.stack);
- this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
- errorReporter.report(err);
- }
- }
- private _isAllowanceSet(): boolean {
- return !this.props.tokenState.allowance.eq(0);
- }
-}
diff --git a/packages/website/ts/components/meta_tags.tsx b/packages/website/ts/components/meta_tags.tsx
new file mode 100644
index 000000000..f6c43d23f
--- /dev/null
+++ b/packages/website/ts/components/meta_tags.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { Helmet } from 'react-helmet';
+
+export interface MetaTagsProps {
+ title: string;
+ description: string;
+ imgSrc?: string;
+}
+
+export const MetaTags: React.StatelessComponent<MetaTagsProps> = ({ title, description, imgSrc }) => (
+ <Helmet>
+ <title>{title}</title>
+ <meta name="description" content={description} />
+ <meta property="og:title" content={title} />
+ <meta property="og:description" content={description} />
+ <meta property="og:type" content="website" />
+ <meta property="og:image" content={imgSrc} />
+ <meta name="twitter:site" content="@0xproject" />
+ <meta name="twitter:image" content={imgSrc} />
+ </Helmet>
+);
+
+MetaTags.defaultProps = {
+ imgSrc: '/images/og_image.png',
+};
diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
index 15d47908d..ff5f0bab6 100644
--- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx
@@ -24,7 +24,7 @@ export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps>
);
};
OnboardingTooltip.defaultProps = {
- pointerDisplay: 'left',
+ pointerDisplay: PointerDirection.Left,
};
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 f395674a1..522687758 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -21,7 +21,7 @@ import {
WrapEthOnboardingStep2,
WrapEthOnboardingStep3,
} from 'ts/components/onboarding/wrap_eth_onboarding_step';
-import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
+import { AllowanceStateToggle } from 'ts/containers/inputs/allowance_state_toggle';
import { BrowserType, ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { utils } from 'ts/utils/utils';
@@ -149,8 +149,8 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
title: 'Step 3: Unlock Tokens',
content: (
<SetAllowancesOnboardingStep
- zrxAllowanceToggle={this._renderZrxAllowanceToggle()}
- ethAllowanceToggle={this._renderEthAllowanceToggle()}
+ zrxAllowanceToggle={this._renderZrxAllowanceStateToggle()}
+ ethAllowanceToggle={this._renderEthAllowanceStateToggle()}
doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()}
/>
),
@@ -243,15 +243,15 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
stepIndex: this.props.stepIndex,
});
}
- private _renderZrxAllowanceToggle(): React.ReactNode {
+ private _renderZrxAllowanceStateToggle(): React.ReactNode {
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
- return this._renderAllowanceToggle(zrxToken);
+ return this._renderAllowanceStateToggle(zrxToken);
}
- private _renderEthAllowanceToggle(): React.ReactNode {
+ private _renderEthAllowanceStateToggle(): React.ReactNode {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
- return this._renderAllowanceToggle(ethToken);
+ return this._renderAllowanceStateToggle(ethToken);
}
- private _renderAllowanceToggle(token: Token): React.ReactNode {
+ private _renderAllowanceStateToggle(token: Token): React.ReactNode {
if (!token) {
return null;
}
@@ -260,10 +260,9 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
return null;
}
return (
- <AllowanceToggle
+ <AllowanceStateToggle
token={token}
tokenState={tokenStateIfExists}
- isDisabled={!tokenStateIfExists.isLoaded}
blockchain={this.props.blockchain}
// tslint:disable-next-line:jsx-no-lambda
refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)}
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 1790a9678..ff11880e3 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -12,6 +12,7 @@ import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_
import { EthWrappers } from 'ts/components/eth_wrappers';
import { FillOrder } from 'ts/components/fill_order';
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
+import { MetaTags } from 'ts/components/meta_tags';
import { BackButton } from 'ts/components/portal/back_button';
import { Loading } from 'ts/components/portal/loading';
import { Menu, MenuTheme } from 'ts/components/portal/menu';
@@ -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 { Image } from 'ts/components/ui/image';
+import { PointerDirection } from 'ts/components/ui/pointer';
import { Text } from 'ts/components/ui/text';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
@@ -107,6 +109,8 @@ const LEFT_COLUMN_WIDTH = 346;
const MENU_PADDING_LEFT = 185;
const LARGE_LAYOUT_MAX_WIDTH = 1200;
const SIDE_PADDING = 20;
+const DOCUMENT_TITLE = '0x Portal';
+const DOCUMENT_DESCRIPTION = 'Learn about and trade on 0x Relayers';
export class Portal extends React.Component<PortalProps, PortalState> {
private _blockchain: Blockchain;
@@ -225,7 +229,8 @@ export class Portal extends React.Component<PortalProps, PortalState> {
: TokenVisibility.TRACKED;
return (
<Container>
- <DocumentTitle title="0x Portal" />
+ <MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
+ <DocumentTitle title={DOCUMENT_TITLE} />
<TopBar
userAddress={this.props.userAddress}
networkId={this.props.networkId}
@@ -355,6 +360,9 @@ export class Portal extends React.Component<PortalProps, PortalState> {
onAddToken={this._onAddToken.bind(this)}
onRemoveToken={this._onRemoveToken.bind(this)}
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
+ toggleTooltipDirection={
+ this.props.isPortalOnboardingShowing ? PointerDirection.Left : PointerDirection.Right
+ }
/>
</Container>
{!isMobile && <Container marginTop="8px">{this._renderStartOnboarding()}</Container>}
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index 550438e76..969ef32ff 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -24,7 +24,7 @@ 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 { AllowanceStateToggle } from 'ts/containers/inputs/allowance_state_toggle';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
import {
@@ -372,14 +372,15 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
)}
</TableRowColumn>
<TableRowColumn>
- <AllowanceToggle
- blockchain={this.props.blockchain}
- token={token}
- tokenState={tokenState}
- onErrorOccurred={this._onErrorOccurred.bind(this)}
- isDisabled={!tokenState.isLoaded}
- refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
- />
+ <div className="flex justify-center">
+ <AllowanceStateToggle
+ blockchain={this.props.blockchain}
+ token={token}
+ tokenState={tokenState}
+ onErrorOccurred={this._onErrorOccurred.bind(this)}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
+ />
+ </div>
</TableRowColumn>
{utils.isTestNetwork(this.props.networkId) && (
<TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}>
diff --git a/packages/website/ts/components/ui/allowance_state_view.tsx b/packages/website/ts/components/ui/allowance_state_view.tsx
new file mode 100644
index 000000000..93d6b0ebb
--- /dev/null
+++ b/packages/website/ts/components/ui/allowance_state_view.tsx
@@ -0,0 +1,51 @@
+import { colors } from '@0xproject/react-shared';
+import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Spinner } from 'ts/components/ui/spinner';
+
+export enum AllowanceState {
+ Locked,
+ Unlocked,
+ Loading,
+}
+
+export interface AllowanceStateViewProps {
+ allowanceState: AllowanceState;
+}
+
+export const AllowanceStateView: React.StatelessComponent<AllowanceStateViewProps> = ({ allowanceState }) => {
+ switch (allowanceState) {
+ case AllowanceState.Locked:
+ return renderLock();
+ case AllowanceState.Unlocked:
+ return renderCheck();
+ case AllowanceState.Loading:
+ return (
+ <Container position="relative" top="3px" left="5px">
+ <Spinner size={18} strokeSize={2} />
+ </Container>
+ );
+ default:
+ return null;
+ }
+};
+
+const renderCheck = (color: string = colors.lightGreen) => (
+ <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="8.5" cy="8.5" r="8.5" fill={color} />
+ <path
+ d="M2.5 4.5L1.79289 5.20711L2.5 5.91421L3.20711 5.20711L2.5 4.5ZM-0.707107 2.70711L1.79289 5.20711L3.20711 3.79289L0.707107 1.29289L-0.707107 2.70711ZM3.20711 5.20711L7.70711 0.707107L6.29289 -0.707107L1.79289 3.79289L3.20711 5.20711Z"
+ transform="translate(5 6.5)"
+ fill="white"
+ />
+ </svg>
+);
+
+const renderLock = () => (
+ <svg width="12" height="15" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M6 0C3.51604 0 1.48688 2.0495 1.48688 4.55837V5.86581C0.664723 5.86581 -3.33647e-08 6.53719 -3.33647e-08 7.36759V13.3217C-3.33647e-08 14.1521 0.664723 14.8235 1.48688 14.8235H10.5131C11.3353 14.8235 12 14.1521 12 13.3217V7.36759C12 6.53719 11.3353 5.86581 10.5131 5.86581V4.55837C10.5131 2.0495 8.48396 0 6 0ZM8.93878 5.86581H3.06122V4.55837C3.06122 2.9329 4.37318 1.59013 6 1.59013C7.62682 1.59013 8.93878 2.9329 8.93878 4.55837V5.86581Z"
+ fill="black"
+ />
+ </svg>
+);
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index 427cc6cc7..f2ae68b70 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -9,6 +9,7 @@ export interface ContainerProps {
marginBottom?: StringOrNum;
marginRight?: StringOrNum;
marginLeft?: StringOrNum;
+ padding?: StringOrNum;
paddingTop?: StringOrNum;
paddingBottom?: StringOrNum;
paddingRight?: StringOrNum;
@@ -31,13 +32,17 @@ export interface ContainerProps {
bottom?: string;
zIndex?: number;
Tag?: ContainerTag;
+ cursor?: string;
+ id?: string;
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
+ overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible';
}
export const Container: React.StatelessComponent<ContainerProps> = props => {
- const { children, className, Tag, isHidden, ...style } = props;
+ const { children, className, Tag, isHidden, id, onClick, ...style } = props;
const visibility = isHidden ? 'hidden' : undefined;
return (
- <Tag style={{ ...style, visibility }} className={className}>
+ <Tag id={id} style={{ ...style, visibility }} className={className} onClick={onClick}>
{children}
</Tag>
);
diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx
index c4ff93531..c8d39352b 100644
--- a/packages/website/ts/components/ui/image.tsx
+++ b/packages/website/ts/components/ui/image.tsx
@@ -5,9 +5,11 @@ export interface ImageProps {
className?: string;
src?: string;
fallbackSrc?: string;
- height?: string | number;
borderRadius?: string;
width?: string | number;
+ height?: string | number;
+ maxWidth?: string | number;
+ maxHeight?: string | number;
}
interface ImageState {
imageLoadFailed: boolean;
@@ -29,6 +31,8 @@ export class Image extends React.Component<ImageProps, ImageState> {
src={src}
style={{
borderRadius: this.props.borderRadius,
+ maxWidth: this.props.maxWidth,
+ maxHeight: this.props.maxHeight,
}}
height={this.props.height}
width={this.props.width}
diff --git a/packages/website/ts/components/ui/pointer.tsx b/packages/website/ts/components/ui/pointer.tsx
index 448786bb4..db0a4188d 100644
--- a/packages/website/ts/components/ui/pointer.tsx
+++ b/packages/website/ts/components/ui/pointer.tsx
@@ -2,7 +2,12 @@ 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 enum PointerDirection {
+ Top = 'top',
+ Right = 'right',
+ Bottom = 'bottom',
+ Left = 'left',
+}
export interface PointerProps {
className?: string;
diff --git a/packages/website/ts/components/ui/spinner.tsx b/packages/website/ts/components/ui/spinner.tsx
new file mode 100644
index 000000000..e8670cc3e
--- /dev/null
+++ b/packages/website/ts/components/ui/spinner.tsx
@@ -0,0 +1,54 @@
+import { colors } from '@0xproject/react-shared';
+import * as React from 'react';
+import { styled } from 'ts/style/theme';
+
+import { dash, rotate } from 'ts/style/keyframes';
+
+interface SpinnerSvgProps {
+ color: string;
+ size: number;
+ viewBox?: string;
+}
+
+const SpinnerSvg: React.StatelessComponent<SpinnerSvgProps> = props => <svg {...props} />;
+
+const StyledSpinner = styled(SpinnerSvg)`
+ animation: ${rotate} 3s linear infinite;
+ margin: ${props => `-${props.size / 2}px 0 0 -${props.size / 2}px`};
+ margin-top: ${props => `-${props.size / 2}px`};
+ margin-left: ${props => `-${props.size / 2}px`};
+ margin-bottom: 0px;
+ margin-right: 0px;
+ size: ${props => `${props.size}px`};
+ height: ${props => `${props.size}px`};
+
+ & .path {
+ stroke: ${props => props.color};
+ stroke-linecap: round;
+ animation: ${dash} 2.5s ease-in-out infinite;
+ }
+`;
+
+export interface SpinnerProps {
+ size?: number;
+ strokeSize?: number;
+ color?: string;
+}
+
+export const Spinner: React.StatelessComponent<SpinnerProps> = ({ size, strokeSize, color }) => {
+ const c = size / 2;
+ const r = c - strokeSize;
+ return (
+ <StyledSpinner color={color} size={size} viewBox={`0 0 ${size} ${size}`}>
+ <circle className="path" cx={c} cy={c} r={r} fill="none" strokeWidth={strokeSize} />
+ </StyledSpinner>
+ );
+};
+
+Spinner.defaultProps = {
+ size: 50,
+ color: colors.mediumBlue,
+ strokeSize: 4,
+};
+
+Spinner.displayName = 'Spinner';
diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx
index 315f72854..734483564 100644
--- a/packages/website/ts/components/ui/text.tsx
+++ b/packages/website/ts/components/ui/text.tsx
@@ -10,6 +10,7 @@ export interface TextProps {
Tag?: TextTag;
fontSize?: string;
fontFamily?: string;
+ fontStyle?: string;
fontColor?: string;
lineHeight?: string;
minHeight?: string;
@@ -18,6 +19,7 @@ export interface TextProps {
textDecorationLine?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
hoverColor?: string;
+ noWrap?: boolean;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
@@ -28,6 +30,7 @@ const PlainText: React.StatelessComponent<TextProps> = ({ children, className, o
export const Text = styled(PlainText)`
font-family: ${props => props.fontFamily};
+ font-style: ${props => props.fontStyle};
font-weight: ${props => props.fontWeight};
font-size: ${props => props.fontSize};
text-decoration-line: ${props => props.textDecorationLine};
@@ -37,6 +40,7 @@ export const Text = styled(PlainText)`
${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
+ ${props => (props.noWrap ? 'white-space: nowrap' : '')};
&:hover {
${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')};
}
@@ -44,12 +48,14 @@ export const Text = styled(PlainText)`
Text.defaultProps = {
fontFamily: 'Roboto',
+ fontStyle: 'normal',
fontWeight: 400,
fontColor: colors.black,
fontSize: '15px',
lineHeight: '1.5em',
textDecorationLine: 'none',
Tag: 'div',
+ noWrap: false,
};
Text.displayName = 'Text';
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 40a8a23ea..6abaa840b 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -14,6 +14,7 @@ import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
import { Island } from 'ts/components/ui/island';
+import { PointerDirection } from 'ts/components/ui/pointer';
import {
CopyAddressSimpleMenuItem,
DifferentWalletSimpleMenuItem,
@@ -28,7 +29,7 @@ import { NullTokenRow } from 'ts/components/wallet/null_token_row';
import { PlaceHolder } from 'ts/components/wallet/placeholder';
import { StandardIconRow } from 'ts/components/wallet/standard_icon_row';
import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
-import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
+import { AllowanceStateToggle } from 'ts/containers/inputs/allowance_state_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import {
@@ -67,6 +68,7 @@ export interface WalletProps {
onRemoveToken: () => void;
refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
style: React.CSSProperties;
+ toggleTooltipDirection?: PointerDirection;
}
interface WalletState {
@@ -74,14 +76,14 @@ interface WalletState {
isHoveringSidebar: boolean;
}
-interface AllowanceToggleConfig {
+interface AllowanceStateToggleConfig {
token: Token;
tokenState: TokenState;
}
interface AccessoryItemConfig {
wrappedEtherDirection?: Side;
- allowanceToggleConfig?: AllowanceToggleConfig;
+ allowanceStateToggleConfig?: AllowanceStateToggleConfig;
}
const ETHER_ICON_PATH = '/images/ether.png';
@@ -89,7 +91,8 @@ const ICON_DIMENSION = 28;
const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
const ETHER_ITEM_KEY = 'ETHER';
-const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
+const WRAP_ROW_ALLOWANCE_TOGGLE_WIDTH = 67;
+const ALLOWANCE_TOGGLE_WIDTH = 56;
const PLACEHOLDER_COLOR = colors.grey300;
const LOADING_ROWS_COUNT = 6;
@@ -338,7 +341,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
const accessoryItemConfig: AccessoryItemConfig = {
wrappedEtherDirection,
- allowanceToggleConfig: {
+ allowanceStateToggleConfig: {
token,
tokenState,
},
@@ -393,13 +396,15 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
private _renderAccessoryItems(config: AccessoryItemConfig): React.ReactElement<{}> {
const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherDirection);
- const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig);
+ const shouldShowToggle = !_.isUndefined(config.allowanceStateToggleConfig);
// if we don't have a toggle, we still want some space to the right of the "wrap" button so that it aligns with
// the "unwrap" button in the row below
- const toggle = shouldShowToggle ? (
- this._renderAllowanceToggle(config.allowanceToggleConfig)
- ) : (
- <div style={{ width: NO_ALLOWANCE_TOGGLE_SPACE_WIDTH }} />
+ const isWrapEtherRow = shouldShowWrappedEtherAction && config.wrappedEtherDirection === Side.Deposit;
+ const width = isWrapEtherRow ? WRAP_ROW_ALLOWANCE_TOGGLE_WIDTH : ALLOWANCE_TOGGLE_WIDTH;
+ const toggle = (
+ <Container className="flex justify-center" width={width}>
+ {shouldShowToggle && this._renderAllowanceToggle(config.allowanceStateToggleConfig)}
+ </Container>
);
return (
<div className="flex items-center">
@@ -410,14 +415,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</div>
);
}
- private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode {
+ private _renderAllowanceToggle(config: AllowanceStateToggleConfig): React.ReactNode {
// TODO: Error handling
return (
- <AllowanceToggle
+ <AllowanceStateToggle
blockchain={this.props.blockchain}
token={config.token}
tokenState={config.tokenState}
- isDisabled={!config.tokenState.isLoaded}
+ tooltipDirection={this.props.toggleTooltipDirection}
refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)}
/>
);
diff --git a/packages/website/ts/containers/inputs/allowance_toggle.ts b/packages/website/ts/containers/inputs/allowance_state_toggle.ts
index 545708f92..70712685e 100644
--- a/packages/website/ts/containers/inputs/allowance_toggle.ts
+++ b/packages/website/ts/containers/inputs/allowance_state_toggle.ts
@@ -2,19 +2,20 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { Blockchain } from 'ts/blockchain';
+import { PointerDirection } from 'ts/components/ui/pointer';
import { State } from 'ts/redux/reducer';
import { BalanceErrs, Token, TokenState } from 'ts/types';
-import { AllowanceToggle as AllowanceToggleComponent } from 'ts/components/inputs/allowance_toggle';
+import { AllowanceStateToggle as AllowanceStateToggleComponent } from 'ts/components/inputs/allowance_state_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
-interface AllowanceToggleProps {
+interface AllowanceStateToggleProps {
blockchain: Blockchain;
onErrorOccurred?: (errType: BalanceErrs) => void;
token: Token;
tokenState: TokenState;
- isDisabled?: boolean;
refetchTokenStateAsync: () => Promise<void>;
+ tooltipDirection?: PointerDirection;
}
interface ConnectedState {
@@ -26,7 +27,7 @@ interface ConnectedDispatch {
dispatcher: Dispatcher;
}
-const mapStateToProps = (state: State, _ownProps: AllowanceToggleProps): ConnectedState => ({
+const mapStateToProps = (state: State, _ownProps: AllowanceStateToggleProps): ConnectedState => ({
networkId: state.networkId,
userAddress: state.userAddress,
});
@@ -35,7 +36,7 @@ const mapDispatchTopProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
dispatcher: new Dispatcher(dispatch),
});
-export const AllowanceToggle: React.ComponentClass<AllowanceToggleProps> = connect(
+export const AllowanceStateToggle: React.ComponentClass<AllowanceStateToggleProps> = connect(
mapStateToProps,
mapDispatchTopProps,
-)(AllowanceToggleComponent);
+)(AllowanceStateToggleComponent);
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index 2a5c5e4f1..c6d10452f 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -4,7 +4,7 @@ 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 { Redirector } from 'ts/components/redirector';
+import { MetaTags } from 'ts/components/meta_tags';
import { About } from 'ts/containers/about';
import { FAQ } from 'ts/containers/faq';
import { Jobs } from 'ts/containers/jobs';
@@ -17,7 +17,6 @@ import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage';
import { store } from 'ts/redux/store';
import { WebsiteLegacyPaths, WebsitePaths } from 'ts/types';
import { muiTheme } from 'ts/utils/mui_theme';
-import { utils } from 'ts/utils/utils';
// Polyfills
injectTapEventPlugin();
@@ -67,78 +66,85 @@ const LazyEthereumTypesDocumentation = createLazyComponent('Documentation', asyn
System.import<any>(/* webpackChunkName: "ethereumTypesDocs" */ 'ts/containers/ethereum_types_documentation'),
);
-render(
- <Router>
- <div>
- <MuiThemeProvider muiTheme={muiTheme}>
- <Provider store={store}>
- <div>
- <Switch>
- <Route exact={true} path="/" component={Landing as any} />
- <Redirect from="/otc" to={`${WebsitePaths.Portal}`} />
- {/* 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} />
- <Route path={WebsitePaths.Wiki} component={Wiki as any} />
- <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} />
- <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} />
- <Route
- path={`${WebsitePaths.SolCompiler}/:version?`}
- component={LazySolCompilerDocumentation}
- />
- <Route path={`${WebsitePaths.SolCov}/:version?`} component={LazySolCovDocumentation} />
- <Route
- path={`${WebsitePaths.JSONSchemas}/:version?`}
- component={LazyJSONSchemasDocumentation}
- />
- <Route
- path={`${WebsitePaths.Subproviders}/:version?`}
- component={LazySubprovidersDocumentation}
- />
- <Route
- path={`${WebsitePaths.OrderUtils}/:version?`}
- component={LazyOrderUtilsDocumentation}
- />
- <Route
- path={`${WebsitePaths.Web3Wrapper}/:version?`}
- component={LazyWeb3WrapperDocumentation}
- />
- <Route
- path={`${WebsitePaths.SmartContracts}/:version?`}
- component={LazySmartContractsDocumentation}
- />
- <Route
- path={`${WebsitePaths.EthereumTypes}/:version?`}
- component={LazyEthereumTypesDocumentation}
- />
+const DOCUMENT_TITLE = '0x: The Protocol for Trading Tokens';
+const DOCUMENT_DESCRIPTION = 'An Open Protocol For Decentralized Exchange On The Ethereum Blockchain';
- {/* Legacy endpoints */}
- <Route
- path={`${WebsiteLegacyPaths.ZeroExJs}/:version?`}
- component={LazyZeroExJSDocumentation}
- />
- <Route
- path={`${WebsiteLegacyPaths.Web3Wrapper}/:version?`}
- component={LazyWeb3WrapperDocumentation}
- />
- <Route
- path={`${WebsiteLegacyPaths.Deployer}/:version?`}
- component={LazySolCompilerDocumentation}
- />
+render(
+ <div>
+ <MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
+ <Router>
+ <div>
+ <MuiThemeProvider muiTheme={muiTheme}>
+ <Provider store={store}>
+ <div>
+ <Switch>
+ <Route exact={true} path="/" component={Landing as any} />
+ <Redirect from="/otc" to={`${WebsitePaths.Portal}`} />
+ <Route path={WebsitePaths.Careers} component={Jobs as any} />
+ <Route path={WebsitePaths.Portal} component={LazyPortal} />
+ <Route path={WebsitePaths.FAQ} component={FAQ as any} />
+ <Route path={WebsitePaths.About} component={About as any} />
+ <Route path={WebsitePaths.Wiki} component={Wiki as any} />
+ <Route
+ path={`${WebsitePaths.ZeroExJs}/:version?`}
+ component={LazyZeroExJSDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.Connect}/:version?`}
+ component={LazyConnectDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.SolCompiler}/:version?`}
+ component={LazySolCompilerDocumentation}
+ />
+ <Route path={`${WebsitePaths.SolCov}/:version?`} component={LazySolCovDocumentation} />
+ <Route
+ path={`${WebsitePaths.JSONSchemas}/:version?`}
+ component={LazyJSONSchemasDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.Subproviders}/:version?`}
+ component={LazySubprovidersDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.OrderUtils}/:version?`}
+ component={LazyOrderUtilsDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.Web3Wrapper}/:version?`}
+ component={LazyWeb3WrapperDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.SmartContracts}/:version?`}
+ component={LazySmartContractsDocumentation}
+ />
+ <Route
+ path={`${WebsitePaths.EthereumTypes}/:version?`}
+ component={LazyEthereumTypesDocumentation}
+ />
- <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
- <Route component={NotFound as any} />
- </Switch>
- </div>
- </Provider>
- </MuiThemeProvider>
- </div>
- </Router>,
+ {/* Legacy endpoints */}
+ <Route
+ path={`${WebsiteLegacyPaths.ZeroExJs}/:version?`}
+ component={LazyZeroExJSDocumentation}
+ />
+ <Route
+ path={`${WebsiteLegacyPaths.Web3Wrapper}/:version?`}
+ component={LazyWeb3WrapperDocumentation}
+ />
+ <Route
+ path={`${WebsiteLegacyPaths.Deployer}/:version?`}
+ component={LazySolCompilerDocumentation}
+ />
+ <Route path={WebsiteLegacyPaths.Jobs} component={Jobs as any} />
+ <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
+ <Route component={NotFound as any} />
+ </Switch>
+ </div>
+ </Provider>
+ </MuiThemeProvider>
+ </div>
+ </Router>
+ </div>,
document.getElementById('app'),
);
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index 33581c938..b9bc906bd 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -314,7 +314,7 @@ export class About extends React.Component<AboutProps, AboutState> {
}}
>
We are seeking outstanding candidates to{' '}
- <Link to={WebsitePaths.Jobs} style={{ color: 'black' }}>
+ <Link to={WebsitePaths.Careers} style={{ color: 'black' }}>
join our team
</Link>
. We value passion, diversity and unique perspectives.
diff --git a/packages/website/ts/pages/jobs/benefits.tsx b/packages/website/ts/pages/jobs/benefits.tsx
index 006facc83..563b72e63 100644
--- a/packages/website/ts/pages/jobs/benefits.tsx
+++ b/packages/website/ts/pages/jobs/benefits.tsx
@@ -1,109 +1,158 @@
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 { Circle } from 'ts/components/ui/circle';
+import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
+import { Text } from 'ts/components/ui/text';
import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
import { ScreenWidths } from 'ts/types';
+import { constants } from 'ts/utils/constants';
-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',
- },
+const BENEFITS = [
+ 'Comprehensive insurance (medical, dental, and vision)',
+ 'Unlimited vacation (three weeks per year minimum)',
+ 'Meals and snacks provided in-office daily',
+ 'Flexible hours and liberal work-from-home-policy',
+ 'Supportive remote working environment',
+ 'Transportation, phone, and wellness expense',
+ 'Relocation assistance',
+ 'Optional team excursions (fully paid, often international)',
+ 'Competitive salary and cryptocurrency-based compensation',
+];
+
+interface Value {
+ iconSrc: string;
+ text: string;
+}
+const VALUES: Value[] = [
{
- 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',
+ iconSrc: 'images/jobs/heart-icon.svg',
+ text: 'Do the right thing',
},
{
- 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',
+ iconSrc: 'images/jobs/ship-icon.svg',
+ text: 'Consistently ship',
},
{
- 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',
+ iconSrc: 'images/jobs/calendar-icon.svg',
+ text: 'Focus on long term impact',
},
];
-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>
-);
+export const Benefits = (props: BenefitsProps) => {
+ const isSmallScreen = props.screenWidth === ScreenWidths.Sm;
+ return (
+ <Container className="flex flex-column items-center py4 px3" backgroundColor={colors.white}>
+ {!isSmallScreen ? (
+ <Container className="flex" maxWidth="1200px">
+ <BenefitsList />
+ <Container marginLeft="120px">
+ <ValuesList />
+ </Container>
+ </Container>
+ ) : (
+ <Container className="flex-column">
+ <BenefitsList />
+ <Container marginTop="50px">
+ <ValuesList />
+ </Container>
+ </Container>
+ )}
+ </Container>
+ );
+};
-const SmallLayout = () => (
- <div>
- <FilledImage src={_.head(IMAGE_PATHS)} />
- <BenefitsList />
- </div>
+const Header: React.StatelessComponent = ({ children }) => (
+ <Container marginBottom="30px">
+ <Text fontFamily="Roboto Mono" fontSize="24px" fontColor={colors.black}>
+ {children}
+ </Text>
+ </Container>
);
-export const BenefitsList = () => {
+interface BenefitsListProps {
+ className?: string;
+}
+const PlainBenefitsList: React.StatelessComponent<BenefitsListProps> = ({ className }) => {
return (
- <div>
- <HeaderItem headerText={HEADER_TEXT} />
- {_.map(BENEFIT_ITEM_PROPS_LIST, valueItemProps => <BenefitItem {...valueItemProps} />)}
- </div>
+ <Container className={className}>
+ <Header>Benefits</Header>
+ {_.map(BENEFITS, benefit => <BenefitItem key={benefit} description={benefit} />)}
+ </Container>
);
};
+const BenefitsList = styled(PlainBenefitsList)`
+ transform: translateY(30px);
+`;
+
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]} />
+const BenefitItem: React.StatelessComponent<BenefitItemProps> = ({ description }) => (
+ <Container marginBottom="15px">
+ <div className="flex">
+ <Circle className="flex-none pr2 pt1" diameter={8} fillColor={colors.black} />
+ <div className="flex-auto">
+ <Text fontSize="14px" lineHeight="24px">
+ {description}
+ </Text>
</div>
</div>
- </div>
+ </Container>
);
+
+interface ValuesListProps {
+ className?: string;
+}
+const PlainValuesList: React.StatelessComponent<ValuesListProps> = ({ className }) => {
+ return (
+ <Container className={className} maxWidth="270px">
+ <Header>Our Values</Header>
+ {_.map(VALUES, value => <ValueItem key={value.text} {...value} />)}
+ <Text fontSize="14px" lineHeight="26px">
+ We care deeply about our culture and values, and encourage you to{' '}
+ <a
+ style={{ color: colors.mediumBlue, textDecoration: 'none' }}
+ target="_blank"
+ href={constants.URL_MISSION_AND_VALUES_BLOG_POST}
+ >
+ read more on our blog
+ </a>.
+ </Text>
+ </Container>
+ );
+};
+
+const ValuesList = styled(PlainValuesList)`
+ border-color: ${colors.beigeWhite};
+ border-radius: 7px;
+ border-width: 1px;
+ border-style: solid;
+ padding-left: 38px;
+ padding-right: 38px;
+ padding-top: 28px;
+ padding-bottom: 28px;
+`;
+
+type ValueItemProps = Value;
+const ValueItem: React.StatelessComponent<ValueItemProps> = ({ iconSrc, text }) => {
+ return (
+ <Container marginBottom="25px">
+ <div className="flex items-center">
+ <Image className="flex-none pr2" width="20px" src={iconSrc} />
+ <div className="flex-auto">
+ <Text fontSize="14px" lineHeight="24px" fontWeight="bold">
+ {text}
+ </Text>
+ </div>
+ </div>
+ </Container>
+ );
+};
diff --git a/packages/website/ts/pages/jobs/jobs.tsx b/packages/website/ts/pages/jobs/jobs.tsx
index 38cefa832..cc4b1f04b 100644
--- a/packages/website/ts/pages/jobs/jobs.tsx
+++ b/packages/website/ts/pages/jobs/jobs.tsx
@@ -4,15 +4,13 @@ import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Footer } from 'ts/components/footer';
+import { MetaTags } from 'ts/components/meta_tags';
import { TopBar } from 'ts/components/top_bar/top_bar';
-import { FilledImage } from 'ts/components/ui/filled_image';
+import { Container } from 'ts/components/ui/container';
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';
@@ -20,7 +18,8 @@ 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'];
+const DOCUMENT_TITLE = 'Careers at 0x';
+const DOCUMENT_DESCRIPTION = 'Join 0x in creating a tokenized world where all value can flow freely';
export interface JobsProps {
location: Location;
@@ -44,8 +43,9 @@ export class Jobs extends React.Component<JobsProps, JobsState> {
}
public render(): React.ReactNode {
return (
- <div>
- <DocumentTitle title="Jobs" />
+ <Container overflowX="hidden">
+ <MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
+ <DocumentTitle title={DOCUMENT_TITLE} />
<TopBar
blockchainIsLoaded={false}
location={this.props.location}
@@ -54,28 +54,18 @@ export class Jobs extends React.Component<JobsProps, JobsState> {
/>
<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>
+ </Container>
);
}
private _onJoin0xCallToActionClick(): void {
sharedUtils.setUrlHash(OPEN_POSITIONS_HASH);
+ sharedUtils.scrollToHash(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
index 72cce3b89..ec8afbd93 100644
--- a/packages/website/ts/pages/jobs/join_0x.tsx
+++ b/packages/website/ts/pages/jobs/join_0x.tsx
@@ -3,8 +3,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 { Image } from 'ts/components/ui/image';
+import { Text } from 'ts/components/ui/text';
+import { constants } from 'ts/utils/constants';
-const BUTTON_TEXT = 'view open positions';
+const BUTTON_TEXT = 'View open positions';
export interface Join0xProps {
onCallToActionClick: () => void;
@@ -12,17 +16,36 @@ export interface Join0xProps {
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="mx-auto inline-block align-middle py4"
+ style={{ lineHeight: '44px', textAlign: 'center', position: 'relative' }}
+ >
+ <Container className="sm-hide xs-hide" position="absolute" left="100%" marginLeft="80px">
+ <Image src="images/jobs/hero-dots-right.svg" width="400px" />
+ </Container>
+ <Container className="sm-hide xs-hide" position="absolute" right="100%" marginRight="80px">
+ <Image src="images/jobs/hero-dots-left.svg" width="400px" />
+ </Container>
<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.
+ Join Us in Our Mission
</div>
+ <Container className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 sm-center" maxWidth="537px">
+ <Text fontSize="14px" lineHeight="30px">
+ At 0x, our mission is to create a tokenized world where all value can flow freely.
+ <br />
+ <br />We are powering a growing ecosystem of decentralized applications and solving novel challenges
+ to make our technology intuitive, flexible, and accessible to all.{' '}
+ <a
+ style={{ color: colors.mediumBlue, textDecoration: 'none' }}
+ target="_blank"
+ href={constants.URL_MISSION_AND_VALUES_BLOG_POST}
+ >
+ Read more
+ </a>{' '}
+ about our mission, and join us in building financial infrastructure upon which the exchange of
+ anything of value will take place.
+ </Text>
+ </Container>
<div className="py3">
<Button
type="button"
diff --git a/packages/website/ts/pages/jobs/list/header_item.tsx b/packages/website/ts/pages/jobs/list/header_item.tsx
deleted file mode 100644
index ac214904c..000000000
--- a/packages/website/ts/pages/jobs/list/header_item.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-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
deleted file mode 100644
index 192433d39..000000000
--- a/packages/website/ts/pages/jobs/list/list_item.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as React from 'react';
-
-import { Circle } from 'ts/components/ui/circle';
-
-export interface ListItemProps {
- bulletColor?: string;
-}
-export const ListItem: React.StatelessComponent<ListItemProps> = ({ bulletColor, children }) => {
- return (
- <div className="flex items-center">
- <Circle className="flex-none lg-px2 md-px2 sm-pl2" diameter={26} fillColor={bulletColor || 'transparent'} />
- <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
index f7f874e04..28546f985 100644
--- a/packages/website/ts/pages/jobs/mission.tsx
+++ b/packages/website/ts/pages/jobs/mission.tsx
@@ -1,5 +1,8 @@
import * as React from 'react';
+import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
+import { Text } from 'ts/components/ui/text';
import { colors } from 'ts/style/colors';
import { ScreenWidths } from 'ts/types';
@@ -7,50 +10,38 @@ 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 shouldShowImage = props.screenWidth === ScreenWidths.Lg;
+ const image = <Image src="/images/jobs/world-map.svg" maxWidth="500px" maxHeight="280px" />;
+ const missionStatementClassName = !shouldShowImage ? 'center' : 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>
+ <Container className={missionStatementClassName} maxWidth="388px">
+ <Text fontFamily="Roboto Mono" fontSize="22px" lineHeight="31px">
+ Powered by a Diverse<br />Worldwide Community
+ </Text>
+ <Container marginTop="32px">
+ <Text fontSize="14px" lineHeight="2em">
+ We're a highly technical team with varied backgrounds in engineering, science, business, finance,
+ and research. While the core team is headquartered in San Francisco, there are 30+ teams building on
+ 0x and hundreds of thousands of participants behind our efforts globally. We're passionate about
+ open-source software and decentralized technology's potential to act as an equalizing force in the
+ world.
+ </Text>
+ </Container>
+ </Container>
);
return (
<div
- className="container lg-py4 md-py4"
+ className="flex flex-column items-center py4 px3"
style={{ backgroundColor: colors.jobsPageBackground, color: colors.black }}
>
- <div className="mx-auto clearfix sm-py4">
- {isSmallScreen ? (
- <div>
- {missionStatement}
- {image}
- </div>
- ) : (
- <div>
- {image}
- {missionStatement}
- </div>
- )}
- </div>
+ {shouldShowImage ? (
+ <Container className="flex items-center" maxWidth="1200px">
+ {image}
+ <Container marginLeft="115px">{missionStatement}</Container>
+ </Container>
+ ) : (
+ <Container className="flex flex-column items-center">{missionStatement}</Container>
+ )}
</div>
);
};
diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx
index e789795c1..b8442a9c4 100644
--- a/packages/website/ts/pages/jobs/open_positions.tsx
+++ b/packages/website/ts/pages/jobs/open_positions.tsx
@@ -1,20 +1,17 @@
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 { Container } from 'ts/components/ui/container';
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';
+import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
-const labelStyle = { fontFamily: 'Roboto Mono', fontSize: 18 };
-const HEADER_TEXT = 'Open Positions';
const TABLE_ROW_MIN_HEIGHT = 100;
export interface OpenPositionsProps {
@@ -45,16 +42,21 @@ export class OpenPositions extends React.Component<OpenPositionsProps, OpenPosit
}
public render(): React.ReactNode {
const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.jobInfos);
+ const isSmallScreen = utils.isMobileWidth(this.props.screenWidth);
return (
- <div id={this.props.hash} className="mx-auto max-width-4">
- {isReadyToRender ? this._renderBody() : this._renderLoading()}
- </div>
+ <Container id={this.props.hash} className="mx-auto pb4 px3 max-width-4">
+ {!isSmallScreen && (
+ <hr style={{ border: 0, borderTop: 1, borderStyle: 'solid', borderColor: colors.beigeWhite }} />
+ )}
+ <Container marginTop="64px" marginBottom="50px">
+ <Text fontFamily="Roboto Mono" fontSize="24px" fontColor={colors.black}>
+ Open Positions
+ </Text>
+ </Container>
+ {isReadyToRender ? this._renderTable() : this._renderLoading()}
+ </Container>
);
}
- 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
@@ -68,66 +70,34 @@ export class OpenPositions extends React.Component<OpenPositionsProps, OpenPosit
</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>
+ <Container width="100%">
+ <div>
+ {_.map(this.state.jobInfos, jobInfo => {
+ return (
+ <JobInfoTableRow
+ key={jobInfo.id}
+ screenWidth={this.props.screenWidth}
+ jobInfo={jobInfo}
+ onClick={this._openJobInfoUrl.bind(this, jobInfo)}
+ />
+ );
+ })}
+ </div>
+ <Container className="center" marginTop="70px">
+ <Text fontStyle="italic" fontSize="14px">
+ Interested in telling us why you'd be a valuable addition to the team outside of the positions
+ listed above?{' '}
+ <a
+ style={{ color: colors.mediumBlue, textDecoration: 'none' }}
+ href={`mailto:${constants.EMAIL_JOBS}`}
+ >
+ Email us!
+ </a>
+ </Text>
+ </Container>
+ </Container>
);
}
private async _fetchJobInfosAsync(): Promise<void> {
@@ -152,41 +122,57 @@ export class OpenPositions extends React.Component<OpenPositionsProps, OpenPosit
}
}
}
- 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;
utils.openUrl(url);
}
}
-export interface JobInfoListItemProps {
- title?: string;
- description?: string;
+export interface JobInfoTableRowProps {
+ className?: string;
+ screenWidth: ScreenWidths;
+ jobInfo: WebsiteBackendJobInfo;
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>
-);
+const PlainJobInfoTableRow: React.StatelessComponent<JobInfoTableRowProps> = ({
+ className,
+ screenWidth,
+ jobInfo,
+ onClick,
+}) => {
+ const isSmallScreen = screenWidth === ScreenWidths.Sm;
+ const titleClassName = isSmallScreen ? 'col col-12 center' : 'col col-5';
+ const paddingLeft = isSmallScreen ? undefined : '30px';
+ return (
+ <Container className={className} onClick={onClick} marginBottom="30px" paddingLeft={paddingLeft}>
+ <Container className="flex items-center" minHeight={TABLE_ROW_MIN_HEIGHT} width="100%">
+ <Container className="clearfix container" width="100%">
+ <Container className={titleClassName}>
+ <Text fontSize="16px" fontWeight="bold" fontColor={colors.mediumBlue}>
+ {jobInfo.title}
+ </Text>
+ </Container>
+ {!isSmallScreen && (
+ <Container className="col col-3">
+ <Text fontSize="16px">{jobInfo.department}</Text>
+ </Container>
+ )}
+ {!isSmallScreen && (
+ <Container className="col col-4 center">
+ <Text fontSize="16px">{jobInfo.office}</Text>
+ </Container>
+ )}
+ </Container>
+ </Container>
+ </Container>
+ );
+};
-export const JobInfoListItem = styled(PlainJobInfoListItem)`
+export const JobInfoTableRow = styled(PlainJobInfoTableRow)`
cursor: pointer;
+ background-color: ${colors.grey100};
+ border-radius: 7px;
&:hover {
opacity: 0.5;
}
diff --git a/packages/website/ts/pages/jobs/teams.tsx b/packages/website/ts/pages/jobs/teams.tsx
deleted file mode 100644
index 80b036396..000000000
--- a/packages/website/ts/pages/jobs/teams.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-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
deleted file mode 100644
index c7c4d5726..000000000
--- a/packages/website/ts/pages/jobs/values.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-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/style/keyframes.ts b/packages/website/ts/style/keyframes.ts
new file mode 100644
index 000000000..28ea50247
--- /dev/null
+++ b/packages/website/ts/style/keyframes.ts
@@ -0,0 +1,22 @@
+import { keyframes } from 'ts/style/theme';
+
+export const rotate = keyframes`
+ 100% {
+ transform: rotate(360deg);
+ }
+`;
+
+export const dash = keyframes`
+ 0% {
+ stroke-dasharray: 1, 150;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -35;
+ }
+ 100% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -124;
+ }
+`;
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 4d0496f6c..e1b5be39a 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -356,6 +356,7 @@ export enum WebsiteLegacyPaths {
ZeroExJs = '/docs/0xjs',
Web3Wrapper = '/docs/web3_wrapper',
Deployer = '/docs/deployer',
+ Jobs = '/jobs',
}
export enum WebsitePaths {
@@ -376,7 +377,7 @@ export enum WebsitePaths {
Subproviders = '/docs/subproviders',
OrderUtils = '/docs/order-utils',
EthereumTypes = '/docs/ethereum-types',
- Jobs = '/jobs',
+ Careers = '/careers',
}
export enum DocPackages {
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index 20441cd75..005d17823 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -41,6 +41,7 @@ export const constants = {
TAKER_FEE: new BigNumber(0),
TESTNET_NAME: 'Kovan',
NUMERAL_USD_FORMAT: '$0,0.00',
+ EMAIL_JOBS: 'jobs@0xproject.com',
PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/',
PROJECT_URL_AMADEUS: 'http://amadeusrelay.org',
PROJECT_URL_DDEX: 'https://ddex.io',
@@ -92,4 +93,5 @@ export const constants = {
URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127',
URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150',
URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js',
+ URL_MISSION_AND_VALUES_BLOG_POST: 'https://blog.0xproject.com/the-0x-mission-and-values-181a58706f9f',
};
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 439af5e4b..39bbd404c 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -363,9 +363,6 @@ export const utils = {
}
return Environments.UNKNOWN;
},
- shouldShowJobsPage(): boolean {
- return utils.isDevelopment() || utils.isStaging() || utils.isDogfood();
- },
getEthToken(tokenByAddress: TokenByAddress): Token {
return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress);
},