diff --git a/website/.env b/website/.env
index ff5808a..c2b97c5 100644
--- a/website/.env
+++ b/website/.env
@@ -1 +1,2 @@
-REACT_APP_TITLE=Deighton AR System
\ No newline at end of file
+REACT_APP_TITLE=Deighton AR System
+REACT_APP_SUPPORT_EMAIL=support@kss.us.com
diff --git a/website/src/API.js b/website/src/API.js
index 9f87df7..32b8435 100644
--- a/website/src/API.js
+++ b/website/src/API.js
@@ -1,5 +1,6 @@
import EventEmitter from 'eventemitter3'
import io from 'socket.io-client'
+import autobind from 'autobind-decorator'
const authTokenName = 'AuthToken'
@@ -28,6 +29,7 @@ class APIError extends Error {
}
}
+@autobind
class API extends EventEmitter {
constructor() {
super()
diff --git a/website/src/App.js b/website/src/App.js
index dc5638f..b647e0f 100644
--- a/website/src/App.js
+++ b/website/src/App.js
@@ -1,5 +1,5 @@
import React, { Component, Fragment } from 'react'
-import { Login, Logout, ResetPassword, ForgotPassword, ConfirmEmail, ProtectedRoute } from './Auth'
+import { Login, Logout, ResetPassword, ForgotPassword, ConfirmEmail, ProtectedRoute, DefaultRoute } from './Auth'
import { Home } from './Home'
import { Profile } from './Profile'
import { Users } from './Users'
@@ -92,16 +92,17 @@ export class App extends Component {
-
-
-
-
-
- ()} />
-
-
-
- ()} />
+
+
+
+
+
+
+ ()} />
+
+
+ ()} />
+
diff --git a/website/src/Auth/ConfirmEmail.js b/website/src/Auth/ConfirmEmail.js
index 0182b67..ad583d4 100644
--- a/website/src/Auth/ConfirmEmail.js
+++ b/website/src/Auth/ConfirmEmail.js
@@ -2,6 +2,7 @@ import React from 'react'
import { api } from 'src/API'
import PropTypes from 'prop-types'
import { MessageModal, WaitModal } from '../Modal'
+import { Logout } from '.'
import autobind from 'autobind-decorator'
export class ConfirmEmail extends React.Component {
@@ -17,7 +18,8 @@ export class ConfirmEmail extends React.Component {
}
componentDidMount(props) {
- let emailToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('email-token')
+ const emailToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('email-token')
+
this.setState({ waitModal: { message: 'Validating Email...' } })
if (emailToken) {
api.confirmEmail(emailToken).then((response) => {
@@ -29,21 +31,17 @@ export class ConfirmEmail extends React.Component {
this.props.history.replace('/login')
}
}).catch((err) => {
- console.error(err)
- const supportEmail = 'support@kingstonsoftware.solutions' // TODO: From configuration
- const message = err.message.includes('The token was not found')
- ? 'This email address may have already been confirmed.'
- : `Please contact ${supportEmail} to request a new user invitation`
this.setState({
waitModal: null,
messageModal: {
- title: 'Error Verifying Email...',
- message: `We couldn't complete that request. ${message}`
+ icon: 'hand',
+ message: `Please contact ${process.env.REACT_APP_SUPPORT_EMAIL} to request another confirmation email.`,
+ detail: err.message
}
})
})
} else {
- this.props.history.replace('/login')
+ this.props.history.replace('/')
}
}
@@ -54,14 +52,23 @@ export class ConfirmEmail extends React.Component {
}
render() {
+ const { messageModal, waitModal } = this.state
+
+ if (api.loggedInUser) {
+ return
+ }
+
return (
-
+
-
)
diff --git a/website/src/Auth/DefaultRoute.js b/website/src/Auth/DefaultRoute.js
new file mode 100644
index 0000000..4612f70
--- /dev/null
+++ b/website/src/Auth/DefaultRoute.js
@@ -0,0 +1,47 @@
+import React, { Fragment, Component } from 'react'
+import { api } from 'src/API'
+import { Route, Redirect } from 'react-router-dom'
+import { Column } from 'ui'
+import autobind from 'autobind-decorator'
+
+export class DefaultRoute extends Component {
+ @autobind
+ updateComponent() {
+ this.forceUpdate()
+ }
+
+ componentDidMount() {
+ api.addListener('login', this.updateComponent)
+ }
+
+ componentWillUnmount() {
+ api.removeListener('login', this.updateComponent)
+ }
+
+ render() {
+ const user = api.loggedInUser
+ let redirect = null
+
+ if (user) {
+ if (!user.pending) {
+ redirect =
+ }
+ } else {
+ redirect =
+ }
+
+ return (
+ {
+ return (
+
+
+ {redirect}
+
+ )
+ }}
+ />
+ )
+ }
+}
diff --git a/website/src/Auth/ForgotPassword.js b/website/src/Auth/ForgotPassword.js
index 6cd2e5b..1bd57fc 100644
--- a/website/src/Auth/ForgotPassword.js
+++ b/website/src/Auth/ForgotPassword.js
@@ -5,6 +5,7 @@ import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from 'ui'
import { MessageModal, WaitModal } from '../Modal'
import { api } from 'src/API'
import { FormBinder } from 'react-form-binder'
+import { Logout } from '.'
import headerLogo from 'images/deighton.png'
import { sizeInfo, colorInfo } from 'ui/style'
import autobind from 'autobind-decorator'
@@ -65,6 +66,10 @@ export class ForgotPassword extends Component {
render() {
const { binder, waitModal, messageModal } = this.state
+ if (api.loggedInUser) {
+ return
+ }
+
return (
diff --git a/website/src/Auth/Logout.js b/website/src/Auth/Logout.js
index 9b19c7c..d805b40 100644
--- a/website/src/Auth/Logout.js
+++ b/website/src/Auth/Logout.js
@@ -1,21 +1,36 @@
-import React from 'react'
+import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { api } from 'src/API'
+import { Column } from 'ui'
-export class Logout extends React.Component {
+export class Logout extends Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
+ redirect: PropTypes.string,
}
componentDidMount(event) {
- api.logout().then(() => {
- if (this.props.history) {
- this.props.history.replace('/login')
+ const { history, redirect } = this.props
+ const cb = () => {
+ if (history && redirect) {
+ try {
+ history.replace(redirect)
+ } catch (error) {
+ history.replace('/login')
+ }
+ } else {
+ window.location.replace('/login')
}
- })
+ }
+
+ api.logout().then(cb, cb)
}
render() {
- return null
+ return (
+
+
+
+ )
}
}
diff --git a/website/src/Auth/ProtectedRoute.js b/website/src/Auth/ProtectedRoute.js
index 663de1d..5c1f4b0 100644
--- a/website/src/Auth/ProtectedRoute.js
+++ b/website/src/Auth/ProtectedRoute.js
@@ -6,14 +6,7 @@ import autobind from 'autobind-decorator'
export class ProtectedRoute extends React.Component {
static propTypes = {
- location: PropTypes.shape({
- pathname: PropTypes.string,
- search: PropTypes.string,
- }),
- }
-
- static defaultProps = {
- roles: ['administrator']
+ location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string }),
}
@autobind
@@ -30,7 +23,7 @@ export class ProtectedRoute extends React.Component {
}
render(props) {
- let user = api.loggedInUser
+ const user = api.loggedInUser
if (user) {
if (user.pending) {
@@ -40,8 +33,8 @@ export class ProtectedRoute extends React.Component {
} else if (user.administrator) {
return
}
+ } else {
+ return
}
-
- return
}
}
diff --git a/website/src/Auth/ResetPassword.js b/website/src/Auth/ResetPassword.js
index b8878ca..b8873e0 100644
--- a/website/src/Auth/ResetPassword.js
+++ b/website/src/Auth/ResetPassword.js
@@ -1,6 +1,7 @@
import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { Box, Text, Image, Column, Row, BoundInput, BoundButton } from 'ui'
+import { Logout } from '.'
import { MessageModal, WaitModal } from '../Modal'
import { api } from 'src/API'
import { FormBinder } from 'react-form-binder'
@@ -32,7 +33,39 @@ export class ResetPassword extends Component {
this.state = {
binder: new FormBinder({}, ResetPassword.bindings),
messageModal: null,
- waitModal: null
+ waitModal: null,
+ tokenConfirmed: false,
+ }
+ }
+
+ componentDidMount(props) {
+ if (this.state.tokenConfirmed) {
+ return
+ }
+
+ const passwordToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('password-token')
+
+ this.setState({ waitModal: { message: 'Confirming password reset...' } })
+ if (passwordToken) {
+ api.confirmResetPassword(passwordToken).then((response) => {
+ this.setState({ waitModal: null })
+ if (response && response.valid) {
+ this.setState({ tokenConfirmed: true })
+ } else {
+ this.props.history.replace('/')
+ }
+ }).catch((err) => {
+ this.setState({
+ waitModal: null,
+ messageModal: {
+ icon: 'hand',
+ message: `We were unable to confirm you requested a password reset. Please request another reset email.`,
+ detail: err.message
+ }
+ })
+ })
+ } else {
+ this.props.history.replace('/')
}
}
@@ -54,8 +87,9 @@ export class ResetPassword extends Component {
waitModal: null,
messageModal: {
icon: 'hand',
- title: 'There was a problem changing your password',
+ message: 'There was a problem changing your password. Please request another reset email.',
detail: err.message,
+ noRetry: true,
}
})
})
@@ -63,12 +97,20 @@ export class ResetPassword extends Component {
@autobind
handleMessageModalDismiss() {
- this.setState({ messageModal: null })
+ if (this.state.messageModal.noRetry) {
+ this.props.history.replace('/login')
+ } else {
+ this.setState({ messageModal: null })
+ }
}
render() {
const { messageModal, waitModal, binder } = this.state
+ if (api.loggedInUser) {
+ return
+ }
+
return (
@@ -132,8 +174,8 @@ export class ResetPassword extends Component {
+
)
}
}
diff --git a/website/src/Profile/ProfileForm.js b/website/src/Profile/ProfileForm.js
index 8b39616..350bdc4 100644
--- a/website/src/Profile/ProfileForm.js
+++ b/website/src/Profile/ProfileForm.js
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { Column, Button, BoundInput, BoundButton } from 'ui'
+import { Column, Row, Box, Button, BoundInput, BoundButton } from 'ui'
+import { sizeInfo, colorInfo } from 'ui/style'
import { regExpPattern } from 'regexp-pattern'
import { FormBinder } from 'react-form-binder'
import autobind from 'autobind-decorator'
@@ -90,32 +91,39 @@ export class ProfileForm extends React.Component {
}
render() {
+ const { binder } = this.state
+
return (
)
}
diff --git a/website/src/Users/Users.js b/website/src/Users/Users.js
index d7ecc25..25a055a 100644
--- a/website/src/Users/Users.js
+++ b/website/src/Users/Users.js
@@ -195,7 +195,6 @@ export class Users extends Component {
@autobind
handleRemoveModalDismiss(yes) {
if (yes) {
- // TODO: Pass the _id back from the dialog input data
const selectedUserId = this.state.selectedUser._id
const selectedIndex = this.state.users.findIndex((user) => (user._id === selectedUserId))
diff --git a/website/src/ui/Icon.js b/website/src/ui/Icon.js
index 796794e..3d2107d 100644
--- a/website/src/ui/Icon.js
+++ b/website/src/ui/Icon.js
@@ -1,14 +1,17 @@
+import Radium from 'radium'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { sizeInfo } from './style'
// See https://www.flaticon.com/packs/free-basic-ui-elements
+@Radium
export class Icon extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
margin: PropTypes.number,
+ style: PropTypes.object,
}
static defaultProps = {
@@ -33,11 +36,11 @@ export class Icon extends Component {
}
render() {
- let { size, name, margin } = this.props
+ let { size, name, margin, style } = this.props
let source = Icon.svgs[name] || Icon.svgs['placeholder']
size -= margin * 2
- return
+ return
}
}
diff --git a/website/src/ui/PanelButton.js b/website/src/ui/PanelButton.js
index 5782536..c50e9e4 100644
--- a/website/src/ui/PanelButton.js
+++ b/website/src/ui/PanelButton.js
@@ -43,7 +43,10 @@ export class PanelButton extends Component {
]}
onClick={onClick}>
-
+