summaryrefslogtreecommitdiff
path: root/app/client/auth
diff options
context:
space:
mode:
Diffstat (limited to 'app/client/auth')
-rw-r--r--app/client/auth/auth.actions.js86
-rw-r--r--app/client/auth/auth.gate.js63
-rw-r--r--app/client/auth/auth.reducer.js92
-rw-r--r--app/client/auth/index.js11
-rw-r--r--app/client/auth/login.component.js89
-rw-r--r--app/client/auth/logout.component.js24
-rw-r--r--app/client/auth/signup.component.js104
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);