| @ -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); | |||||