aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-06-14 17:17:58 +0800
committerFabio Berger <me@fabioberger.com>2018-06-14 17:17:58 +0800
commitf9410d5d008ce289d075741710c6f7705bd3ad2a (patch)
treee2ca5fe2601ae1958f9ed6555b60fe8f1a20678a
parentc232a32991725f2f05d61c40f1fb899d138544d2 (diff)
parent0e354e5ea1f9951088331a310999bf87c8f8f4b3 (diff)
downloaddexon-sol-tools-f9410d5d008ce289d075741710c6f7705bd3ad2a.tar.gz
dexon-sol-tools-f9410d5d008ce289d075741710c6f7705bd3ad2a.tar.zst
dexon-sol-tools-f9410d5d008ce289d075741710c6f7705bd3ad2a.zip
Merge branch 'v2-prototype' into feature/combinatorial-testing
* v2-prototype: (27 commits) Add back redirector behind feature flag Update minHeight in Text Use Text components in Values Use spread operator instead of React.createElement Fix issue with positions hash Revert localhost config Use Text in Teams Use Text in OpenPositions Use Text component for HeaderItem Remove FloatingImage Move FilledImage into components/ui Replace FlatButton with Button in Retry Fix incorrect colors Add font family to Button component and use in Join0x component Consolidate jobs page grey colors with shared colors Fix lint errors Remove extra packages Implement small open positions Implement large screen open positions Teams section ...
-rw-r--r--packages/website/public/images/jobs/location1.pngbin0 -> 659608 bytes
-rw-r--r--packages/website/public/images/jobs/location2.pngbin0 -> 223872 bytes
-rw-r--r--packages/website/public/images/jobs/location3.pngbin0 -> 288733 bytes
-rw-r--r--packages/website/public/images/jobs/map.pngbin0 -> 271102 bytes
-rw-r--r--packages/website/public/images/jobs/office1.pngbin0 -> 482657 bytes
-rw-r--r--packages/website/public/images/jobs/office2.pngbin0 -> 479918 bytes
-rw-r--r--packages/website/public/images/jobs/office3.pngbin0 -> 480055 bytes
-rw-r--r--packages/website/ts/components/redirector.tsx (renamed from packages/website/ts/components/redirecter.tsx)4
-rw-r--r--packages/website/ts/components/relayer_index/relayer_index.tsx33
-rw-r--r--packages/website/ts/components/ui/button.tsx4
-rw-r--r--packages/website/ts/components/ui/filled_image.tsx18
-rw-r--r--packages/website/ts/components/ui/retry.tsx32
-rw-r--r--packages/website/ts/components/ui/text.tsx4
-rw-r--r--packages/website/ts/containers/jobs.ts28
-rw-r--r--packages/website/ts/index.tsx11
-rw-r--r--packages/website/ts/pages/jobs/benefits.tsx109
-rw-r--r--packages/website/ts/pages/jobs/jobs.tsx81
-rw-r--r--packages/website/ts/pages/jobs/join_0x.tsx41
-rw-r--r--packages/website/ts/pages/jobs/list/header_item.tsx26
-rw-r--r--packages/website/ts/pages/jobs/list/list_item.tsx15
-rw-r--r--packages/website/ts/pages/jobs/mission.tsx56
-rw-r--r--packages/website/ts/pages/jobs/open_positions.tsx192
-rw-r--r--packages/website/ts/pages/jobs/photo_rail.tsx22
-rw-r--r--packages/website/ts/pages/jobs/teams.tsx90
-rw-r--r--packages/website/ts/pages/jobs/values.tsx60
-rw-r--r--packages/website/ts/style/colors.ts2
-rw-r--r--packages/website/ts/types.ts8
-rw-r--r--packages/website/ts/utils/backend_client.ts13
-rw-r--r--packages/website/ts/utils/utils.ts3
29 files changed, 814 insertions, 38 deletions
diff --git a/packages/website/public/images/jobs/location1.png b/packages/website/public/images/jobs/location1.png
new file mode 100644
index 000000000..bfda47576
--- /dev/null
+++ b/packages/website/public/images/jobs/location1.png
Binary files differ
diff --git a/packages/website/public/images/jobs/location2.png b/packages/website/public/images/jobs/location2.png
new file mode 100644
index 000000000..c05f9403f
--- /dev/null
+++ b/packages/website/public/images/jobs/location2.png
Binary files differ
diff --git a/packages/website/public/images/jobs/location3.png b/packages/website/public/images/jobs/location3.png
new file mode 100644
index 000000000..34b2e5380
--- /dev/null
+++ b/packages/website/public/images/jobs/location3.png
Binary files differ
diff --git a/packages/website/public/images/jobs/map.png b/packages/website/public/images/jobs/map.png
new file mode 100644
index 000000000..7b85ff66e
--- /dev/null
+++ b/packages/website/public/images/jobs/map.png
Binary files differ
diff --git a/packages/website/public/images/jobs/office1.png b/packages/website/public/images/jobs/office1.png
new file mode 100644
index 000000000..f6e6d9163
--- /dev/null
+++ b/packages/website/public/images/jobs/office1.png
Binary files differ
diff --git a/packages/website/public/images/jobs/office2.png b/packages/website/public/images/jobs/office2.png
new file mode 100644
index 000000000..65f97dcad
--- /dev/null
+++ b/packages/website/public/images/jobs/office2.png
Binary files differ
diff --git a/packages/website/public/images/jobs/office3.png b/packages/website/public/images/jobs/office3.png
new file mode 100644
index 000000000..1dfcb9c58
--- /dev/null
+++ b/packages/website/public/images/jobs/office3.png
Binary files differ
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/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/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/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..eb384fbb4 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -318,6 +318,9 @@ 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 {
const tokens = _.values(tokenByAddress);
const etherToken = _.find(tokens, { symbol: constants.ETHER_TOKEN_SYMBOL });