diff options
Diffstat (limited to 'packages/website/ts/pages/jobs/open_positions.tsx')
-rw-r--r-- | packages/website/ts/pages/jobs/open_positions.tsx | 192 |
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; + } +`; |