aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrandon Millman <brandon@0xproject.com>2018-06-14 04:34:54 +0800
committerGitHub <noreply@github.com>2018-06-14 04:34:54 +0800
commit0e354e5ea1f9951088331a310999bf87c8f8f4b3 (patch)
tree91e67da2ff5c7bfd85f31714eb09e34292eb4c66
parent4efd28c092e74b438d0397069c0c55cc90c537f2 (diff)
parentd172a97247f133a5340c5df263fe0f23019db956 (diff)
downloaddexon-0x-contracts-0e354e5ea1f9951088331a310999bf87c8f8f4b3.tar.gz
dexon-0x-contracts-0e354e5ea1f9951088331a310999bf87c8f8f4b3.tar.zst
dexon-0x-contracts-0e354e5ea1f9951088331a310999bf87c8f8f4b3.zip
Merge pull request #685 from 0xProject/feature/website/jobs-page
Jobs page
-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 });