diff options
Diffstat (limited to 'app/client/auth')
| -rw-r--r-- | app/client/auth/auth.actions.js | 86 | ||||
| -rw-r--r-- | app/client/auth/auth.gate.js | 63 | ||||
| -rw-r--r-- | app/client/auth/auth.reducer.js | 92 | ||||
| -rw-r--r-- | app/client/auth/index.js | 11 | ||||
| -rw-r--r-- | app/client/auth/login.component.js | 89 | ||||
| -rw-r--r-- | app/client/auth/logout.component.js | 24 | ||||
| -rw-r--r-- | app/client/auth/signup.component.js | 104 |
7 files changed, 469 insertions, 0 deletions
diff --git a/app/client/auth/auth.actions.js b/app/client/auth/auth.actions.js new file mode 100644 index 0000000..c4d9b52 --- /dev/null +++ b/app/client/auth/auth.actions.js @@ -0,0 +1,86 @@ +import types from '../types' +import { put } from '../api/crud.fetch' + +export const setToken = (data) => { + return { type: types.auth.set_token, data } +} +export const setReturnTo = (data) => { + return { type: types.auth.set_return_to, data } +} +export const setError = (data) => { + return { type: types.auth.set_error, data } +} +export const setCurrentUser = (data) => { + return { type: types.auth.set_current_user, data } +} +export function logout() { + return { type: types.auth.logout_user } +} +export function initialized() { + return { type: types.auth.initialized } +} +export function loading() { + return { type: types.auth.loading } +} + +export function InvalidCredentialsException(message) { + this.message = message + this.name = 'InvalidCredentialsException' +} + +const api = { + login: '/api/login', + logout: '/api/logout', + signup: '/api/signup', + checkin: '/api/checkin', +} + +export function login(username, password) { + return (dispatch) => { + dispatch(loading()) + fetch(api.login, put({ + username, + password + })) + .then(req => req.json()) + .then(data => { + console.log(data) + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(setError(true)) + }) + } +} + +export function signup(data) { + return (dispatch) => { + dispatch(loading()) + fetch(api.signup, put(data)) + .then(req => req.json()) + .then(data => { + console.log(data) + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(initialized()) + }) + } +} + +export function checkin() { + return (dispatch) => { + dispatch(loading()) + fetch(api.checkin, put({})) + .then(req => req.json()) + .then(data => { + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(initialized()) + }) + } +} diff --git a/app/client/auth/auth.gate.js b/app/client/auth/auth.gate.js new file mode 100644 index 0000000..087dfc6 --- /dev/null +++ b/app/client/auth/auth.gate.js @@ -0,0 +1,63 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { BrowserRouter, Route, Switch, Redirect, withRouter } from 'react-router-dom' +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import * as authActions from './auth.actions'; + +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +import { randint } from '../util/math' + +class AuthRouter extends Component { + render(){ + return ( + <BrowserRouter> + <div> + <div className="diamond"></div> + <Switch> + <Route exact path='/' component={Login} /> + <Route exact path='/login' component={Login} /> + <Route exact path='/logout' component={Logout} /> + <Route exact path='/signup' component={Signup} /> + <Route component={props => { + this.props.actions.setReturnTo(props.location.pathname) + return ( + <Redirect to="/login" /> + ) + }} /> + </Switch> + </div> + </BrowserRouter> + ) + } + componentDidMount(){ + document.querySelector('.diamond').style.backgroundImage = 'linear-gradient(' + (randint(40)-5) + 'deg, #fde, #ffe)' + } +} + +class AuthGate extends Component { + render(){ + if (!this.props.auth.initialized) { + return <div className='loading'>Loading</div> + } + if (this.props.auth.isAuthenticated) return <div>{this.props.children}</div> + return <AuthRouter {...this.props} /> + } + componentDidMount(){ + this.props.actions.checkin() + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AuthGate); diff --git a/app/client/auth/auth.reducer.js b/app/client/auth/auth.reducer.js new file mode 100644 index 0000000..80b1ec5 --- /dev/null +++ b/app/client/auth/auth.reducer.js @@ -0,0 +1,92 @@ +import types from '../types' + +const authInitialState = { + token: null, + user: {}, + groups: {}, + initialized: false, + loading: false, + isAuthenticated: false, + returnTo: null, +} + +const auth = (state = authInitialState, action) => { + switch(action.type) { + case types.auth.set_token: + return { + ...state, + token: action.data, + isAuthenticated: !!action.data, + loading: false, + error: null, + } + + case types.auth.initialized: + return { + ...state, + loading: false, + initialized: true, + error: null, + } + + case types.auth.loading: + return { + ...state, + loading: true, + error: null, + } + + case types.auth.set_current_user: + return { + ...state, + loading: false, + initialized: true, + isAuthenticated: true, + user: action.data, + error: null, + } + + case types.auth.set_return_to: + return { + ...state, + returnTo: action.data, + } + + case types.auth.logout_user: + return { + ...authInitialState + } + + case types.auth.set_error: + return { + ...state, + loading: false, + error: action.data, + } + + case types.auth.loading: + // const initial_state_el = document.querySelector('#initial_state') + // if (initial_state_el) { + // try { + // const initial_state = JSON.parse(initial_state_el.innerHTML) + // if (initial_state && initial_state.auth && initial_state.auth.user) { + // console.log(initial_state.auth.user) + // return { + // ...state, + // user: { + // ...initial_state.auth.user, + // } + // } + // } + // } catch (e) { + // console.error("error loading initial state") + // } + // } + return state + + default: + return state + } +} + +export default auth diff --git a/app/client/auth/index.js b/app/client/auth/index.js new file mode 100644 index 0000000..5e6b2b0 --- /dev/null +++ b/app/client/auth/index.js @@ -0,0 +1,11 @@ +import Gate from './auth.gate'; +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +export default { + Gate, + Login, + Logout, + Signup, +}
\ No newline at end of file diff --git a/app/client/auth/login.component.js b/app/client/auth/login.component.js new file mode 100644 index 0000000..2ef01a6 --- /dev/null +++ b/app/client/auth/login.component.js @@ -0,0 +1,89 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +// import { Link } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Login extends Component { + state = { + username: '', + password: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(value, name) { + this.setState({ + [name]: value, + error: null, + }) + } + handleSubmit(e) { + e.preventDefault() + if (this.props.auth.loading) return + this.props.actions.login(this.state.username, this.state.password) + } + render(){ + if (this.props.auth.isAuthenticated) { + let { returnTo } = this.props.auth + if (!returnTo || returnTo.match(/(login|logout|signup)/i)) { + returnTo = '/' + } + return <Redirect to={returnTo} /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>Log in</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onInput={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onInput={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Login + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"There was an error logging you in (bad password?)"}</div> + ) + } + return null + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/app/client/auth/logout.component.js b/app/client/auth/logout.component.js new file mode 100644 index 0000000..bcc3bce --- /dev/null +++ b/app/client/auth/logout.component.js @@ -0,0 +1,24 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +class Logout extends Component { + componentWillMount(props){ + this.props.actions.logout() + } + render(){ + return <Redirect to="/" /> + } +} + +const mapStateToProps = (state) => ({ +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Logout); diff --git a/app/client/auth/signup.component.js b/app/client/auth/signup.component.js new file mode 100644 index 0000000..4882681 --- /dev/null +++ b/app/client/auth/signup.component.js @@ -0,0 +1,104 @@ +import { h, Component } from 'preact'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import * as actions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Signup extends Component { + state = { + username: '', + password: '', + password2: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(value, name) { + this.setState({ + [name]: value, + }) + } + validate(){ + if (!this.state.password || this.state.password !== this.state.password2) { + return false + } + return true + } + handleSubmit(e) { + e.preventDefault() + if (this.props.auth.loading) return + if (!this.validate()) { + return this.props.actions.setError('bad password') + } + let { ...user } = this.state + this.props.actions.signup(user) + } + render(){ + if (this.props.auth.isAuthenticated) { + let { returnTo } = this.props.auth + if (!returnTo || returnTo.match(/(api|login|logout|signup)/i)) { + returnTo = '/' + } + return <Redirect to={returnTo} /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>New account</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onInput={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onInput={this.handleChange} + /> + <TextInput + title="Password again :)" + name="password2" + type="password" + value={this.state.password2} + onInput={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Sign up + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"Please doublecheck the form (o=_o~~)"}</div> + ) + } + return <div className='form-input-hint'></div> + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ ...actions }, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Signup); |
