Merge branch 'v2-prototype' of https://github.com/0xProject/0x-monorepo into feature/website/portal-v2-analytics
BIN
packages/website/public/images/jobs/location1.png
Normal file
|
After Width: | Height: | Size: 644 KiB |
BIN
packages/website/public/images/jobs/location2.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
packages/website/public/images/jobs/location3.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
packages/website/public/images/jobs/map.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
packages/website/public/images/jobs/office1.png
Normal file
|
After Width: | Height: | Size: 471 KiB |
BIN
packages/website/public/images/jobs/office2.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
packages/website/public/images/jobs/office3.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
@@ -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 = {
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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';
|
||||
|
||||
18
packages/website/ts/components/ui/filled_image.tsx
Normal file
@@ -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',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
32
packages/website/ts/components/ui/retry.tsx
Normal file
@@ -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>
|
||||
);
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
41
packages/website/ts/containers/inputs/allowance_toggle.ts
Normal file
@@ -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);
|
||||
28
packages/website/ts/containers/jobs.ts
Normal file
@@ -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);
|
||||
@@ -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 {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
109
packages/website/ts/pages/jobs/benefits.tsx
Normal file
@@ -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>
|
||||
);
|
||||
81
packages/website/ts/pages/jobs/jobs.tsx
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
41
packages/website/ts/pages/jobs/join_0x.tsx
Normal file
@@ -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>
|
||||
);
|
||||
26
packages/website/ts/pages/jobs/list/header_item.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
15
packages/website/ts/pages/jobs/list/list_item.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
56
packages/website/ts/pages/jobs/mission.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
192
packages/website/ts/pages/jobs/open_positions.tsx
Normal file
@@ -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;
|
||||
}
|
||||
`;
|
||||
22
packages/website/ts/pages/jobs/photo_rail.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
90
packages/website/ts/pages/jobs/teams.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
60
packages/website/ts/pages/jobs/values.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,8 @@ const appColors = {
|
||||
wrapEtherConfirmationButton: sharedColors.mediumBlue,
|
||||
drawerMenuBackground: '#4a4a4a',
|
||||
menuItemDefaultSelectedBackground: '#424242',
|
||||
jobsPageBackground: sharedColors.grey50,
|
||||
jobsPageOpenPositionRow: sharedColors.grey100,
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||