diff options
author | fragosti <francesco.agosti93@gmail.com> | 2018-06-15 06:50:46 +0800 |
---|---|---|
committer | fragosti <francesco.agosti93@gmail.com> | 2018-06-15 06:50:46 +0800 |
commit | 2cc7289b7b3c3177230957ff2384c58bed4266f3 (patch) | |
tree | 21acfb995ece8753c25cb9e2cf0f05de86a1b16c /packages/website/ts | |
parent | 81d6df925e430f2199ddef44b3ccd353e6c3c19b (diff) | |
parent | e7eb220c503118631a6b23071c71b4b55df5b5cf (diff) | |
download | dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar.gz dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar.bz2 dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar.lz dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar.xz dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.tar.zst dexon-sol-tools-2cc7289b7b3c3177230957ff2384c58bed4266f3.zip |
Merge branch 'v2-prototype' of https://github.com/0xProject/0x-monorepo into feature/website/portal-v2-analytics
Diffstat (limited to 'packages/website/ts')
29 files changed, 946 insertions, 61 deletions
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 09791f125..0dd2a5aa5 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -1,5 +1,6 @@ import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { BigNumber, logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; import Toggle from 'material-ui/Toggle'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; @@ -16,11 +17,11 @@ interface AllowanceToggleProps { networkId: number; blockchain: Blockchain; dispatcher: Dispatcher; - onErrorOccurred: (errType: BalanceErrs) => void; token: Token; tokenState: TokenState; userAddress: string; - isDisabled: boolean; + isDisabled?: boolean; + onErrorOccurred?: (errType: BalanceErrs) => void; refetchTokenStateAsync: () => Promise<void>; } @@ -55,6 +56,10 @@ const styles: Styles = { }; export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { + public static defaultProps = { + onErrorOccurred: _.noop, + isDisabled: false, + }; constructor(props: AllowanceToggleProps) { super(props); this.state = { diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index e4df1ec36..ec2365c11 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -3,13 +3,16 @@ import * as _ from 'lodash'; import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; +import { Blockchain } from 'ts/blockchain'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; -import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; +import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { utils } from 'ts/utils/utils'; export interface PortalOnboardingFlowProps { networkId: number; + blockchain: Blockchain; stepIndex: number; isRunning: boolean; userAddress: string; @@ -22,6 +25,7 @@ export interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { @@ -42,7 +46,6 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr /> ); } - private _getSteps(): Step[] { const steps: Step[] = [ { @@ -80,18 +83,33 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr placement: 'right', continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', }, + { + target: '.weth-row', + content: ( + <div> + Unlock your tokens for trading. You only need to do this once for each token. + <div> ETH: {this._renderEthAllowanceToggle()}</div> + <div> ZRX: {this._renderZrxAllowanceToggle()}</div> + </div> + ), + placement: 'right', + continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled', + }, + { + target: '.wallet', + content: 'Congrats! Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem.', + placement: 'right', + continueButtonDisplay: 'enabled', + }, ]; return steps; } - private _isAddressAvailable(): boolean { return !_.isEmpty(this.props.userAddress); } - private _userHasVisibleEth(): boolean { return this.props.userEtherBalanceInWei > new BigNumber(0); } - private _userHasVisibleWeth(): boolean { const ethToken = utils.getEthToken(this.props.tokenByAddress); if (!ethToken) { @@ -100,15 +118,25 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; return wethTokenState.balance > new BigNumber(0); } - + private _userHasAllowancesForWethAndZrx(): boolean { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + if (ethToken && zrxToken) { + const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance; + const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance; + return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0); + } + return false; + } private _overrideOnboardingStateIfShould(): void { this._autoStartOnboardingIfShould(); this._adjustStepIfShould(); } private _adjustStepIfShould(): void { + const stepIndex = this.props.stepIndex; if (this._isAddressAvailable()) { - if (this.props.stepIndex < 2) { + if (stepIndex < 2) { this.props.updateOnboardingStep(2); } return; @@ -118,10 +146,14 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr this.props.injectedProviderName, ); if (isExternallyInjected) { - this.props.updateOnboardingStep(1); + if (stepIndex !== 1) { + this.props.updateOnboardingStep(1); + } return; } - this.props.updateOnboardingStep(0); + if (stepIndex !== 0) { + this.props.updateOnboardingStep(0); + } } private _autoStartOnboardingIfShould(): void { if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) { @@ -140,4 +172,28 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr this.props.updateIsRunning(false); analytics.logEvent('Portal', 'Onboarding Closed', networkName, this.props.stepIndex); } + private _renderZrxAllowanceToggle(): React.ReactNode { + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(zrxToken); + } + private _renderEthAllowanceToggle(): React.ReactNode { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(ethToken); + } + private _renderAllowanceToggle(token: Token): React.ReactNode { + if (!token) { + return null; + } + const tokenState = this.props.trackedTokenStateByAddress[token.address]; + return ( + <AllowanceToggle + token={token} + tokenState={tokenState} + isDisabled={!tokenState.isLoaded} + blockchain={this.props.blockchain} + // tslint:disable-next-line:jsx-no-lambda + refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)} + /> + ); + } } diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9c89145ca..5170bf1e1 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -237,7 +237,11 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <div style={styles.root}> - <PortalOnboardingFlow trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} /> + <PortalOnboardingFlow + blockchain={this._blockchain} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} + /> <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} diff --git a/packages/website/ts/components/redirecter.tsx b/packages/website/ts/components/redirector.tsx index 477aecb77..a02693003 100644 --- a/packages/website/ts/components/redirecter.tsx +++ b/packages/website/ts/components/redirector.tsx @@ -1,9 +1,9 @@ import { constants } from 'ts/utils/constants'; -interface RedirecterProps { +interface RedirectorProps { location: string; } -export function Redirecter(_props: RedirecterProps): void { +export function Redirector(_props: RedirectorProps): void { window.location.href = constants.URL_ANGELLIST; } diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index 683f7084b..3c5761bcd 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -1,11 +1,11 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; -import FlatButton from 'material-ui/FlatButton'; import { GridList } from 'material-ui/GridList'; import * as React from 'react'; import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile'; +import { Retry } from 'ts/components/ui/retry'; import { colors } from 'ts/style/colors'; import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; @@ -63,7 +63,8 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos); if (!isReadyToRender) { return ( - // TODO: consolidate this loading component with the one in portal + // TODO: consolidate this loading component with the one in portal and OpenPositions + // TODO: possibly refactor into a generic loading container with spinner and retry UI <div className="center"> {_.isUndefined(this.state.error) ? ( <CircularProgress size={40} thickness={5} /> @@ -124,31 +125,3 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde } } } - -interface RetryProps { - onRetry: () => void; -} -const Retry = (props: RetryProps) => ( - <div className="clearfix center" style={{ color: colors.black }}> - <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}> - <div className="h2" style={{ fontFamily: 'Roboto Mono' }}> - Something went wrong. - </div> - <div className="py3"> - <FlatButton - label={'reload'} - backgroundColor={colors.black} - labelStyle={{ - fontSize: 18, - fontFamily: 'Roboto Mono', - fontWeight: 'lighter', - color: colors.white, - textTransform: 'lowercase', - }} - style={{ width: 280, height: 62, borderRadius: 5 }} - onClick={props.onRetry} - /> - </div> - </div> - </div> -); diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 2a051651d..7af80745c 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -20,11 +20,11 @@ import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; import { AssetPicker } from 'ts/components/generate_order/asset_picker'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { SendButton } from 'ts/components/send_button'; import { HelpTooltip } from 'ts/components/ui/help_tooltip'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { TokenIcon } from 'ts/components/ui/token_icon'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { @@ -362,13 +362,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </TableRowColumn> <TableRowColumn> <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={token} tokenState={tokenState} onErrorOccurred={this._onErrorOccurred.bind(this)} - userAddress={this.props.userAddress} isDisabled={!tokenState.isLoaded} refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)} /> diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx index 4c7d59839..1f88297de 100644 --- a/packages/website/ts/components/ui/button.tsx +++ b/packages/website/ts/components/ui/button.tsx @@ -7,6 +7,7 @@ export interface ButtonProps { className?: string; fontSize?: string; fontColor?: string; + fontFamily?: string; backgroundColor?: string; borderColor?: string; width?: string; @@ -28,7 +29,7 @@ export const Button = styled(PlainButton)` border-radius: 6px; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; - font-family: 'Roboto'; + font-family: ${props => props.fontFamily}; width: ${props => props.width}; background-color: ${props => props.backgroundColor}; border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; @@ -44,6 +45,7 @@ Button.defaultProps = { fontSize: '12px', backgroundColor: colors.white, width: 'auto', + fontFamily: 'Roboto', }; Button.displayName = 'Button'; diff --git a/packages/website/ts/components/ui/filled_image.tsx b/packages/website/ts/components/ui/filled_image.tsx new file mode 100644 index 000000000..7f58ee5b9 --- /dev/null +++ b/packages/website/ts/components/ui/filled_image.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +export interface FilledImageProps { + src: string; +} +export const FilledImage = (props: FilledImageProps) => ( + <div + style={{ + width: '100%', + height: '100%', + objectFit: 'cover', + backgroundImage: `url(${props.src})`, + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + backgroundSize: 'cover', + }} + /> +); diff --git a/packages/website/ts/components/ui/retry.tsx b/packages/website/ts/components/ui/retry.tsx new file mode 100644 index 000000000..543b7df4b --- /dev/null +++ b/packages/website/ts/components/ui/retry.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { colors } from 'ts/style/colors'; + +const BUTTON_TEXT = 'reload'; + +export interface RetryProps { + onRetry: () => void; +} +export const Retry = (props: RetryProps) => ( + <div className="clearfix center" style={{ color: colors.black }}> + <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}> + <div className="h2" style={{ fontFamily: 'Roboto Mono' }}> + Something went wrong. + </div> + <div className="py3"> + <Button + type="button" + backgroundColor={colors.black} + width="290px" + fontColor={colors.white} + fontSize="18px" + fontFamily="Roboto Mono" + onClick={props.onRetry} + > + {BUTTON_TEXT} + </Button> + </div> + </div> + </div> +); diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index e90c1707d..7e47f1d09 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -11,8 +11,9 @@ export interface TextProps { fontFamily?: string; fontColor?: string; lineHeight?: string; + minHeight?: string; center?: boolean; - fontWeight?: number; + fontWeight?: number | string; } const PlainText: React.StatelessComponent<TextProps> = ({ children, className, Tag }) => ( @@ -26,6 +27,7 @@ export const Text = styled(PlainText)` ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')}; ${props => (props.center ? 'text-align: center' : '')}; color: ${props => props.fontColor}; + ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')}; `; Text.defaultProps = { diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 42aa5af5d..fc5e45248 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -19,7 +19,6 @@ import { Link } from 'react-router-dom'; import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { Container } from 'ts/components/ui/container'; import { IconButton } from 'ts/components/ui/icon_button'; import { Identicon } from 'ts/components/ui/identicon'; @@ -27,6 +26,7 @@ import { Island } from 'ts/components/ui/island'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item'; import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { zIndex } from 'ts/style/z_index'; @@ -421,15 +421,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode { + // TODO: Error handling return ( <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={config.token} tokenState={config.tokenState} - onErrorOccurred={_.noop} // TODO: Error handling - userAddress={this.props.userAddress} isDisabled={!config.tokenState.isLoaded} refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)} /> diff --git a/packages/website/ts/containers/inputs/allowance_toggle.ts b/packages/website/ts/containers/inputs/allowance_toggle.ts new file mode 100644 index 000000000..545708f92 --- /dev/null +++ b/packages/website/ts/containers/inputs/allowance_toggle.ts @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; +import { State } from 'ts/redux/reducer'; +import { BalanceErrs, Token, TokenState } from 'ts/types'; + +import { AllowanceToggle as AllowanceToggleComponent } from 'ts/components/inputs/allowance_toggle'; +import { Dispatcher } from 'ts/redux/dispatcher'; + +interface AllowanceToggleProps { + blockchain: Blockchain; + onErrorOccurred?: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + isDisabled?: boolean; + refetchTokenStateAsync: () => Promise<void>; +} + +interface ConnectedState { + networkId: number; + userAddress: string; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: AllowanceToggleProps): ConnectedState => ({ + networkId: state.networkId, + userAddress: state.userAddress, +}); + +const mapDispatchTopProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const AllowanceToggle: React.ComponentClass<AllowanceToggleProps> = connect( + mapStateToProps, + mapDispatchTopProps, +)(AllowanceToggleComponent); diff --git a/packages/website/ts/containers/jobs.ts b/packages/website/ts/containers/jobs.ts new file mode 100644 index 000000000..b18485882 --- /dev/null +++ b/packages/website/ts/containers/jobs.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Jobs as JobsComponent, JobsProps } from 'ts/pages/jobs/jobs'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; + screenWidth: ScreenWidths; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: JobsProps): ConnectedState => ({ + translate: state.translate, + screenWidth: state.screenWidth, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Jobs: React.ComponentClass<JobsProps> = connect(mapStateToProps, mapDispatchToProps)(JobsComponent); diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts index 67ee6bb28..ba2b8f512 100644 --- a/packages/website/ts/containers/portal_onboarding_flow.ts +++ b/packages/website/ts/containers/portal_onboarding_flow.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; import { ActionTypes, ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow'; @@ -9,6 +10,8 @@ import { State } from 'ts/redux/reducer'; interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; + blockchain: Blockchain; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } interface ConnectedState { diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index 719604c02..249b4fdc9 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -4,9 +4,10 @@ import { render } from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'; import * as injectTapEventPlugin from 'react-tap-event-plugin'; -import { Redirecter } from 'ts/components/redirecter'; +import { Redirector } from 'ts/components/redirector'; import { About } from 'ts/containers/about'; import { FAQ } from 'ts/containers/faq'; +import { Jobs } from 'ts/containers/jobs'; import { Landing } from 'ts/containers/landing'; import { NotFound } from 'ts/containers/not_found'; import { Wiki } from 'ts/containers/wiki'; @@ -86,8 +87,12 @@ render( <Switch> <Route exact={true} path="/" component={Landing as any} /> <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> - - <Route path={WebsitePaths.Jobs} component={Redirecter as any} /> + {/* TODO: Remove this once we ship the jobs page*/} + {utils.shouldShowJobsPage() ? ( + <Route path={WebsitePaths.Jobs} component={Jobs as any} /> + ) : ( + <Route path={WebsitePaths.Jobs} component={Redirector as any} /> + )} <Route path={WebsitePaths.Portal} component={LazyPortal} /> <Route path={WebsitePaths.FAQ} component={FAQ as any} /> <Route path={WebsitePaths.About} component={About as any} /> diff --git a/packages/website/ts/pages/jobs/benefits.tsx b/packages/website/ts/pages/jobs/benefits.tsx new file mode 100644 index 000000000..006facc83 --- /dev/null +++ b/packages/website/ts/pages/jobs/benefits.tsx @@ -0,0 +1,109 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { FilledImage } from 'ts/components/ui/filled_image'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +const IMAGE_PATHS = ['/images/jobs/location1.png', '/images/jobs/location2.png', '/images/jobs/location3.png']; +const BENEFIT_ITEM_PROPS_LIST: BenefitItemProps[] = [ + { + bulletColor: '#6FCF97', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#56CCF2', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#6FCF97', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#56CCF2', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const LARGE_LAYOUT_HEIGHT = 937; +const LARGE_LAYOUT_BENEFITS_LIST_PADDING_LEFT = 205; +const HEADER_TEXT = 'Benefits'; +const BENEFIT_ITEM_MIN_HEIGHT = 150; + +export interface BenefitsProps { + screenWidth: ScreenWidths; +} + +export const Benefits = (props: BenefitsProps) => ( + <div style={{ backgroundColor: colors.jobsPageBackground }}> + {props.screenWidth === ScreenWidths.Sm ? <SmallLayout /> : <LargeLayout />} + </div> +); + +const LargeLayout = () => ( + <div className="flex" style={{ height: LARGE_LAYOUT_HEIGHT }}> + <div style={{ width: '43%', height: '100%' }}> + <ImageGrid /> + </div> + <div + className="pr4" + style={{ paddingLeft: LARGE_LAYOUT_BENEFITS_LIST_PADDING_LEFT, width: '57%', height: '100%' }} + > + <BenefitsList /> + </div> + </div> +); + +const SmallLayout = () => ( + <div> + <FilledImage src={_.head(IMAGE_PATHS)} /> + <BenefitsList /> + </div> +); + +export const BenefitsList = () => { + return ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(BENEFIT_ITEM_PROPS_LIST, valueItemProps => <BenefitItem {...valueItemProps} />)} + </div> + ); +}; +interface BenefitItemProps { + bulletColor: string; + description: string; +} + +const BenefitItem: React.StatelessComponent<BenefitItemProps> = ({ bulletColor, description }) => ( + <div style={{ minHeight: BENEFIT_ITEM_MIN_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <div style={{ fontSize: 16, lineHeight: 1.5 }}>{description}</div> + </ListItem> + </div> +); + +const ImageGrid = () => ( + <div style={{ width: '100%', height: '100%' }}> + <div className="flex" style={{ height: '67%' }}> + <FilledImage src={IMAGE_PATHS[0]} /> + </div> + <div className="clearfix" style={{ height: '33%' }}> + <div className="col lg-col-6 md-col-6 col-12" style={{ height: '100%' }}> + <FilledImage src={IMAGE_PATHS[1]} /> + </div> + <div className="col lg-col-6 md-col-6 col-12" style={{ height: '100%' }}> + <FilledImage src={IMAGE_PATHS[2]} /> + </div> + </div> + </div> +); diff --git a/packages/website/ts/pages/jobs/jobs.tsx b/packages/website/ts/pages/jobs/jobs.tsx new file mode 100644 index 000000000..314669ee9 --- /dev/null +++ b/packages/website/ts/pages/jobs/jobs.tsx @@ -0,0 +1,81 @@ +import { colors, utils as sharedUtils } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; + +import { Footer } from 'ts/components/footer'; +import { TopBar } from 'ts/components/top_bar/top_bar'; +import { FilledImage } from 'ts/components/ui/filled_image'; +import { Benefits } from 'ts/pages/jobs/benefits'; +import { Join0x } from 'ts/pages/jobs/join_0x'; +import { Mission } from 'ts/pages/jobs/mission'; +import { OpenPositions } from 'ts/pages/jobs/open_positions'; +import { PhotoRail } from 'ts/pages/jobs/photo_rail'; +import { Teams } from 'ts/pages/jobs/teams'; +import { Values } from 'ts/pages/jobs/values'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; +import { utils } from 'ts/utils/utils'; + +const OPEN_POSITIONS_HASH = 'positions'; +const THROTTLE_TIMEOUT = 100; +const PHOTO_RAIL_IMAGES = ['/images/jobs/office1.png', '/images/jobs/office2.png', '/images/jobs/office3.png']; + +export interface JobsProps { + location: Location; + translate: Translate; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; +} + +export interface JobsState {} + +export class Jobs extends React.Component<JobsProps, JobsState> { + // TODO: consolidate this small screen scaffolding into one place (its being used in portal and docs as well) + private _throttledScreenWidthUpdate: () => void; + public constructor(props: JobsProps) { + super(props); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount(): void { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public render(): React.ReactNode { + return ( + <div> + <DocumentTitle title="Jobs" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + style={{ backgroundColor: colors.white, position: 'relative' }} + translate={this.props.translate} + /> + <Join0x onCallToActionClick={this._onJoin0xCallToActionClick.bind(this)} /> + <Mission screenWidth={this.props.screenWidth} /> + {this._isSmallScreen() ? ( + <FilledImage src={_.head(PHOTO_RAIL_IMAGES)} /> + ) : ( + <PhotoRail images={PHOTO_RAIL_IMAGES} /> + )} + <Values /> + <Benefits screenWidth={this.props.screenWidth} /> + <Teams screenWidth={this.props.screenWidth} /> + <OpenPositions hash={OPEN_POSITIONS_HASH} screenWidth={this.props.screenWidth} /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> + </div> + ); + } + private _onJoin0xCallToActionClick(): void { + sharedUtils.setUrlHash(OPEN_POSITIONS_HASH); + } + private _updateScreenWidth(): void { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private _isSmallScreen(): boolean { + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + return isSmallScreen; + } +} diff --git a/packages/website/ts/pages/jobs/join_0x.tsx b/packages/website/ts/pages/jobs/join_0x.tsx new file mode 100644 index 000000000..72cce3b89 --- /dev/null +++ b/packages/website/ts/pages/jobs/join_0x.tsx @@ -0,0 +1,41 @@ +import { colors } from '@0xproject/react-shared'; + +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; + +const BUTTON_TEXT = 'view open positions'; + +export interface Join0xProps { + onCallToActionClick: () => void; +} + +export const Join0x = (props: Join0xProps) => ( + <div className="clearfix center lg-py4 md-py4" style={{ backgroundColor: colors.white, color: colors.black }}> + <div className="mx-auto inline-block align-middle py4" style={{ lineHeight: '44px', textAlign: 'center' }}> + <div className="h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + Join 0x + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h4 sm-center" + style={{ fontFamily: 'Roboto', lineHeight: 2, maxWidth: 537 }} + > + 0x is transforming the way that value is exchanged on a global scale. Come join us in San Francisco or + work remotely anywhere in the world to help create the infrastructure of a new tokenized economy. + </div> + <div className="py3"> + <Button + type="button" + backgroundColor={colors.black} + width="290px" + fontColor={colors.white} + fontSize="18px" + fontFamily="Roboto Mono" + onClick={props.onCallToActionClick} + > + {BUTTON_TEXT} + </Button> + </div> + </div> + </div> +); diff --git a/packages/website/ts/pages/jobs/list/header_item.tsx b/packages/website/ts/pages/jobs/list/header_item.tsx new file mode 100644 index 000000000..ac214904c --- /dev/null +++ b/packages/website/ts/pages/jobs/list/header_item.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; + +export interface HeaderItemProps { + headerText?: string; +} +export const HeaderItem: React.StatelessComponent<HeaderItemProps> = ({ headerText }) => { + return ( + <div className="h2 lg-py4 md-py4 sm-py3"> + <ListItem> + <Text + fontFamily="Roboto Mono" + fontSize="24px" + lineHeight="1.25" + minHeight="1.25em" + fontColor={colors.black} + > + {headerText} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/list/list_item.tsx b/packages/website/ts/pages/jobs/list/list_item.tsx new file mode 100644 index 000000000..d7838bc01 --- /dev/null +++ b/packages/website/ts/pages/jobs/list/list_item.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface ListItemProps { + bulletColor?: string; +} +export const ListItem: React.StatelessComponent<ListItemProps> = ({ bulletColor, children }) => { + return ( + <div className="flex items-center"> + <svg className="flex-none lg-px2 md-px2 sm-pl2" height="26" width="26"> + <circle cx="13" cy="13" r="13" fill={bulletColor || 'transparent'} /> + </svg> + <div className="flex-auto px2">{children}</div> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/mission.tsx b/packages/website/ts/pages/jobs/mission.tsx new file mode 100644 index 000000000..f7f874e04 --- /dev/null +++ b/packages/website/ts/pages/jobs/mission.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; + +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +export interface MissionProps { + screenWidth: ScreenWidths; +} +export const Mission = (props: MissionProps) => { + const isSmallScreen = props.screenWidth === ScreenWidths.Sm; + const image = ( + <div className="col lg-col-6 md-col-6 col-12 sm-py2 px2 center"> + <img src="/images/jobs/map.png" style={{ width: '100%' }} /> + </div> + ); + const missionStatementStyle = !isSmallScreen ? { height: 364, lineHeight: '364px' } : undefined; + const missionStatement = ( + <div className="col lg-col-6 md-col-6 col-12 center" style={missionStatementStyle}> + <div + className="mx-auto inline-block align-middle" + style={{ maxWidth: 385, lineHeight: '44px', textAlign: 'center' }} + > + <div className="h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + Our Mission + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h4 sm-center" + style={{ fontFamily: 'Roboto', lineHeight: 2, maxWidth: 537 }} + > + We believe a system can exist in which all world value is accessible to anyone, anywhere, regardless + of where you happen to be born. + </div> + </div> + </div> + ); + return ( + <div + className="container lg-py4 md-py4" + style={{ backgroundColor: colors.jobsPageBackground, color: colors.black }} + > + <div className="mx-auto clearfix sm-py4"> + {isSmallScreen ? ( + <div> + {missionStatement} + {image} + </div> + ) : ( + <div> + {image} + {missionStatement} + </div> + )} + </div> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx new file mode 100644 index 000000000..f3d980315 --- /dev/null +++ b/packages/website/ts/pages/jobs/open_positions.tsx @@ -0,0 +1,192 @@ +import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; +import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; +import * as React from 'react'; + +import { Retry } from 'ts/components/ui/retry'; +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; +import { ScreenWidths, WebsiteBackendJobInfo } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; + +const labelStyle = { fontFamily: 'Roboto Mono', fontSize: 18 }; +const HEADER_TEXT = 'Open Positions'; +const TABLE_ROW_MIN_HEIGHT = 100; + +export interface OpenPositionsProps { + hash: string; + screenWidth: ScreenWidths; +} +export interface OpenPositionsState { + jobInfos?: WebsiteBackendJobInfo[]; + error?: Error; +} + +export class OpenPositions extends React.Component<OpenPositionsProps, OpenPositionsState> { + private _isUnmounted: boolean; + constructor(props: OpenPositionsProps) { + super(props); + this._isUnmounted = false; + this.state = { + jobInfos: undefined, + error: undefined, + }; + } + public componentWillMount(): void { + // tslint:disable-next-line:no-floating-promises + this._fetchJobInfosAsync(); + } + public componentWillUnmount(): void { + this._isUnmounted = true; + } + public render(): React.ReactNode { + const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.jobInfos); + return ( + <div id={this.props.hash} className="mx-auto max-width-4"> + {isReadyToRender ? this._renderBody() : this._renderLoading()} + </div> + ); + } + private _renderBody(): React.ReactNode { + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + return isSmallScreen ? this._renderList() : this._renderTable(); + } + private _renderLoading(): React.ReactNode { + return ( + // TODO: consolidate this loading component with the one in portal and RelayerIndex + // TODO: possibly refactor into a generic loading container with spinner and retry UI + <div className="center"> + {_.isUndefined(this.state.error) ? ( + <CircularProgress size={40} thickness={5} /> + ) : ( + <Retry onRetry={this._fetchJobInfosAsync.bind(this)} /> + )} + </div> + ); + } + private _renderList(): React.ReactNode { + return ( + <div style={{ backgroundColor: colors.jobsPageBackground }}> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(this.state.jobInfos, jobInfo => ( + <JobInfoListItem + key={jobInfo.id} + title={jobInfo.title} + description={jobInfo.department} + onClick={this._openJobInfoUrl.bind(this, jobInfo)} + /> + ))} + </div> + ); + } + private _renderTable(): React.ReactNode { + return ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + <Table selectable={false} onCellClick={this._onCellClick.bind(this)}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn colSpan={5} style={labelStyle}> + Position + </TableHeaderColumn> + <TableHeaderColumn colSpan={3} style={labelStyle}> + Department + </TableHeaderColumn> + <TableHeaderColumn colSpan={4} style={labelStyle}> + Office + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false} showRowHover={true}> + {_.map(this.state.jobInfos, jobInfo => { + return this._renderJobInfoTableRow(jobInfo); + })} + </TableBody> + </Table> + </div> + ); + } + private _renderJobInfoTableRow(jobInfo: WebsiteBackendJobInfo): React.ReactNode { + return ( + <TableRow + key={jobInfo.id} + hoverable={true} + displayBorder={false} + style={{ height: TABLE_ROW_MIN_HEIGHT, border: 2 }} + > + <TableRowColumn colSpan={5} style={labelStyle}> + {jobInfo.title} + </TableRowColumn> + <TableRowColumn colSpan={3} style={labelStyle}> + {jobInfo.department} + </TableRowColumn> + <TableRowColumn colSpan={4} style={labelStyle}> + {jobInfo.office} + </TableRowColumn> + </TableRow> + ); + } + private async _fetchJobInfosAsync(): Promise<void> { + try { + if (!this._isUnmounted) { + this.setState({ + jobInfos: undefined, + error: undefined, + }); + } + const jobInfos = await backendClient.getJobInfosAsync(); + if (!this._isUnmounted) { + this.setState({ + jobInfos, + }); + } + } catch (error) { + if (!this._isUnmounted) { + this.setState({ + error, + }); + } + } + } + private _onCellClick(rowNumber: number): void { + if (_.isUndefined(this.state.jobInfos)) { + return; + } + const jobInfo = this.state.jobInfos[rowNumber]; + this._openJobInfoUrl(jobInfo); + } + + private _openJobInfoUrl(jobInfo: WebsiteBackendJobInfo): void { + const url = jobInfo.url; + window.open(url, '_blank'); + } +} + +export interface JobInfoListItemProps { + title?: string; + description?: string; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; +} + +const PlainJobInfoListItem: React.StatelessComponent<JobInfoListItemProps> = ({ title, description, onClick }) => ( + <div className="mb3" onClick={onClick}> + <ListItem> + <Text fontWeight="bold" fontSize="16px" fontColor={colors.mediumBlue}> + {title + ' ›'} + </Text> + <Text className="pt1" fontSize="16px" fontColor={colors.darkGrey}> + {description} + </Text> + </ListItem> + </div> +); + +export const JobInfoListItem = styled(PlainJobInfoListItem)` + cursor: pointer; + &:hover { + opacity: 0.5; + } +`; diff --git a/packages/website/ts/pages/jobs/photo_rail.tsx b/packages/website/ts/pages/jobs/photo_rail.tsx new file mode 100644 index 000000000..acc9dcb91 --- /dev/null +++ b/packages/website/ts/pages/jobs/photo_rail.tsx @@ -0,0 +1,22 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { FilledImage } from 'ts/components/ui/filled_image'; + +export interface PhotoRailProps { + images: string[]; +} + +export const PhotoRail = (props: PhotoRailProps) => { + return ( + <div className="clearfix" style={{ height: 490 }}> + {_.map(props.images, (image: string) => { + return ( + <div key={image} className="col lg-col-4 md-col-4 col-12 center" style={{ height: '100%' }}> + <FilledImage src={image} /> + </div> + ); + })} + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/teams.tsx b/packages/website/ts/pages/jobs/teams.tsx new file mode 100644 index 000000000..80b036396 --- /dev/null +++ b/packages/website/ts/pages/jobs/teams.tsx @@ -0,0 +1,90 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; +import { ScreenWidths } from 'ts/types'; + +const TEAM_ITEM_PROPS_COLUMN1: TeamItemProps[] = [ + { + bulletColor: '#EB5757', + title: 'User Growth', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + title: 'Governance', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const TEAM_ITEM_PROPS_COLUMN2: TeamItemProps[] = [ + { + bulletColor: '#EB5757', + title: 'Developer Tools', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, + { + bulletColor: '#EB5757', + title: 'Marketing', + description: + 'Donec eget auctor mauris, a imperdiet ante. Ut a tellus ullamcorper, pharetra nibh sed, dignissim mauris. Quisque vel magna vitae nisi scelerisque commodo sed eget dolor. Maecenas vehicula orci', + }, +]; +const HEADER_TEXT = 'Our Teams'; +const MINIMUM_ITEM_HEIGHT = 240; + +export interface TeamsProps { + screenWidth: ScreenWidths; +} + +export const Teams = (props: TeamsProps) => (props.screenWidth === ScreenWidths.Sm ? <SmallLayout /> : <LargeLayout />); + +const LargeLayout = () => ( + <div className="mx-auto max-width-4 clearfix pb4"> + <div className="col lg-col-6 md-col-6 col-12"> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(TEAM_ITEM_PROPS_COLUMN1, teamItemProps => <TeamItem {...teamItemProps} />)} + </div> + <div className="col lg-col-6 md-col-6 col-12"> + <HeaderItem headerText=" " /> + {_.map(TEAM_ITEM_PROPS_COLUMN2, teamItemProps => <TeamItem {...teamItemProps} />)} + </div> + </div> +); + +const SmallLayout = () => ( + <div> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(_.concat(TEAM_ITEM_PROPS_COLUMN1, TEAM_ITEM_PROPS_COLUMN2), teamItemProps => ( + <TeamItem {...teamItemProps} /> + ))} + </div> +); + +interface TeamItemProps { + bulletColor: string; + title: string; + description: string; +} + +export const TeamItem: React.StatelessComponent<TeamItemProps> = ({ bulletColor, title, description }) => { + return ( + <div style={{ minHeight: MINIMUM_ITEM_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <Text fontWeight="bold" fontSize="16px" fontColor={colors.black}> + {title} + </Text> + </ListItem> + <ListItem> + <Text className="pt1" fontSize="16px" lineHeight="2em" fontColor={colors.black}> + {description} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/pages/jobs/values.tsx b/packages/website/ts/pages/jobs/values.tsx new file mode 100644 index 000000000..c7c4d5726 --- /dev/null +++ b/packages/website/ts/pages/jobs/values.tsx @@ -0,0 +1,60 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { HeaderItem } from 'ts/pages/jobs/list/header_item'; +import { ListItem } from 'ts/pages/jobs/list/list_item'; +import { colors } from 'ts/style/colors'; + +const VALUE_ITEM_PROPS_LIST: ValueItemProps[] = [ + { + bulletColor: '#6FCF97', + title: 'Ethics/Doing the right thing', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + bulletColor: '#56CCF2', + title: 'Consistently ship', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + bulletColor: '#EB5757', + title: 'Focus on long term impact', + description: 'orem ipsum dolor sit amet, consectetur adipiscing elit.', + }, +]; + +const HEADER_TEXT = 'Our Values'; +const VALUE_ITEM_MIN_HEIGHT = 150; + +export const Values = () => { + return ( + <div className="mx-auto max-width-4"> + <HeaderItem headerText={HEADER_TEXT} /> + {_.map(VALUE_ITEM_PROPS_LIST, valueItemProps => <ValueItem {...valueItemProps} />)} + </div> + ); +}; + +interface ValueItemProps { + bulletColor: string; + title: string; + description: string; +} + +export const ValueItem: React.StatelessComponent<ValueItemProps> = ({ bulletColor, title, description }) => { + return ( + <div style={{ minHeight: VALUE_ITEM_MIN_HEIGHT }}> + <ListItem bulletColor={bulletColor}> + <Text fontWeight="bold" fontSize="16x" fontColor={colors.black}> + {title} + </Text> + </ListItem> + <ListItem> + <Text className="pt1" fontSize="16x" lineHeight="2em" fontColor={colors.black}> + {description} + </Text> + </ListItem> + </div> + ); +}; diff --git a/packages/website/ts/style/colors.ts b/packages/website/ts/style/colors.ts index 5ffdd6ba7..b15000d7a 100644 --- a/packages/website/ts/style/colors.ts +++ b/packages/website/ts/style/colors.ts @@ -11,6 +11,8 @@ const appColors = { wrapEtherConfirmationButton: sharedColors.mediumBlue, drawerMenuBackground: '#4a4a4a', menuItemDefaultSelectedBackground: '#424242', + jobsPageBackground: sharedColors.grey50, + jobsPageOpenPositionRow: sharedColors.grey100, }; export const colors = { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 15444e517..24e86a901 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -536,4 +536,12 @@ export interface WebsiteBackendTokenInfo { export interface WebsiteBackendGasInfo { average: number; } + +export interface WebsiteBackendJobInfo { + id: number; + title: string; + department: string; + office: string; + url: string; +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts index 6b16aea6b..835a6ef4d 100644 --- a/packages/website/ts/utils/backend_client.ts +++ b/packages/website/ts/utils/backend_client.ts @@ -1,10 +1,17 @@ import * as _ from 'lodash'; -import { ArticlesBySection, WebsiteBackendGasInfo, WebsiteBackendPriceInfo, WebsiteBackendRelayerInfo } from 'ts/types'; +import { + ArticlesBySection, + WebsiteBackendGasInfo, + WebsiteBackendJobInfo, + WebsiteBackendPriceInfo, + WebsiteBackendRelayerInfo, +} from 'ts/types'; import { fetchUtils } from 'ts/utils/fetch_utils'; import { utils } from 'ts/utils/utils'; const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station'; +const JOBS_ENDPOINT = '/jobs'; const PRICES_ENDPOINT = '/prices'; const RELAYERS_ENDPOINT = '/relayers'; const WIKI_ENDPOINT = '/wiki'; @@ -15,6 +22,10 @@ export const backendClient = { const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), ETH_GAS_STATION_ENDPOINT); return result; }, + async getJobInfosAsync(): Promise<WebsiteBackendJobInfo[]> { + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), JOBS_ENDPOINT); + return result; + }, async getPriceInfoAsync(tokenSymbols: string[]): Promise<WebsiteBackendPriceInfo> { if (_.isEmpty(tokenSymbols)) { return {}; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 651a4212a..fdee264b2 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -318,9 +318,18 @@ export const utils = { shouldShowPortalV2(): boolean { return this.isDevelopment() || this.isStaging() || this.isDogfood(); }, + shouldShowJobsPage(): boolean { + return this.isDevelopment() || this.isStaging() || this.isDogfood(); + }, getEthToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress); + }, + getZrxToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ZRX_TOKEN_SYMBOL, tokenByAddress); + }, + getTokenBySymbol(symbol: string, tokenByAddress: TokenByAddress): Token { const tokens = _.values(tokenByAddress); - const etherToken = _.find(tokens, { symbol: constants.ETHER_TOKEN_SYMBOL }); - return etherToken; + const token = _.find(tokens, { symbol }); + return token; }, }; |