diff options
Diffstat (limited to 'packages/website/ts')
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); }, |