| @ -0,0 +1,4 @@ | |||
| export const LOGIN_STATUS_UNKNOWN = Symbol("LOGIN_STATUS_UNKNOWN"); | |||
| export const LOGIN_STATUS_PENDING = Symbol("LOGIN_STATUS_PENDING"); | |||
| export const LOGIN_STATUS_SUCCESS = Symbol("LOGIN_STATUS_SUCCESS"); | |||
| export const LOGIN_STATUS_FAILURE = Symbol("LOGIN_STATUS_FAILURE"); | |||
| @ -0,0 +1,104 @@ | |||
| import React, { PropTypes } from "react"; | |||
| import { connect } from "react-redux"; | |||
| import ControlRequest from "../utils/ControlRequest"; | |||
| import propTypeSymbol from "../utils/propTypeSymbol"; | |||
| import { | |||
| LOGIN_STATUS_UNKNOWN, | |||
| LOGIN_STATUS_PENDING, | |||
| LOGIN_STATUS_SUCCESS, | |||
| LOGIN_STATUS_FAILURE | |||
| } from "../constants/login"; | |||
| const ID = "login-status-pane"; | |||
| const INTERVAL_MSEC = 60 * 1000; | |||
| class LoginStatusPane extends React.Component | |||
| { | |||
| constructor(props) { | |||
| super(props); | |||
| } | |||
| componentDidMount() { | |||
| const fetchStatus = () => { | |||
| this.props.socketSend(ControlRequest.loginStatus()); | |||
| }; | |||
| fetchStatus(); | |||
| // Refresh login status periodically | |||
| this.interval_id = setInterval(fetchStatus, INTERVAL_MSEC); | |||
| } | |||
| componentWillUnmount() { | |||
| clearInterval(this.interval_id); | |||
| } | |||
| render_unknown() { | |||
| return ( | |||
| <div id={ID}> | |||
| Fetching login status... | |||
| </div> | |||
| ); | |||
| } | |||
| render_pending() { | |||
| return ( | |||
| <div id={ID}> | |||
| Logging in as {this.props.username}... | |||
| </div> | |||
| ); | |||
| } | |||
| render_success() { | |||
| let motd_element; | |||
| if (this.props.motd) { | |||
| motd_element = ( | |||
| <span id="login-motd"> | |||
| MOTD: {this.props.motd} | |||
| </span> | |||
| ); | |||
| } | |||
| return ( | |||
| <div id={ID}> | |||
| Logged in as {this.props.username} | |||
| {motd_element} | |||
| </div> | |||
| ); | |||
| } | |||
| render_failure() { | |||
| return ( | |||
| <div id={ID}> | |||
| Failed to log in as {this.props.username} | |||
| <span id="login-reason"> | |||
| Reason: {this.props.reason} | |||
| </span> | |||
| </div> | |||
| ); | |||
| } | |||
| render() { | |||
| switch (this.props.status) { | |||
| case LOGIN_STATUS_UNKNOWN: | |||
| return this.render_unknown(); | |||
| case LOGIN_STATUS_PENDING: | |||
| return this.render_pending(); | |||
| case LOGIN_STATUS_SUCCESS: | |||
| return this.render_success(); | |||
| case LOGIN_STATUS_FAILURE: | |||
| return this.render_failure(); | |||
| } | |||
| } | |||
| } | |||
| LoginStatusPane.propTypes = { | |||
| status: propTypeSymbol.isRequired, | |||
| username: PropTypes.string, | |||
| motd: PropTypes.string, | |||
| reason: PropTypes.string, | |||
| socketSend: PropTypes.func.isRequired | |||
| }; | |||
| export default connect( | |||
| state => state.login | |||
| )(LoginStatusPane); | |||
| @ -0,0 +1,56 @@ | |||
| import { SOCKET_RECEIVE_MESSAGE } from "../constants/ActionTypes"; | |||
| import { | |||
| LOGIN_STATUS_UNKNOWN, | |||
| LOGIN_STATUS_PENDING, | |||
| LOGIN_STATUS_SUCCESS, | |||
| LOGIN_STATUS_FAILURE | |||
| } from "../constants/login"; | |||
| const initialState = { | |||
| status: LOGIN_STATUS_UNKNOWN | |||
| }; | |||
| export default (state = initialState, action) => { | |||
| const { type, payload } = action; | |||
| if (type !== SOCKET_RECEIVE_MESSAGE) { | |||
| return state; | |||
| } | |||
| const { variant, data } = payload; | |||
| if (variant !== "LoginStatusResponse") { | |||
| return state; | |||
| } | |||
| switch (data.variant) { | |||
| case "Pending": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username ] = data.fields; | |||
| return { | |||
| status: LOGIN_STATUS_PENDING, | |||
| username | |||
| }; | |||
| } | |||
| case "Success": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username, motd ] = data.fields; | |||
| return { | |||
| status: LOGIN_STATUS_SUCCESS, | |||
| username, | |||
| motd | |||
| }; | |||
| } | |||
| case "Failure": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username, reason ] = data.fields; | |||
| return { | |||
| status: LOGIN_STATUS_FAILURE, | |||
| username, | |||
| reason | |||
| }; | |||
| } | |||
| default: | |||
| return state; | |||
| } | |||
| }; | |||
| @ -0,0 +1,15 @@ | |||
| const checkRequiredThenValidate = (validator) => | |||
| (props, propName, componentName, ...rest) => | |||
| { | |||
| if (props[propName] !== null) { | |||
| return validator(props, propName, componentName, ...rest); | |||
| } | |||
| return new Error( | |||
| `Missing prop \`${propName}\` not supplied to \`${componentName}\`` | |||
| ); | |||
| }; | |||
| export default (validator) => { | |||
| validator.isRequired = checkRequiredThenValidate(validator); | |||
| return validator; | |||
| }; | |||
| @ -0,0 +1,20 @@ | |||
| import propTypeRequiredWrapper from "./propTypeRequiredWrapper"; | |||
| // Simple validator for Symbols (instanceof does not work on symbols). | |||
| function propTypeSymbol(props, propName, componentName) { | |||
| const prop = props[propName]; | |||
| if (prop === null) { | |||
| return; | |||
| } | |||
| const type = typeof prop; | |||
| if (type === "symbol") { | |||
| return; | |||
| } | |||
| return new Error( | |||
| `Invalid prop \`${propName}\` of type \`${type}\` ` + | |||
| `supplied to \`${componentName}\`, expected \`symbol\`` | |||
| ); | |||
| } | |||
| export default propTypeRequiredWrapper(propTypeSymbol); | |||