Fixing last couple of auth dialogs
This commit is contained in:
@@ -43,6 +43,10 @@ export class AuthRoutes {
|
|||||||
app.route('/auth/password/send')
|
app.route('/auth/password/send')
|
||||||
.post(this.sendPasswordResetEmail)
|
.post(this.sendPasswordResetEmail)
|
||||||
|
|
||||||
|
// Confirm a password reset token is valid
|
||||||
|
app.route('/auth/password/confirm')
|
||||||
|
.post(this.confirmPasswordToken)
|
||||||
|
|
||||||
// Finish a password reset
|
// Finish a password reset
|
||||||
app.route('/auth/password/reset')
|
app.route('/auth/password/reset')
|
||||||
.post(this.resetPassword)
|
.post(this.resetPassword)
|
||||||
@@ -52,110 +56,104 @@ export class AuthRoutes {
|
|||||||
.get(passport.authenticate('bearer', { session: false }), this.whoAmI)
|
.get(passport.authenticate('bearer', { session: false }), this.whoAmI)
|
||||||
}
|
}
|
||||||
|
|
||||||
login(req, res, next) {
|
async login(req, res, next) {
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
const password = req.body.password
|
const password = req.body.password
|
||||||
|
|
||||||
if (!email || !password) {
|
|
||||||
return next(new createError.BadRequest('Must supply user name and password'))
|
|
||||||
}
|
|
||||||
|
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
|
|
||||||
// Lookup the user
|
try {
|
||||||
User.findOne({ email }).then((user) => {
|
if (!email || !password) {
|
||||||
|
createError.BadRequest('Must supply user name and password')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the user
|
||||||
|
const user = await User.findOne({ email })
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// NOTE: Don't return NotFound as that gives too much information away to hackers
|
// NOTE: Don't return NotFound as that gives too much information away to hackers
|
||||||
return Promise.reject(createError.BadRequest("Email or password incorrect"))
|
throw createError.BadRequest("Email or password incorrect")
|
||||||
} else if (user.emailToken || !user.passwordHash) {
|
} else if (user.emailToken || !user.passwordHash) {
|
||||||
return Promise.reject(createError.Forbidden("Must confirm email and set password"))
|
throw createError.Forbidden("Must confirm email and set password")
|
||||||
} else {
|
|
||||||
let cr = credential()
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
Promise.resolve(user),
|
|
||||||
cr.verify(JSON.stringify(user.passwordHash), req.body.password)
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}).then((arr) => {
|
|
||||||
const [user, isValid] = arr
|
let cr = credential()
|
||||||
|
const isValid = cr.verify(JSON.stringify(user.passwordHash), req.body.password)
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
user.loginToken = loginToken.pack(user._id.toString(), user.email)
|
user.loginToken = loginToken.pack(user._id.toString(), user.email)
|
||||||
} else {
|
} else {
|
||||||
user.loginToken = null // A bad login removes existing token for this user...
|
user.loginToken = null // A bad login removes existing token for this user...
|
||||||
}
|
}
|
||||||
return user.save()
|
|
||||||
}).then((savedUser) => {
|
const savedUser = await user.save()
|
||||||
|
|
||||||
if (savedUser.loginToken) {
|
if (savedUser.loginToken) {
|
||||||
res.set('Authorization', `Bearer ${savedUser.loginToken}`)
|
res.set('Authorization', `Bearer ${savedUser.loginToken}`)
|
||||||
res.json(savedUser.toClient())
|
res.json(savedUser.toClient())
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(createError.BadRequest('email or password incorrect'))
|
throw createError.BadRequest('email or password incorrect')
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(`${err ? err.message : ''}`))
|
next(createError.InternalServerError(err.message))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(req, res, next) {
|
async logout(req, res, next) {
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
|
|
||||||
User.findById({ _id: req.user._id }).then((user) => {
|
try {
|
||||||
if (!user) {
|
const user = await User.findById({ _id: req.user._id })
|
||||||
return next(createError.BadRequest())
|
|
||||||
|
if (user) {
|
||||||
|
user.loginToken = null
|
||||||
|
await user.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
user.loginToken = null
|
res.json({})
|
||||||
user.save().then((savedUser) => {
|
} catch(err) {
|
||||||
res.json({})
|
next(createError.InternalServerError(err.message))
|
||||||
})
|
}
|
||||||
}).catch((err) => {
|
|
||||||
next(createError.InternalServerError(`Unable to login. ${err ? err.message : ''}`))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whoAmI(req, res, next) {
|
whoAmI(req, res, next) {
|
||||||
res.json(req.user.toClient())
|
res.json(req.user.toClient())
|
||||||
}
|
}
|
||||||
|
|
||||||
sendChangeEmailEmail(req, res, next) {
|
async sendChangeEmailEmail(req, res, next) {
|
||||||
let existingEmail = req.body.existingEmail
|
let existingEmail = req.body.existingEmail
|
||||||
const newEmail = req.body.newEmail
|
const newEmail = req.body.newEmail
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
const isAdmin = !!req.user.administrator
|
const isAdmin = !!req.user.administrator
|
||||||
|
|
||||||
if (existingEmail) {
|
try {
|
||||||
if (!isAdmin) {
|
if (existingEmail) {
|
||||||
return next(createError.Forbidden('Only admins can resend change email to any user'))
|
if (!isAdmin) {
|
||||||
|
throw createError.Forbidden('Only admins can resend change email to any user')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
existingEmail = req.user.email
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
existingEmail = req.user.email
|
|
||||||
}
|
|
||||||
|
|
||||||
let promiseArray = [User.findOne({ email: existingEmail })]
|
const user = await User.findOne({ email: existingEmail })
|
||||||
|
let conflictingUser = null
|
||||||
|
|
||||||
if (newEmail) {
|
if (newEmail) {
|
||||||
promiseArray.push(User.findOne({ email: newEmail }))
|
conflictingUser = await User.findOne({ email: newEmail })
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(promiseArray).then((arr) => {
|
|
||||||
const [user, conflictingUser] = arr
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return Promise.reject(createError.NotFound(`User with email '${existingEmail}' was not found`))
|
throw createError.NotFound(`User with email '${existingEmail}' was not found`)
|
||||||
} else if (conflictingUser) {
|
} else if (conflictingUser) {
|
||||||
return Promise.reject(createError.BadRequest(`A user with '${newEmail}' already exists`))
|
throw createError.BadRequest(`A user with '${newEmail}' already exists`)
|
||||||
} else if (!isAdmin && user.emailToken && (new Date() - user.emailToken.created) < this.sendEmailDelayInSeconds) {
|
} else if (!isAdmin && user.emailToken && (new Date() - user.emailToken.created) < this.sendEmailDelayInSeconds) {
|
||||||
return Promise.reject(createError.BadRequest('Cannot request email confirmation again so soon'))
|
throw createError.BadRequest('Cannot request email confirmation again so soon')
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([Promise.resolve(user), util.promisify(crypto.randomBytes)(32)])
|
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||||
}).then((arr) => {
|
|
||||||
let [ user, buf ] = arr
|
|
||||||
|
|
||||||
user.emailToken = {
|
user.emailToken = {
|
||||||
value: urlSafeBase64.encode(buf),
|
value: urlSafeBase64.encode(buf),
|
||||||
@@ -166,8 +164,7 @@ export class AuthRoutes {
|
|||||||
user.newEmail = newEmail
|
user.newEmail = newEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.save()
|
const savedUser = await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||||
const siteUrl = url.parse(req.headers.referer)
|
const siteUrl = url.parse(req.headers.referer)
|
||||||
let msgs = []
|
let msgs = []
|
||||||
@@ -193,36 +190,41 @@ export class AuthRoutes {
|
|||||||
supportEmail: this.supportEmail
|
supportEmail: this.supportEmail
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msgs) : Promise.resolve()
|
|
||||||
}).then(() => {
|
if (this.sendEmail) {
|
||||||
|
await this.mq.request('dar-email', 'sendEmail', msgs)
|
||||||
|
}
|
||||||
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(`Unable to send change email email. ${err.message}`))
|
next(createError.InternalServerError(err.message))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmEmail(req, res, next) {
|
async confirmEmail(req, res, next) {
|
||||||
const token = req.body.emailToken
|
const token = req.body.emailToken
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
|
|
||||||
if (!token) {
|
try {
|
||||||
return next(createError.BadRequest('Invalid request parameters'))
|
if (!token) {
|
||||||
}
|
throw createError.BadRequest('Invalid request parameters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({ 'emailToken.value': token })
|
||||||
|
|
||||||
User.findOne({ 'emailToken.value': token }).then((user) => {
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return Promise.reject(createError.BadRequest(`The token was not found`))
|
throw createError.BadRequest(`The token was not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token must not be too old
|
// Token must not be too old
|
||||||
const ageInHours = (new Date() - user.emailToken.created) / 3600000
|
const ageInHours = (new Date() - user.emailToken.created) / 3600000
|
||||||
|
|
||||||
if (ageInHours > this.maxEmailTokenAgeInHours) {
|
if (ageInHours > this.maxEmailTokenAgeInHours) {
|
||||||
return Promise.reject(createError.BadRequest(`Token has expired`))
|
throw createError.BadRequest(`Token has expired`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the email token & any login token as it will become invalid
|
// Remove the email token & any login token as it will become invalid
|
||||||
@@ -235,86 +237,109 @@ export class AuthRoutes {
|
|||||||
user.newEmail = undefined
|
user.newEmail = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
let promiseArray = [ Promise.resolve(user) ]
|
let buf = null
|
||||||
|
|
||||||
|
// If user has no password, create reset token for them
|
||||||
if (!user.passwordHash) {
|
if (!user.passwordHash) {
|
||||||
// User has no password, create reset token for them
|
buf = await util.promisify(crypto.randomBytes)(32)
|
||||||
promiseArray.push(util.promisify(crypto.randomBytes)(32))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promiseArray)
|
|
||||||
}).then((arr) => {
|
|
||||||
let [ user, buf ] = arr
|
|
||||||
|
|
||||||
if (buf) {
|
|
||||||
user.passwordToken = {
|
user.passwordToken = {
|
||||||
value: urlSafeBase64.encode(buf),
|
value: urlSafeBase64.encode(buf),
|
||||||
created: new Date()
|
created: new Date()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.save()
|
const savedUser = await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
let obj = {}
|
let obj = {}
|
||||||
|
|
||||||
// Only because the user has sent us a valid email reset token can we respond with an password reset token
|
// Only because the user has sent us a valid email reset token
|
||||||
|
// can we respond with an password reset token.
|
||||||
if (savedUser.passwordToken) {
|
if (savedUser.passwordToken) {
|
||||||
obj.passwordToken = savedUser.passwordToken.value
|
obj.passwordToken = savedUser.passwordToken.value
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(obj)
|
res.json(obj)
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(`Unable to confirm set email token. ${err.message}`))
|
next(createError.InternalServerError(err.message))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPassword(req, res, next) {
|
async confirmPasswordToken(req, res, next) {
|
||||||
const token = req.body.passwordToken
|
const token = req.body.passwordToken
|
||||||
const newPassword = req.body.newPassword
|
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
let cr = credential()
|
|
||||||
|
|
||||||
if (!token || !newPassword) {
|
try {
|
||||||
return next(createError.BadRequest('Invalid request parameters'))
|
if (!token) {
|
||||||
}
|
throw createError.BadRequest('Invalid request parameters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({ 'passwordToken.value': token })
|
||||||
|
|
||||||
User.findOne({ 'passwordToken.value': token }).then((user) => {
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return Promise.reject(createError.BadRequest(`The token was not found`))
|
throw createError.BadRequest(`The token was not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token must not be too old
|
// Token must not be too old
|
||||||
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||||
|
|
||||||
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||||
return Promise.reject(createError.BadRequest(`Token has expired`))
|
throw createError.BadRequest(`Token has expired`)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({})
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof createError.HttpError) {
|
||||||
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetPassword(req, res, next) {
|
||||||
|
const token = req.body.passwordToken
|
||||||
|
const newPassword = req.body.newPassword
|
||||||
|
let User = this.db.User
|
||||||
|
let cr = credential()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!token || !newPassword) {
|
||||||
|
throw createError.BadRequest('Invalid request parameters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({ 'passwordToken.value': token })
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw createError.BadRequest(`The token was not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token must not be too old
|
||||||
|
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||||
|
|
||||||
|
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||||
|
throw createError.BadRequest(`Token has expired`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the password token & any login token
|
// Remove the password token & any login token
|
||||||
user.passwordToken = undefined
|
user.passwordToken = undefined
|
||||||
user.loginToken = undefined
|
user.loginToken = undefined
|
||||||
|
|
||||||
return Promise.all([
|
const json = await cr.hash(newPassword)
|
||||||
Promise.resolve(user),
|
|
||||||
cr.hash(newPassword)
|
|
||||||
])
|
|
||||||
}).then((arr) => {
|
|
||||||
const [user, json] = arr
|
|
||||||
user.passwordHash = JSON.parse(json)
|
user.passwordHash = JSON.parse(json)
|
||||||
return user.save()
|
await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(`Unable to confirm password reset token. ${err.message}`))
|
next(createError.InternalServerError(err.message))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(req, res, next) {
|
async changePassword(req, res, next) {
|
||||||
|
|||||||
@@ -230,6 +230,9 @@ class API extends EventEmitter {
|
|||||||
sendResetPassword(email) {
|
sendResetPassword(email) {
|
||||||
return this.post('/auth/password/send', { email })
|
return this.post('/auth/password/send', { email })
|
||||||
}
|
}
|
||||||
|
confirmResetPassword(passwordToken) {
|
||||||
|
return this.post('/auth/password/confirm', { passwordToken })
|
||||||
|
}
|
||||||
resetPassword(passwords) {
|
resetPassword(passwords) {
|
||||||
return this.post('/auth/password/reset', passwords)
|
return this.post('/auth/password/reset', passwords)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ export class App extends Component {
|
|||||||
<Route exact path='/confirm-email' component={ConfirmEmail} />
|
<Route exact path='/confirm-email' component={ConfirmEmail} />
|
||||||
<Route exact path='/reset-password' component={ResetPassword} />
|
<Route exact path='/reset-password' component={ResetPassword} />
|
||||||
<Route exact path='/forgot-password' component={ForgotPassword} />
|
<Route exact path='/forgot-password' component={ForgotPassword} />
|
||||||
<ProtectedRoute exact path='/profile' component={Profile} />
|
<ProtectedRoute exact path='/profile' render={props => (<Profile {...props} changeTitle={this.handleChangeTitle} />)} />
|
||||||
<ProtectedRoute exact admin path='/users' render={props => (<Users {...props} onChangeTitle={this.handleChangeTitle} />)} />
|
<ProtectedRoute exact admin path='/users' render={props => (<Users {...props} changeTitle={this.handleChangeTitle} />)} />
|
||||||
<ProtectedRoute exact admin path='/teams' component={Users} />
|
<ProtectedRoute exact admin path='/teams' component={Users} />
|
||||||
<ProtectedRoute exact admin path='/system' component={Users} />
|
<ProtectedRoute exact admin path='/system' component={Users} />
|
||||||
<ProtectedRoute exact admin path='/home' render={props => (<Home {...props} onChangeTitle={this.handleChangeTitle} />)} />
|
<ProtectedRoute exact admin path='/home' render={props => (<Home {...props} changeTitle={this.handleChangeTitle} />)} />
|
||||||
<DefaultRoute />
|
<DefaultRoute />
|
||||||
</Switch>
|
</Switch>
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import React from 'react'
|
|||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { MessageModal, WaitModal } from '../Modal'
|
import { MessageModal, WaitModal } from '../Modal'
|
||||||
import { Logout } from '.'
|
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
export class ConfirmEmail extends React.Component {
|
export class ConfirmEmail extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -22,7 +22,9 @@ export class ConfirmEmail extends React.Component {
|
|||||||
|
|
||||||
this.setState({ waitModal: { message: 'Validating Email...' } })
|
this.setState({ waitModal: { message: 'Validating Email...' } })
|
||||||
if (emailToken) {
|
if (emailToken) {
|
||||||
api.confirmEmail(emailToken).then((response) => {
|
api.logout().then(() => {
|
||||||
|
return api.confirmEmail(emailToken)
|
||||||
|
}).then((response) => {
|
||||||
this.setState({ waitModal: null })
|
this.setState({ waitModal: null })
|
||||||
if (response && response.passwordToken) {
|
if (response && response.passwordToken) {
|
||||||
// API will send a password reset token if this is the first time loggin on
|
// API will send a password reset token if this is the first time loggin on
|
||||||
@@ -54,10 +56,6 @@ export class ConfirmEmail extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const { messageModal, waitModal } = this.state
|
const { messageModal, waitModal } = this.state
|
||||||
|
|
||||||
if (api.loggedInUser) {
|
|
||||||
return <Logout redirect={`${window.location.pathname}${window.location.search}`} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<WaitModal
|
<WaitModal
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from 'ui'
|
|||||||
import { MessageModal, WaitModal } from '../Modal'
|
import { MessageModal, WaitModal } from '../Modal'
|
||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
import { Logout } from '.'
|
|
||||||
import headerLogo from 'images/deighton.png'
|
import headerLogo from 'images/deighton.png'
|
||||||
import { sizeInfo, colorInfo } from 'ui/style'
|
import { sizeInfo, colorInfo } from 'ui/style'
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
@@ -35,6 +34,10 @@ export class ForgotPassword extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
api.logout()
|
||||||
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -66,10 +69,6 @@ export class ForgotPassword extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { binder, waitModal, messageModal } = this.state
|
const { binder, waitModal, messageModal } = this.state
|
||||||
|
|
||||||
if (api.loggedInUser) {
|
|
||||||
return <Logout redirect={`${window.location.pathname}${window.location.search}`} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Column.Item grow />
|
<Column.Item grow />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import autobind from 'autobind-decorator'
|
|||||||
export class ProtectedRoute extends React.Component {
|
export class ProtectedRoute extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string }),
|
location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string }),
|
||||||
|
admin: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -30,11 +31,11 @@ export class ProtectedRoute extends React.Component {
|
|||||||
// The API might be in the middle of fetching the user information
|
// The API might be in the middle of fetching the user information
|
||||||
// Return something and wait for login evint to fire to re-render
|
// Return something and wait for login evint to fire to re-render
|
||||||
return <div />
|
return <div />
|
||||||
} else if (user.administrator) {
|
} else if (!this.props.admin || (this.props.admin && user.administrator)) {
|
||||||
return <Route {...this.props} />
|
return <Route {...this.props} />
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return <Redirect to={`/login?redirect=${this.props.location.pathname}${this.props.location.search}`} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <Redirect to={`/login?redirect=${this.props.location.pathname}${this.props.location.search}`} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { Component, Fragment } from 'react'
|
import React, { Component, Fragment } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Box, Text, Image, Column, Row, BoundInput, BoundButton } from 'ui'
|
import { Box, Text, Image, Column, Row, BoundInput, BoundButton } from 'ui'
|
||||||
import { Logout } from '.'
|
|
||||||
import { MessageModal, WaitModal } from '../Modal'
|
import { MessageModal, WaitModal } from '../Modal'
|
||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
@@ -47,13 +46,10 @@ export class ResetPassword extends Component {
|
|||||||
|
|
||||||
this.setState({ waitModal: { message: 'Confirming password reset...' } })
|
this.setState({ waitModal: { message: 'Confirming password reset...' } })
|
||||||
if (passwordToken) {
|
if (passwordToken) {
|
||||||
api.confirmResetPassword(passwordToken).then((response) => {
|
api.logout().then(() => {
|
||||||
this.setState({ waitModal: null })
|
return api.confirmResetPassword(passwordToken)
|
||||||
if (response && response.valid) {
|
}).then((response) => {
|
||||||
this.setState({ tokenConfirmed: true })
|
this.setState({ waitModal: null, tokenConfirmed: true })
|
||||||
} else {
|
|
||||||
this.props.history.replace('/')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
@@ -107,10 +103,6 @@ export class ResetPassword extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { messageModal, waitModal, binder } = this.state
|
const { messageModal, waitModal, binder } = this.state
|
||||||
|
|
||||||
if (api.loggedInUser) {
|
|
||||||
return <Logout redirect={`${window.location.pathname}${window.location.search}`} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Column.Item grow />
|
<Column.Item grow />
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import { sizeInfo } from 'ui/style'
|
|||||||
export class Home extends Component {
|
export class Home extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
onChangeTitle: PropTypes.func.isRequired,
|
changeTitle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.onChangeTitle('Home')
|
this.props.changeTitle('Home')
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.onChangeTitle('')
|
this.props.changeTitle('')
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bindings = {
|
static bindings = {
|
||||||
|
oldEmail: {
|
||||||
|
noValue: true,
|
||||||
|
},
|
||||||
newEmail: {
|
newEmail: {
|
||||||
isValid: (r, v) => (v !== '' && regExpPattern.email.test(v))
|
isValid: (r, v) => (v !== '' && regExpPattern.email.test(v) && v !== r.getFieldValue('oldEmail'))
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
isDisabled: (r) => (!r.allValid),
|
isDisabled: (r) => (!r.allValid),
|
||||||
@@ -26,7 +29,9 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
binder: new FormBinder({}, ChangeEmailModal.bindings)
|
binder: new FormBinder({
|
||||||
|
oldEmail: props.oldEmail,
|
||||||
|
}, ChangeEmailModal.bindings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,12 +48,7 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
@autobind
|
@autobind
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let newEmail = null
|
let newEmail = this.state.binder.getFieldValue('newEmail')
|
||||||
|
|
||||||
if (this.state.binder.anyModified && this.state.binder.allValid) {
|
|
||||||
newEmail = this.state.binder.getFieldValue('newEmail')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close(newEmail)
|
this.close(newEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +58,11 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { binder } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal dimmer='inverted' open={this.props.open} onClose={this.handleClose}
|
<Modal open={this.props.open} width={sizeInfo.modalWidth}>
|
||||||
closeOnDimmerClick={false}>
|
<form id='changeEmailForm' onSubmit={this.handleSubmit}>
|
||||||
<form id='emailForm' onSubmit={this.handleSubmit}>
|
|
||||||
<Column>
|
<Column>
|
||||||
<Column.Item height={sizeInfo.formColumnSpacing} />
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
@@ -69,6 +70,11 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
<Row.Item width={sizeInfo.formRowSpacing} />
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Row.Item grow>
|
<Row.Item grow>
|
||||||
<Column>
|
<Column>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item color='black' icon='edit'>
|
||||||
|
<Text size='large'>Change Password</Text>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<Text>{this.props.oldEmail}</Text>
|
<Text>{this.props.oldEmail}</Text>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
@@ -76,7 +82,7 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
<Column.Item>
|
<Column.Item>
|
||||||
<BoundInput label='New Email' name='newEmail'
|
<BoundInput label='New Email' name='newEmail'
|
||||||
message='Your new email address, e.g. xyz@abc.com, cannot be blank'
|
message='Your new email address, e.g. xyz@abc.com, cannot be blank'
|
||||||
binder={this.state.binder} />
|
binder={binder} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item height={sizeInfo.formColumnSpacing} />
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
@@ -87,7 +93,7 @@ export class ChangeEmailModal extends React.Component {
|
|||||||
</Row.Item>
|
</Row.Item>
|
||||||
<Row.Item width={sizeInfo.formRowSpacing} />
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Row.Item>
|
<Row.Item>
|
||||||
<BoundButton submit='emailForm' name='submit' binder={this.state.binder} text='OK' />
|
<BoundButton submit='changeEmailForm' name='submit' binder={binder} text='OK' />
|
||||||
</Row.Item>
|
</Row.Item>
|
||||||
</Row>
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
import { Modal, Button, Icon, Column, Row, Text, BoundInput, BoundButton } from 'ui'
|
import { Modal, Button, Column, Row, Text, BoundInput, BoundButton } from 'ui'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo } from 'ui/style'
|
||||||
|
|
||||||
@@ -21,8 +21,7 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
isValid: (r, v) => (v !== '' && v !== r.fields.oldPassword.value)
|
isValid: (r, v) => (v !== '' && v !== r.fields.oldPassword.value)
|
||||||
},
|
},
|
||||||
reenteredNewPassword: {
|
reenteredNewPassword: {
|
||||||
alwaysGet: true,
|
isValid: (r, v) => (v !== '' && v === r.getFieldValue('newPassword')),
|
||||||
isValid: (r, v) => (v !== '' && v === r.fields.newPassword.value)
|
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
isDisabled: (r) => (!r.allValid),
|
isDisabled: (r) => (!r.allValid),
|
||||||
@@ -51,10 +50,11 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let passwords = null
|
let passwords = null
|
||||||
|
const { binder } = this.state
|
||||||
|
|
||||||
if (this.state.binder.allValid) {
|
if (binder.allValid) {
|
||||||
const oldPassword = this.state.binder.getField('oldPassword').value
|
const oldPassword = binder.getFieldValue('oldPassword')
|
||||||
const newPassword = this.state.binder.getField('newPassword').value
|
const newPassword = binder.getFieldValue('newPassword')
|
||||||
passwords = { oldPassword, newPassword }
|
passwords = { oldPassword, newPassword }
|
||||||
}
|
}
|
||||||
this.close(passwords)
|
this.close(passwords)
|
||||||
@@ -66,41 +66,57 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { binder } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal dimmer='inverted' open={this.props.open} width={sizeInfo.modalWidth}>
|
<Modal open={this.props.open} width={sizeInfo.modalWidth}>
|
||||||
<form id='passwordForm' onSubmit={this.handleSubmit}>
|
<form id='changePasswordForm' onSubmit={this.handleSubmit}>
|
||||||
<Column.Item color='black' icon='edit'>
|
<Row>
|
||||||
<Text size='large'>Change Password</Text>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
</Column.Item>
|
<Row.Item grow>
|
||||||
<Column.Item>
|
<Column>
|
||||||
<Column>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item color='black' icon='edit'>
|
||||||
<BoundInput label='Current Password' password name='oldPassword'
|
<Text size='large'>Change Password</Text>
|
||||||
message='Your existing password, cannot be blank'
|
</Column.Item>
|
||||||
binder={this.state.binder} />
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
</Column.Item>
|
<Column.Item>
|
||||||
<Column.Item>
|
<Column>
|
||||||
<BoundInput label='New Password' password name='newPassword'
|
<Column.Item>
|
||||||
message='A new password, cannot be blank or the same as your old password'
|
<BoundInput label='Current Password' password name='oldPassword'
|
||||||
binder={this.state.binder} />
|
message='Your existing password, cannot be blank'
|
||||||
</Column.Item>
|
binder={binder} />
|
||||||
<Column.Item>
|
</Column.Item>
|
||||||
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
|
<Column.Item>
|
||||||
message='The new password again, must match and cannot be blank'
|
<BoundInput label='New Password' password name='newPassword'
|
||||||
binder={this.state.binder} />
|
message='A new password, cannot be blank or the same as your old password'
|
||||||
</Column.Item>
|
binder={binder} />
|
||||||
</Column>
|
</Column.Item>
|
||||||
</Column.Item>
|
<Column.Item>
|
||||||
<Column.Item>
|
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
|
||||||
<Row>
|
message='The new password again, must match and cannot be blank'
|
||||||
<BoundButton primary submit form='passwordForm' name='submit' binder={this.state.binder}>
|
binder={binder} />
|
||||||
<Icon name='checkmark' /> OK
|
</Column.Item>
|
||||||
</BoundButton>
|
</Column>
|
||||||
<Button color='red' onClick={this.handleClick}>
|
</Column.Item>
|
||||||
<Icon name='close' /> Cancel
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
</Button>
|
<Column.Item>
|
||||||
</Row>
|
<Row>
|
||||||
</Column.Item>
|
<Row.Item grow />
|
||||||
|
<Row.Item>
|
||||||
|
<Button onClick={this.handleClick} text='Cancel' />
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
<Row.Item>
|
||||||
|
<BoundButton text='Submit' submit='changePasswordForm' name='submit' binder={binder} />
|
||||||
|
</Row.Item>
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
</Column>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
</Row>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ import { api } from 'src/API'
|
|||||||
import { WaitModal, MessageModal, ChangePasswordModal, ChangeEmailModal } from '../Modal'
|
import { WaitModal, MessageModal, ChangePasswordModal, ChangeEmailModal } from '../Modal'
|
||||||
import { Column, Row } from 'ui'
|
import { Column, Row } from 'ui'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo } from 'ui/style'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
export class Profile extends Component {
|
export class Profile extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
changeTitle: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -23,11 +28,11 @@ export class Profile extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
api.addListener('newProfileImage', this.handleNewProfileImage)
|
this.props.changeTitle('Profile')
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
api.removeListener('newProfileImage', this.handleNewProfileImage)
|
this.props.changeTitle('')
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -41,7 +46,11 @@ export class Profile extends Component {
|
|||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
messageModal: { title: 'Update Error...', message: `Unable to save the profile changes. ${error.message}` }
|
messageModal: {
|
||||||
|
icon: 'hand',
|
||||||
|
message: 'Unable to save the profile changes.',
|
||||||
|
detail: error.message,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -56,21 +65,6 @@ export class Profile extends Component {
|
|||||||
this.setState({ changePasswordModal: true })
|
this.setState({ changePasswordModal: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
|
||||||
handleProgress(uploadData) {
|
|
||||||
if (this.state.progressModal) {
|
|
||||||
this.setState({ uploadPercent: Math.round(uploadData.uploadedChunks / uploadData.numberOfChunks * 100) })
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
handleUploadCancel(result) {
|
|
||||||
this.setState({ progressModal: null })
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleChangePasswordDismiss(passwords) {
|
handleChangePasswordDismiss(passwords) {
|
||||||
this.setState({ changePasswordModal: false })
|
this.setState({ changePasswordModal: false })
|
||||||
@@ -85,8 +79,9 @@ export class Profile extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
waitModal: false,
|
waitModal: false,
|
||||||
messageModal: {
|
messageModal: {
|
||||||
title: 'Changing Password Error',
|
icon: 'hand',
|
||||||
message: `Unable to change password. ${error.message}.`
|
message: 'Unable to change password',
|
||||||
|
detail: error.message,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -94,8 +89,8 @@ export class Profile extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleChangeEmail() {
|
handleChangeEmail(oldEmail) {
|
||||||
this.setState({ changeEmailModal: {} })
|
this.setState({ changeEmailModal: { oldEmail } })
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -111,32 +106,32 @@ export class Profile extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
messageModal: {
|
messageModal: {
|
||||||
error: false,
|
icon: 'thumb',
|
||||||
title: 'Email Change Requested...',
|
|
||||||
message: `An email has been sent to '${newEmail}' with a link that you need to click on to finish changing your email.`
|
message: `An email has been sent to '${newEmail}' with a link that you need to click on to finish changing your email.`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: true,
|
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
messageModal: {
|
messageModal: {
|
||||||
error: true,
|
icon: 'hand',
|
||||||
title: 'Email Change Error...',
|
message: 'Unable to request email change.',
|
||||||
message: `Unable to request email change. ${error ? error.message : ''}`
|
detail: error.message
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { messageModal, waitModal, changeEmailModal, changePasswordModal } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Column.Item grow />
|
<Column.Item grow />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<Row>
|
<Row>
|
||||||
<Row.Item grow />
|
<Row.Item grow />
|
||||||
<Row.Item width={sizeInfo.modalWidth}>
|
<Row.Item width={sizeInfo.profileWidth}>
|
||||||
<ProfileForm
|
<ProfileForm
|
||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
onSaved={this.handleSaved}
|
onSaved={this.handleSaved}
|
||||||
@@ -149,16 +144,25 @@ export class Profile extends Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<MessageModal error open={!!this.state.messageModal}
|
<MessageModal
|
||||||
title={this.state.messageModal ? this.state.messageModal.title : ''}
|
open={!!messageModal}
|
||||||
message={this.state.messageModal ? this.state.messageModal.message : ''}
|
icon={messageModal ? messageModal.icon : ''}
|
||||||
|
title={messageModal ? messageModal.title : ''}
|
||||||
|
message={messageModal ? messageModal.message : ''}
|
||||||
onDismiss={this.handleMessageModalDismiss} />
|
onDismiss={this.handleMessageModalDismiss} />
|
||||||
|
|
||||||
<ChangeEmailModal open={!!this.state.changeEmailModal} onDismiss={this.handleChangeEmailDismiss} />
|
<ChangeEmailModal
|
||||||
|
open={!!changeEmailModal}
|
||||||
|
oldEmail={changeEmailModal ? changeEmailModal.oldEmail : ''}
|
||||||
|
onDismiss={this.handleChangeEmailDismiss} />
|
||||||
|
|
||||||
<WaitModal active={!!this.state.waitModal} message={this.state.waitModal ? this.state.waitModal.message : ''} />
|
<WaitModal
|
||||||
|
active={!!waitModal}
|
||||||
|
message={waitModal ? waitModal.message : ''} />
|
||||||
|
|
||||||
<ChangePasswordModal open={!!this.state.changePasswordModal} onDismiss={this.handleChangePasswordDismiss} />
|
<ChangePasswordModal
|
||||||
|
open={!!changePasswordModal}
|
||||||
|
onDismiss={this.handleChangePasswordDismiss} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item grow />
|
<Column.Item grow />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Column, Row, Box, Button, BoundInput, BoundButton } from 'ui'
|
import { Column, Row, Box, Button, BoundInput, BoundButton } from 'ui'
|
||||||
import { sizeInfo, colorInfo } from 'ui/style'
|
import { sizeInfo, colorInfo } from 'ui/style'
|
||||||
import { regExpPattern } from 'regexp-pattern'
|
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
@@ -26,36 +25,6 @@ export class ProfileForm extends React.Component {
|
|||||||
lastName: {
|
lastName: {
|
||||||
isValid: (r, v) => (v !== '')
|
isValid: (r, v) => (v !== '')
|
||||||
},
|
},
|
||||||
zip: {
|
|
||||||
isValid: (r, v) => (v === '' || regExpPattern.zip.test(v))
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
isValid: (r, v) => (v === '' || regExpPattern.state.test(v))
|
|
||||||
},
|
|
||||||
city: {
|
|
||||||
isValid: true
|
|
||||||
},
|
|
||||||
address1: {
|
|
||||||
isValid: true
|
|
||||||
},
|
|
||||||
address2: {
|
|
||||||
isValid: true
|
|
||||||
},
|
|
||||||
homePhone: {
|
|
||||||
isValid: (r, v) => (v === '' || regExpPattern.phone.test(v))
|
|
||||||
},
|
|
||||||
cellPhone: {
|
|
||||||
isValid: (r, v) => (v === '' || regExpPattern.phone.test(v))
|
|
||||||
},
|
|
||||||
dateOfBirth: {
|
|
||||||
isValid: true
|
|
||||||
},
|
|
||||||
dateOfHire: {
|
|
||||||
isValid: true
|
|
||||||
},
|
|
||||||
ssn: {
|
|
||||||
isValid: (r, v) => (v === '' || regExpPattern.ssn.test(v))
|
|
||||||
},
|
|
||||||
save: {
|
save: {
|
||||||
noValue: true,
|
noValue: true,
|
||||||
isDisabled: (r) => (!r.anyModified || !r.allValid)
|
isDisabled: (r) => (!r.anyModified || !r.allValid)
|
||||||
@@ -90,38 +59,59 @@ export class ProfileForm extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleChangeEmail() {
|
||||||
|
this.props.onChangeEmail(this.state.binder.getFieldValue('email'))
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder } = this.state
|
const { binder } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.handleSubmit} id='profileForm'>
|
<form onSubmit={this.handleSubmit} id='profileForm'>
|
||||||
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
|
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }}
|
||||||
|
radius={sizeInfo.formBoxRadius}>
|
||||||
<Row>
|
<Row>
|
||||||
<Row.Item width={sizeInfo.formRowSpacing} />
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Row.Item>
|
<Row.Item grow>
|
||||||
<Column stackable>
|
<Column>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<BoundInput label='First Name' name='firstName'
|
<BoundInput label='First Name' name='firstName'
|
||||||
binder={binder} />
|
message='First name is required' binder={binder} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<BoundInput label='Last Name' name='lastName'
|
<BoundInput label='Last Name' name='lastName'
|
||||||
binder={binder} />
|
binder={binder} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<BoundInput label='Email' name='email' message='Required. Must be a valid email address.'
|
<BoundInput label='Email' name='email'
|
||||||
|
message='Required. Must be a valid email address.'
|
||||||
binder={binder} />
|
binder={binder} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Button text={'Change Email'} label=' '
|
<Column.Item height={sizeInfo.buttonHeight}>
|
||||||
onClick={this.props.onChangeEmail} />
|
<Row>
|
||||||
<Button text={'Change Password'} label=' '
|
<Row.Item>
|
||||||
onClick={this.props.onChangePassword} />
|
<Button text={'Change Email'} label=' '
|
||||||
<BoundButton submit size='medium' text='Save' label=' ' name='save'
|
width={sizeInfo.buttonWideWidth} onClick={this.handleChangeEmail} />
|
||||||
binder={binder} />
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
<Row.Item>
|
||||||
|
<Button text={'Change Password'} label=' '
|
||||||
|
width={sizeInfo.buttonWideWidth} onClick={this.props.onChangePassword} />
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item grow />
|
||||||
|
<Row.Item>
|
||||||
|
<BoundButton submit='profileForm' size='medium' text='Save' label=' ' name='save'
|
||||||
|
binder={binder} />
|
||||||
|
</Row.Item>
|
||||||
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
</Column>
|
</Column>
|
||||||
</Row.Item>
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
</Row>
|
</Row>
|
||||||
</Box>
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -149,12 +149,12 @@ export class UserForm extends React.Component {
|
|||||||
<Row>
|
<Row>
|
||||||
<Row.Item>
|
<Row.Item>
|
||||||
<BoundButton text='Change Email' name='changeEmail' binder={binder}
|
<BoundButton text='Change Email' name='changeEmail' binder={binder}
|
||||||
width={sizeInfo.formButtonLarge} onClick={this.handleChangeEmail} />
|
width={sizeInfo.buttonWideWidth} onClick={this.handleChangeEmail} />
|
||||||
</Row.Item>
|
</Row.Item>
|
||||||
<Row.Item grow />
|
<Row.Item grow />
|
||||||
<Row.Item>
|
<Row.Item>
|
||||||
<BoundButton text='Resend Confirmation Email' name='resendEmail' binder={binder}
|
<BoundButton text='Resend Confirmation Email' name='resendEmail' binder={binder}
|
||||||
width={sizeInfo.formButtonLarge} onClick={this.handleResendEmail} />
|
width={sizeInfo.buttonWideWidth} onClick={this.handleResendEmail} />
|
||||||
</Row.Item>
|
</Row.Item>
|
||||||
</Row>
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { sizeInfo, colorInfo } from 'ui/style'
|
|||||||
|
|
||||||
export class Users extends Component {
|
export class Users extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChangeTitle: PropTypes.func.isRequired,
|
changeTitle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -28,7 +28,7 @@ export class Users extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.onChangeTitle('Users')
|
this.props.changeTitle('Users')
|
||||||
|
|
||||||
api.listUsers().then((list) => {
|
api.listUsers().then((list) => {
|
||||||
list.items.sort((userA, userB) => (userA.lastName.localeCompare(userB.lastName)))
|
list.items.sort((userA, userB) => (userA.lastName.localeCompare(userB.lastName)))
|
||||||
@@ -45,7 +45,7 @@ export class Users extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.onChangeTitle('')
|
this.props.changeTitle('')
|
||||||
}
|
}
|
||||||
|
|
||||||
removeUnfinishedNewUser() {
|
removeUnfinishedNewUser() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class Button extends Component {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
visible: true,
|
visible: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
width: sizeInfo.buttonWidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
static style = {
|
static style = {
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ const sizeInfo = {
|
|||||||
|
|
||||||
buttonHeight: 40,
|
buttonHeight: 40,
|
||||||
buttonPadding: '0 15px 0 15px',
|
buttonPadding: '0 15px 0 15px',
|
||||||
|
buttonWidth: 125,
|
||||||
|
buttonWideWidth: 225,
|
||||||
|
|
||||||
checkboxSize: 25,
|
checkboxSize: 25,
|
||||||
checkmarkBorder: '0 3px 3px 0',
|
checkmarkBorder: '0 3px 3px 0',
|
||||||
@@ -72,7 +74,6 @@ const sizeInfo = {
|
|||||||
formRowSpacing: 20,
|
formRowSpacing: 20,
|
||||||
formBoundIcon: 30,
|
formBoundIcon: 30,
|
||||||
formBoundIconMargin: 0,
|
formBoundIconMargin: 0,
|
||||||
formButtonLarge: 225,
|
|
||||||
|
|
||||||
listBorderWidth: 1,
|
listBorderWidth: 1,
|
||||||
listTopBottomGap: 10,
|
listTopBottomGap: 10,
|
||||||
@@ -83,6 +84,8 @@ const sizeInfo = {
|
|||||||
modalShadowWidth: 25,
|
modalShadowWidth: 25,
|
||||||
modalMessageIcon: 150,
|
modalMessageIcon: 150,
|
||||||
|
|
||||||
|
profileWidth: '65vw',
|
||||||
|
|
||||||
inputPadding: 5,
|
inputPadding: 5,
|
||||||
inputBorderRadius: 5,
|
inputBorderRadius: 5,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user