aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/pages/jobs/open_positions.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/pages/jobs/open_positions.tsx')
-rw-r--r--packages/website/ts/pages/jobs/open_positions.tsx192
1 files changed, 192 insertions, 0 deletions
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;
+ }
+`;