New assets, fixed list box scrolling, header text, etc..
@@ -90,13 +90,13 @@ export class AuthRoutes {
|
||||
res.set('Authorization', `Bearer ${savedUser.loginToken}`)
|
||||
res.json(savedUser.toClient())
|
||||
} else {
|
||||
return Promise.reject(createError.BadRequest('Email or password incorrect'))
|
||||
return Promise.reject(createError.BadRequest('email or password incorrect'))
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(`Unable to login. ${err ? err.message : ''}`))
|
||||
next(createError.InternalServerError(`${err ? err.message : ''}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -126,11 +126,10 @@ export class AuthRoutes {
|
||||
let existingEmail = req.body.existingEmail
|
||||
const newEmail = req.body.newEmail
|
||||
let User = this.db.User
|
||||
const role = req.user.role
|
||||
const isAdminOrExec = (role === 'executive' || role === 'administrator')
|
||||
const isAdmin = !!req.user.administrator
|
||||
|
||||
if (existingEmail) {
|
||||
if (!isAdminOrExec) {
|
||||
if (!isAdmin) {
|
||||
return next(createError.Forbidden('Only admins can resend change email to any user'))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -24,9 +24,6 @@ export class UserRoutes {
|
||||
.post(passport.authenticate('bearer', { session: false }), this.createUser)
|
||||
.put(passport.authenticate('bearer', { session: false }), this.updateUser)
|
||||
|
||||
app.route('/users/brokers')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.listBrokerUsers)
|
||||
|
||||
app.route('/users/:_id([a-f0-9]{24})')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getUser)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteUser)
|
||||
@@ -45,9 +42,9 @@ export class UserRoutes {
|
||||
const User = this.db.User
|
||||
const limit = req.params.limit || 20
|
||||
const skip = req.params.skip || 0
|
||||
const role = req.user.role
|
||||
const isAdmin = !!req.user.administrator
|
||||
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
if (!isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
@@ -76,41 +73,14 @@ export class UserRoutes {
|
||||
})
|
||||
}
|
||||
|
||||
listBrokerUsers(req, res, next) {
|
||||
let User = this.db.User
|
||||
const role = req.user.role
|
||||
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
let users = []
|
||||
let cursor = User.find({ role: 'broker' })
|
||||
.select('_id firstName lastName thumbnailImageId t12 aum numHouseholds cellPhone').cursor()
|
||||
|
||||
cursor.on('data', (doc) => {
|
||||
users.push(doc)
|
||||
})
|
||||
cursor.on('end', () => {
|
||||
res.json({
|
||||
total: users.length,
|
||||
offset: 0,
|
||||
count: users.length,
|
||||
items: users
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
}
|
||||
|
||||
getUser(req, res, next) {
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const isSelf = (_id === req.user._id)
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// User can see themselves, otherwise must be super user
|
||||
if (!isSelf && role !== 'executive' && role !== 'administrator') {
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
@@ -130,9 +100,9 @@ export class UserRoutes {
|
||||
}
|
||||
|
||||
createUser(req, res, next) {
|
||||
const role = req.user.role
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
if (!isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
@@ -168,7 +138,7 @@ export class UserRoutes {
|
||||
}
|
||||
|
||||
updateUser(req, res, next) {
|
||||
const role = req.user.role
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// Do this here because Mongoose will add it automatically otherwise
|
||||
if (!req.body._id) {
|
||||
@@ -178,7 +148,7 @@ export class UserRoutes {
|
||||
const isSelf = (req.body._id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && role !== 'executive' && role !== 'administrator') {
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
@@ -191,8 +161,8 @@ export class UserRoutes {
|
||||
return next(createError.BadRequest('Invalid data'))
|
||||
}
|
||||
|
||||
if (isSelf && userUpdates.role && userUpdates.role !== req.user.role) {
|
||||
return next(createError.BadRequest('Cannot modify own role'))
|
||||
if (isSelf && !isAdmin) {
|
||||
return next(createError.BadRequest('Cannot modify own administrator level'))
|
||||
}
|
||||
|
||||
User.findById(userUpdates._id).then((foundUser) => {
|
||||
@@ -213,7 +183,7 @@ export class UserRoutes {
|
||||
}
|
||||
|
||||
setImage(req, res, next) {
|
||||
const role = req.user.role
|
||||
const isAdmin = req.user.administrator
|
||||
const { _id, imageId } = req.body
|
||||
|
||||
if (!_id || !imageId) {
|
||||
@@ -223,7 +193,7 @@ export class UserRoutes {
|
||||
const isSelf = (_id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && role !== 'executive' && role !== 'administrator') {
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
@@ -304,9 +274,9 @@ export class UserRoutes {
|
||||
}
|
||||
|
||||
deleteUser(req, res, next) {
|
||||
const role = req.user.role
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ new DB().connect(mongoUri).then((db) => {
|
||||
|
||||
const User = db.User
|
||||
let user = new User({
|
||||
role: "administrator"
|
||||
administrator: true,
|
||||
})
|
||||
user.firstName = readlineSync.question('First name? ')
|
||||
user.lastName = readlineSync.question('Last name? ')
|
||||
|
||||
@@ -31,14 +31,11 @@ export let userSchema = new Schema({
|
||||
},
|
||||
firstName: { type: String, required: true },
|
||||
lastName: { type: String, required: true },
|
||||
role: { type: String, required: true, enum: {
|
||||
values: [ 'administrator', 'normal'],
|
||||
message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
|
||||
}},
|
||||
administrator: { type: Boolean, required: true },
|
||||
}, { timestamps: true, id: false })
|
||||
|
||||
userSchema.methods.toClient = function(authUser) {
|
||||
if (authUser === undefined) {
|
||||
if (!authUser) {
|
||||
authUser = this
|
||||
}
|
||||
|
||||
@@ -50,23 +47,7 @@ userSchema.methods.toClient = function(authUser) {
|
||||
thumbnailImageId: this.thumbnailImageId,
|
||||
firstName: this.firstName,
|
||||
lastName: this.lastName,
|
||||
role: this.role
|
||||
}
|
||||
|
||||
if ((authUser.role === 'administrator' || authUser.role === 'executive') || authUser._id.equals(this._id)) {
|
||||
user.zip = this.zip
|
||||
user.state = this.state
|
||||
user.city = this.city
|
||||
user.address1 = this.address1
|
||||
user.address2 = this.address2
|
||||
user.homePhone = this.homePhone
|
||||
user.cellPhone = this.cellPhone
|
||||
user.ssn = this.ssn
|
||||
user.dateOfBirth = this.dateOfBirth
|
||||
user.dateOfHire = this.dateOfHire
|
||||
user.numHouseholds = this.numHouseholds
|
||||
user.t12 = this.t12
|
||||
user.aum = this.aum
|
||||
administrator: this.administrator
|
||||
}
|
||||
|
||||
return user
|
||||
|
||||
6
website/package-lock.json
generated
@@ -10214,9 +10214,9 @@
|
||||
}
|
||||
},
|
||||
"react-form-binder": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-1.0.5.tgz",
|
||||
"integrity": "sha512-R5oWObsNFnOCviaV5MmFDP0e+RcpPhjzVpMgvDqW3CCUdZp7XYjfyqmv1IXYlvsqavodPmxBLSFhQwxglyzJkg==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-1.1.0.tgz",
|
||||
"integrity": "sha512-48bPtvfHTsgi8CBbKx3FHNI2H6xwCbVHkZn+wjmTUcqfdug/M7qev6+ssvwU16USSr+IHu8bUJVD3Q0xYT8o1Q==",
|
||||
"requires": {
|
||||
"eventemitter3": "2.0.3",
|
||||
"prop-types": "15.6.0",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"radium": "^0.22.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-form-binder": "^1.0.5",
|
||||
"react-form-binder": "^1.1.0",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"regexp-pattern": "^1.0.4",
|
||||
"socket.io-client": "^2.0.3"
|
||||
|
||||
@@ -3,15 +3,20 @@ import { Login, Logout, ResetPassword, ForgotPassword, ConfirmEmail, ProtectedRo
|
||||
import { Home } from './Home'
|
||||
import { Profile } from './Profile'
|
||||
import { Users } from './Users'
|
||||
import { HeaderButton, Column, Row, Text, Box } from 'ui'
|
||||
import { HeaderButton, HeaderText, Column, Row, Text, Box } from 'ui'
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
||||
import logoImage from 'images/logo.png'
|
||||
import { versionInfo } from './version'
|
||||
import { sizeInfo, colorInfo } from 'ui/style'
|
||||
import { api } from 'src/API'
|
||||
import { reactAutoBind } from 'auto-bind2'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export class App extends Component {
|
||||
static propTypes = {
|
||||
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
reactAutoBind(this)
|
||||
@@ -35,7 +40,16 @@ export class App extends Component {
|
||||
}
|
||||
|
||||
handleLogout() {
|
||||
api.logout()
|
||||
// We have to use window here because App does not have history in it's props
|
||||
window.location.replace('/logout')
|
||||
}
|
||||
|
||||
handleHome() {
|
||||
window.location.replace('/')
|
||||
}
|
||||
|
||||
handleChangeTitle(title) {
|
||||
this.setState({ title })
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -45,11 +59,13 @@ export class App extends Component {
|
||||
|
||||
if (loggedInUser) {
|
||||
headerButtonsLeft = (
|
||||
<HeaderButton image={logoImage} />
|
||||
<Fragment>
|
||||
<HeaderButton image={logoImage} onClick={this.handleHome} />
|
||||
<HeaderText text={this.state.title} />
|
||||
</Fragment>
|
||||
)
|
||||
headerButtonsRight = (
|
||||
<Fragment>
|
||||
<HeaderButton icon='profile' />
|
||||
<HeaderButton icon='logout' onClick={this.handleLogout} />
|
||||
</Fragment>
|
||||
)
|
||||
@@ -73,7 +89,7 @@ export class App extends Component {
|
||||
<Route path='/reset-password' component={ResetPassword} />
|
||||
<Route path='/forgot-password' component={ForgotPassword} />
|
||||
<ProtectedRoute path='/profile' component={Profile} />
|
||||
<ProtectedRoute path='/users' component={Users} />
|
||||
<ProtectedRoute path='/users' render={props => (<Users {...props} onChangeTitle={this.handleChangeTitle} />)} />
|
||||
<ProtectedRoute path='/teams' component={Users} />
|
||||
<ProtectedRoute path='/system' component={Users} />
|
||||
<ProtectedRoute path='/logout' component={Logout} />
|
||||
|
||||
@@ -62,8 +62,8 @@ export class Login extends Component {
|
||||
api.login(obj.email, obj.password, obj.rememberMe).then((user) => {
|
||||
this.setState({ waitModal: false })
|
||||
if (this.props.history) {
|
||||
const landing = user.role === 'broker' ? '/broker-dashboard' : 'dashboard'
|
||||
let url = new URLSearchParams(window.location.search).get('redirect') || landing
|
||||
let url = new URLSearchParams(window.location.search).get('redirect') || '/'
|
||||
|
||||
try {
|
||||
this.props.history.replace(url)
|
||||
} catch (error) {
|
||||
@@ -73,7 +73,7 @@ export class Login extends Component {
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
waitModal: false,
|
||||
messageModal: { icon: 'hold', message: `Unable to login`, detail: error.message }
|
||||
messageModal: { icon: 'hand', message: `Unable to login`, detail: error.message }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ export class Logout extends React.Component {
|
||||
static propTypes = {
|
||||
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
}
|
||||
|
||||
componentDidMount(event) {
|
||||
api.logout().then(() => {
|
||||
if (this.props.history) {
|
||||
this.props.history.push('/')
|
||||
this.props.history.replace('/login')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { api } from 'src/API'
|
||||
|
||||
export class ProtectedRoute extends React.Component {
|
||||
static propTypes = {
|
||||
roles: PropTypes.array,
|
||||
location: PropTypes.shape({
|
||||
pathname: PropTypes.string,
|
||||
search: PropTypes.string,
|
||||
@@ -41,17 +40,11 @@ export class ProtectedRoute extends React.Component {
|
||||
// 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 <div />
|
||||
}
|
||||
|
||||
let roles = this.props.roles
|
||||
|
||||
if (roles && roles.includes(user.role)) {
|
||||
} else if (user.administrator) {
|
||||
return <Route {...this.props} />
|
||||
} else {
|
||||
return <Redirect to='/' />
|
||||
}
|
||||
} 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}`} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export class MessageModal extends React.Component {
|
||||
<Row>
|
||||
<Row.Item grow />
|
||||
<Row.Item>
|
||||
<Button submit='messageModal' onClick={this.onSubmit}>OK</Button>
|
||||
<Button submit='messageModal' text='OK' onClick={this.onSubmit} />
|
||||
</Row.Item>
|
||||
<Row.Item grow />
|
||||
</Row>
|
||||
|
||||
@@ -1,39 +1,68 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Modal, Button, Column, Text, Icon } from 'ui'
|
||||
import { Modal, Button, Row, Column, Text, Icon } from 'ui'
|
||||
import { sizeInfo } from 'ui/style'
|
||||
import { reactAutoBind } from 'auto-bind2'
|
||||
|
||||
export class YesNoMessageModal extends React.Component {
|
||||
static propTypes = {
|
||||
open: PropTypes.bool,
|
||||
title: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
question: PropTypes.string.isRequired,
|
||||
onDismiss: PropTypes.func
|
||||
}
|
||||
|
||||
onDismiss(yes) {
|
||||
return () => (this.props.onDismiss ? this.props.onDismiss(yes) : null)
|
||||
constructor(props) {
|
||||
super(props)
|
||||
reactAutoBind(this)
|
||||
}
|
||||
|
||||
onSubmit(e, yes) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const { onDismiss } = this.props
|
||||
|
||||
if (onDismiss) {
|
||||
onDismiss(yes)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { open, question } = this.props
|
||||
|
||||
return (
|
||||
<Modal open={this.props.open} onClose={this.onDismiss(false)}
|
||||
closeOnDimmerClick={false} className='yes-no-modal'>
|
||||
<Column>
|
||||
<Column.Item color='orange'>
|
||||
<Text>{this.props.title}</Text>
|
||||
</Column.Item>
|
||||
<Column.Item>
|
||||
<Text>{this.props.message}</Text>
|
||||
</Column.Item>
|
||||
<Column.Item>
|
||||
<Button negative onClick={this.onDismiss(false)}>
|
||||
<Icon name='remove' /> No
|
||||
</Button>
|
||||
<Button positive onClick={this.onDismiss(true)}>
|
||||
<Icon name='checkmark' /> Yes
|
||||
</Button>
|
||||
</Column.Item>
|
||||
</Column>
|
||||
<Modal open={open} width={400}>
|
||||
<form onSubmit={this.onSubmit} id='messageModal'>
|
||||
<Row>
|
||||
<Row.Item>
|
||||
<Icon name='help' size={150} />
|
||||
</Row.Item>
|
||||
<Row.Item grow>
|
||||
<Column height='100%'>
|
||||
<Column.Item height={15} />
|
||||
<Column.Item grow>
|
||||
<Text width='100%' align='center'>{question}</Text>
|
||||
</Column.Item>
|
||||
<Column.Item height={15} />
|
||||
<Column.Item height={sizeInfo.buttonHeight}>
|
||||
<Row>
|
||||
<Row.Item grow />
|
||||
<Row.Item>
|
||||
<Button submit='messageModal' text='Yes' onClick={(e) => this.onSubmit(e, true)} />
|
||||
</Row.Item>
|
||||
<Row.Item width={10} />
|
||||
<Row.Item>
|
||||
<Button submit='messageModal' text='No' onClick={(e) => this.onSubmit(e, true)} />
|
||||
</Row.Item>
|
||||
<Row.Item grow />
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item height={15} />
|
||||
</Column>
|
||||
</Row.Item>
|
||||
<Row.Item width={10} />
|
||||
</Row>
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,9 +54,6 @@ export class ProfileForm extends React.Component {
|
||||
ssn: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.ssn.test(v))
|
||||
},
|
||||
role: {
|
||||
isDisabled: true
|
||||
},
|
||||
save: {
|
||||
nonValue: true,
|
||||
isDisabled: (r) => (!r.anyModified || !r.allValid)
|
||||
|
||||
@@ -12,7 +12,7 @@ export class UserForm extends React.Component {
|
||||
user: PropTypes.object,
|
||||
onSave: PropTypes.func,
|
||||
onRemove: PropTypes.func,
|
||||
onAnyModified: PropTypes.func,
|
||||
onModifiedChanged: PropTypes.func,
|
||||
onChangeEmail: PropTypes.func,
|
||||
onResendEmail: PropTypes.func
|
||||
}
|
||||
@@ -20,18 +20,18 @@ export class UserForm extends React.Component {
|
||||
static bindings = {
|
||||
email: {
|
||||
isValid: (r, v) => (regExpPattern.email.test(v)),
|
||||
isDisabled: (r) => (!!r._id)
|
||||
isDisabled: (r) => (r._id)
|
||||
},
|
||||
emailValidated: {
|
||||
isDisabled: (r) => (!!r._id === false)
|
||||
isDisabled: (r) => (!r._id)
|
||||
},
|
||||
changeEmail: {
|
||||
nonValue: true,
|
||||
isDisabled: (r) => (!!r._id === false)
|
||||
isDisabled: (r) => (!r._id)
|
||||
},
|
||||
resendEmail: {
|
||||
nonValue: true,
|
||||
isDisabled: (r) => (!!r._id === false)
|
||||
isDisabled: (r) => (!r._id || !!r.getFieldValue('emailValidated'))
|
||||
},
|
||||
firstName: {
|
||||
isValid: (r, v) => (v !== '')
|
||||
@@ -40,15 +40,13 @@ export class UserForm extends React.Component {
|
||||
isValid: (r, v) => (v !== '')
|
||||
},
|
||||
administrator: {
|
||||
isValid: (r, v) => (v !== ''),
|
||||
isDisabled: (r) => (api.loggedInUser._id === r._id) // Adding a new user
|
||||
},
|
||||
project: {
|
||||
isValid: (r, v) => (v !== '' || v === '')
|
||||
isValid: (r, v) => true,
|
||||
isDisabled: (r) => (api.loggedInUser._id === r._id), // Adding a new user
|
||||
alwaysGet: true,
|
||||
},
|
||||
remove: {
|
||||
nonValue: true,
|
||||
isVisible: (r) => (!!r._id),
|
||||
isVisible: (r) => (r._id),
|
||||
isDisabled: (r) => (api.loggedInUser._id === r._id)
|
||||
},
|
||||
reset: {
|
||||
@@ -57,7 +55,7 @@ export class UserForm extends React.Component {
|
||||
},
|
||||
submit: {
|
||||
nonValue: true,
|
||||
isDisabled: (r) => (!r.anyModified && !r.allValid)
|
||||
isDisabled: (r) => (!r.anyModified || !r.allValid),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,33 +63,35 @@ export class UserForm extends React.Component {
|
||||
super(props)
|
||||
reactAutoBind(this)
|
||||
this.state = {
|
||||
binder: new FormBinder(this.props.user, UserForm.bindings, this.props.onAnyModified)
|
||||
binder: new FormBinder(this.props.user, UserForm.bindings, this.props.onModifiedChanged)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.user !== this.props.user) {
|
||||
this.setState({
|
||||
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onAnyModified)
|
||||
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onModifiedChanged)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
let obj = this.state.binder.getValues()
|
||||
|
||||
let obj = this.state.binder.getModifiedFieldValues()
|
||||
|
||||
if (obj) {
|
||||
this.props.onSave(obj)
|
||||
}
|
||||
}
|
||||
|
||||
handleReset() {
|
||||
const { user, onAnyModified } = this.props
|
||||
const { user, onModifiedChanged } = this.props
|
||||
|
||||
this.setState({ binder: new FormBinder(user, UserForm.bindings, onAnyModified) })
|
||||
this.setState({ binder: new FormBinder(user, UserForm.bindings, onModifiedChanged) })
|
||||
|
||||
if (onAnyModified) {
|
||||
onAnyModified(false)
|
||||
if (onModifiedChanged) {
|
||||
onModifiedChanged(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export class UserForm extends React.Component {
|
||||
const { binder } = this.state
|
||||
|
||||
return (
|
||||
<form id='userForm' onSubmit={this.handleSubmit}>
|
||||
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='userForm' onSubmit={this.handleSubmit}>
|
||||
<Column>
|
||||
<Column.Item height={20} />
|
||||
<Row>
|
||||
@@ -118,18 +118,17 @@ export class UserForm extends React.Component {
|
||||
<Column.Item>
|
||||
<Row>
|
||||
<Row.Item grow>
|
||||
<BoundInput label='First Name' name='firstName' binder={binder} />
|
||||
<BoundInput label='First Name' name='firstName' message='Must not be empty' binder={binder} />
|
||||
</Row.Item>
|
||||
<Row.Item width={20} />
|
||||
<Row.Item grow>
|
||||
<BoundInput label='Last Name' name='lastName' binder={binder} />
|
||||
</Row.Item>
|
||||
<Row.Item width={20} />
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item>
|
||||
<Row>
|
||||
<Row.Item>
|
||||
<Row.Item grow>
|
||||
<BoundInput label='Email' name='email' message='Must be a valid email address. Required.' binder={binder} />
|
||||
</Row.Item>
|
||||
<Row.Item width={10} />
|
||||
@@ -138,17 +137,21 @@ export class UserForm extends React.Component {
|
||||
</Row.Item>
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item height={20} />
|
||||
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||
<Row>
|
||||
<Row.Item>
|
||||
<BoundButton text='Change Email' name='changeEmail' binder={binder} onClick={this.handleChangeEmail} />
|
||||
<BoundButton text='Change Email' name='changeEmail' binder={binder}
|
||||
width={220} onClick={this.handleChangeEmail} />
|
||||
</Row.Item>
|
||||
<Row.Item grow />
|
||||
<Row.Item>
|
||||
<BoundButton text='Resend Confirmation Email' name='resendEmail' binder={binder} onClick={this.handleResendEmail} />
|
||||
<BoundButton text='Resend Confirmation Email' name='resendEmail' binder={binder}
|
||||
width={220} onClick={this.handleResendEmail} />
|
||||
</Row.Item>
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item height={20} />
|
||||
<Column.Item>
|
||||
<Row>
|
||||
<Row.Item>
|
||||
@@ -157,6 +160,7 @@ export class UserForm extends React.Component {
|
||||
<Row.Item grow />
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item height={20} />
|
||||
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||
<Row>
|
||||
<Row.Item>
|
||||
@@ -166,6 +170,7 @@ export class UserForm extends React.Component {
|
||||
<Row.Item>
|
||||
<BoundButton text='Remove' name='remove' binder={binder} onClick={this.props.onRemove} />
|
||||
</Row.Item>
|
||||
<Row.Item width={20} />
|
||||
<Row.Item>
|
||||
<BoundButton submit='userForm' text={binder._id ? 'Save' : 'Add'} name='submit' binder={binder} />
|
||||
</Row.Item>
|
||||
|
||||
@@ -17,7 +17,6 @@ export class UserList extends React.Component {
|
||||
this.state = {
|
||||
users: null
|
||||
}
|
||||
this.filterUsers = this.filterUsers.bind(this)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
@@ -26,16 +25,9 @@ export class UserList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
filterUsers(e, data) {
|
||||
e.preventDefault()
|
||||
let users = this.props.users
|
||||
if (data.value !== 'all') {
|
||||
users = users.filter(user => data.value === user.role)
|
||||
}
|
||||
this.setState({ users })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedUser, selectionModified } = this.props
|
||||
|
||||
return (
|
||||
<Column fillParent>
|
||||
<Column.Item grow>
|
||||
@@ -43,13 +35,13 @@ export class UserList extends React.Component {
|
||||
{
|
||||
this.state.users
|
||||
? this.state.users.map((user, index) =>
|
||||
(<List.Item className='user-list-item' key={user._id || '0'} onClick={this.props.onUserListClick}
|
||||
active={user === this.props.selectedUser} data-index={index}>
|
||||
<List.Icon name='profile' size={30} />
|
||||
(<List.Item className='user-list-item' key={user._id || '0'} onClick={(e) => (this.props.onUserListClick(e, index))}
|
||||
active={user === this.props.selectedUser}>
|
||||
<List.Icon name={user.administrator ? 'admin' : 'profile'} size={25} />
|
||||
<List.Text>
|
||||
{ user._id ? user.firstName + ' ' + user.lastName : '[New User]' }
|
||||
</List.Text>
|
||||
{ user === this.props.selectedUser && this.props.selectionModified ? <List.Icon className='user-update' name='edit' /> : null }
|
||||
{ user === selectedUser && selectionModified ? <List.Icon name='edit' size={25} /> : null }
|
||||
</List.Item>)
|
||||
)
|
||||
: null
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { reactAutoBind } from 'auto-bind2'
|
||||
import { UserList } from './UserList'
|
||||
import { UserForm } from './UserForm'
|
||||
@@ -9,10 +10,15 @@ import { YesNoMessageModal, MessageModal, ChangeEmailModal, WaitModal } from '..
|
||||
import { sizeInfo, colorInfo } from 'ui/style'
|
||||
|
||||
export class Users extends Component {
|
||||
constructor() {
|
||||
super()
|
||||
static propTypes = {
|
||||
onChangeTitle: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
reactAutoBind(this)
|
||||
this.state = {
|
||||
modified: false,
|
||||
selectedUser: null,
|
||||
users: [],
|
||||
yesNoModal: null,
|
||||
@@ -23,15 +29,17 @@ export class Users extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onChangeTitle('Users')
|
||||
|
||||
api.listUsers().then((list) => {
|
||||
list.items.sort((userA, userB) => (userA.lastName.localeCompare(userB.lastName)))
|
||||
this.setState({ users: list.items })
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'User List Error',
|
||||
message: `Unable to get the list of users. ${error.message}`
|
||||
icon: 'hand',
|
||||
message: 'Unable to get the list of users.',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -45,15 +53,14 @@ export class Users extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleUserListClick(e) {
|
||||
let user = this.state.users[Number(e.currentTarget.getAttribute('data-index'))]
|
||||
handleUserListClick(e, index) {
|
||||
let user = this.state.users[index]
|
||||
|
||||
if (this.state.modified) {
|
||||
this.nextSelectedUser = user
|
||||
this.setState({
|
||||
yesNoModal: {
|
||||
title: 'User Modified',
|
||||
message: 'This user has been modified. Are you sure you would like to navigate away?',
|
||||
question: 'This user has been modified. Are you sure you would like to navigate away?',
|
||||
onDismiss: this.handleModifiedModalDismiss
|
||||
}
|
||||
})
|
||||
@@ -77,9 +84,9 @@ export class Users extends Component {
|
||||
this.setState({
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'Update Error',
|
||||
message: `Unable to save the user changes. ${error.message}`
|
||||
icon: 'hand',
|
||||
message: 'Unable to save the user changes',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -88,7 +95,9 @@ export class Users extends Component {
|
||||
api.createUser(user).then((createdUser) => {
|
||||
this.setState({
|
||||
waitModal: false,
|
||||
users: this.state.users.map((user) => (!user._id ? createdUser : user)),
|
||||
users: this.state.users.map((user) => (!user._id ? createdUser : user)).sort((a, b) => (
|
||||
a.lastName < b.lastName ? -1 : a.lastName > b.lastName : 0
|
||||
)),
|
||||
modified: false,
|
||||
selectedUser: createdUser
|
||||
})
|
||||
@@ -96,9 +105,9 @@ export class Users extends Component {
|
||||
this.setState({
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'Create Error',
|
||||
message: `Unable to create the user. ${error.message}`
|
||||
icon: 'hand',
|
||||
message: 'Unable to create the user.',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -117,9 +126,8 @@ export class Users extends Component {
|
||||
this.setState({
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: false,
|
||||
title: "We've Re-Sent the Email...",
|
||||
message: `An email has been sent to '${this.state.selectedUser.email}'. This user will need to follow that email's included link to verify their account.`
|
||||
icon: 'thumb',
|
||||
message: `An email has been sent to '${this.state.selectedUser.email}' with further instructions.`
|
||||
}
|
||||
})
|
||||
}).catch((error) => {
|
||||
@@ -127,9 +135,9 @@ export class Users extends Component {
|
||||
error: true,
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'Email Change Error...',
|
||||
message: `Unable to request email change. ${error ? error.message : ''}`
|
||||
error: 'hand',
|
||||
message: 'Unable to request email change.',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -147,9 +155,8 @@ export class Users extends Component {
|
||||
this.setState({
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: false,
|
||||
title: 'Email Change Requested...',
|
||||
message: `An email has been sent to '${newEmail}'. This user will need to follow that email's included link to finish changing their email.`
|
||||
icon: 'hand',
|
||||
message: `An email has been sent to '${newEmail}' to confirm this email.`
|
||||
}
|
||||
})
|
||||
}).catch((error) => {
|
||||
@@ -157,9 +164,9 @@ export class Users extends Component {
|
||||
error: true,
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'Email Change Error...',
|
||||
message: `Unable to request email change. ${error ? error.message : ''}`
|
||||
icon: 'hand',
|
||||
message: 'Unable to request email change.',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -168,8 +175,7 @@ export class Users extends Component {
|
||||
handleRemove() {
|
||||
this.setState({
|
||||
yesNoModal: {
|
||||
title: 'Permanently Delete User?',
|
||||
message: 'You are about to delete this user from the system. This includes references to this user in Projects, Packages, and so on. Are you sure you want to remove this user?',
|
||||
question: 'Are you sure you want to remove this user? This will also remove them from any teams they belong to.',
|
||||
onDismiss: this.handleRemoveModalDismiss
|
||||
}
|
||||
})
|
||||
@@ -193,9 +199,9 @@ export class Users extends Component {
|
||||
this.setState({
|
||||
waitModal: null,
|
||||
messageModal: {
|
||||
error: true,
|
||||
title: 'Remove Error',
|
||||
message: `Unable to remove the user. ${error.message}`
|
||||
icon: 'hand',
|
||||
message: 'Unable to remove the user.',
|
||||
detail: error.message,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -237,7 +243,7 @@ export class Users extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { messageModal } = this.state
|
||||
const { messageModal, yesNoModal } = this.state
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -268,17 +274,15 @@ export class Users extends Component {
|
||||
<Column.Item height={20}>
|
||||
<ChangeEmailModal open={!!this.state.changeEmailModal} onDismiss={this.handleChangeEmailDismiss} />
|
||||
|
||||
<YesNoMessageModal open={!!this.state.yesNoModal}
|
||||
title={this.state.yesNoModal ? this.state.yesNoModal.title : ''}
|
||||
message={this.state.yesNoModal ? this.state.yesNoModal.message : ''}
|
||||
onDismiss={this.state.yesNoModal ? this.state.yesNoModal.onDismiss : null} />
|
||||
<YesNoMessageModal open={!!yesNoModal}
|
||||
question={yesNoModal ? yesNoModal.question : ''}
|
||||
onDismiss={yesNoModal ? yesNoModal.onDismiss : null} />
|
||||
|
||||
<MessageModal
|
||||
open={!!messageModal}
|
||||
icon={messageModal ? messageModal.icon : ''}
|
||||
error={messageModal ? messageModal.error : false}
|
||||
title={messageModal ? messageModal.title : ''}
|
||||
message={messageModal ? messageModal.message : ''}
|
||||
detail={messageModal ? messageModal.detail : null}
|
||||
onDismiss={this.handleMessageModalDismiss} />
|
||||
|
||||
<WaitModal active={!!this.state.waitModal} message={this.state.waitModal ? this.state.waitModal.message : ''} />
|
||||
|
||||
15
website/src/assets/icons/admin.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="55px" height="52px" viewBox="0 0 55 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>admin</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Home" transform="translate(-251.000000, -479.000000)" fill="#000000" fill-rule="nonzero">
|
||||
<g id="admin" transform="translate(251.000000, 479.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
17
website/src/assets/icons/confirmed.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>confirmed</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Users" transform="translate(-922.000000, -206.000000)" fill="#03A603" fill-rule="nonzero">
|
||||
<g id="Right-Side" transform="translate(365.000000, 68.000000)">
|
||||
<g id="confirmed" transform="translate(557.000000, 138.000000)">
|
||||
<path d="M18.0703125,0 C8.233875,0 0,7.94735156 0,17.9296875 C0,27.8256094 8.15688281,36 18.0703125,36 C28.0638281,36 36,27.7553672 36,17.9296875 C36,8.01991406 27.9809297,0 18.0703125,0 Z M18.0703125,33.9046875 C9.2694375,33.9046875 2.0953125,26.7305625 2.0953125,17.9296875 C2.0953125,9.20636719 9.2694375,2.0953125 18.0703125,2.0953125 C26.7936328,2.0953125 33.9046875,9.20636719 33.9046875,17.9296875 C33.9046875,26.7305625 26.7936328,33.9046875 18.0703125,33.9046875 Z" id="Shape"></path>
|
||||
<path d="M29.0675728,10.8852775 C27.8269756,9.70747145 25.8174203,9.70344523 24.5722283,10.8819894 L16.1857915,18.7199158 L12.4311294,15.1556969 C11.1879874,13.9755422 9.1756752,13.975408 7.93246252,15.1556969 C6.68924985,16.3357845 6.68910847,18.2458274 7.93246252,19.4261163 L13.9306615,25.1201431 C15.1677242,26.2943255 17.3359063,26.2922453 18.570707,25.1201431 L29.0675021,15.1556969 C30.3107855,13.975408 30.3108562,12.0654994 29.0675728,10.8852775 Z M27.5679701,13.7321567 L17.0711749,23.6966028 C16.6571477,24.0896298 15.8442915,24.0896298 15.4301936,23.6966028 L9.43199459,18.0025761 C9.01761393,17.6092135 9.01747255,16.9726668 9.43206528,16.579103 C9.84630456,16.1857404 10.517146,16.1856733 10.9315973,16.579103 L15.4301936,20.8495223 C15.8419587,21.2405363 16.5089122,21.2428849 16.9238584,20.855092 L26.0625001,12.3141861 C26.0644794,12.3123743 26.0664587,12.3104954 26.0683673,12.3086165 C26.4826773,11.915321 27.1535187,11.9151197 27.5678994,12.3086165 L27.5679701,12.3086165 C27.9824214,12.7021803 27.9824214,13.3387271 27.5679701,13.7321567 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
14
website/src/assets/icons/edit.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="23px" height="23px" viewBox="0 0 23 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>edit</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Users" transform="translate(-238.000000, -86.000000)" fill="#000000" fill-rule="nonzero">
|
||||
<g id="edit" transform="translate(238.000000, 86.000000)">
|
||||
<path d="M21.8175546,1.18243252 C20.2409756,-0.394144173 17.6757106,-0.394144173 16.0991317,1.18243252 L2.97331247,14.3082331 C2.95579293,14.3253932 2.93917182,14.3436764 2.92349408,14.362858 C2.81586121,14.4942993 2.98526169,14.1363172 0.10212935,21.1383284 C-0.105724273,21.6431151 0.00941055218,22.2186087 0.3953795,22.604622 C0.781662902,22.9909498 1.35733703,23.1055454 1.86167518,22.8978717 C8.80529788,20.0387318 8.54915322,20.1723742 8.69178025,20.0266478 L21.8176444,6.90080231 C23.3941335,5.32427055 23.3941335,2.75900921 21.8175546,1.18243252 Z M1.34839757,21.6515604 L2.01553268,20.0313197 L2.96864059,20.9844262 L1.34839757,21.6515604 Z M4.31881316,20.4284736 L2.57148609,18.6811939 L3.68339291,15.9808077 L7.01915829,19.3165684 L4.31881316,20.4284736 Z M8.21053195,18.6017721 L4.39819015,14.7894358 L13.2398978,5.94774068 L17.0521947,9.76003213 L8.21053195,18.6017721 Z M18.0053026,8.8069705 L14.1930057,4.99467905 L15.1461136,4.04157249 L18.9584105,7.85390887 L18.0053026,8.8069705 Z M20.8645365,5.94783053 L19.9114735,6.90089216 L16.0991766,3.08860071 L17.0522396,2.13553907 C18.1033222,1.084458 19.8134989,1.08450292 20.8645814,2.13553907 C21.9155292,3.18657523 21.9155292,4.89674945 20.8645365,5.94783053 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
18
website/src/assets/icons/help.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>help</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Users" transform="translate(-922.000000, -536.000000)" fill="#F5A623" fill-rule="nonzero">
|
||||
<g id="Right-Side" transform="translate(365.000000, 68.000000)">
|
||||
<g id="help" transform="translate(557.000000, 468.000000)">
|
||||
<path d="M18,0 C8.0748,0 0,8.07469091 0,17.9998909 C0,27.9250909 8.0748,36 18,36 C27.9252,36 36,27.9250909 36,17.9998909 C36,8.07469091 27.9252,0 18,0 Z M18,32.7272727 C9.87938182,32.7272727 3.27272727,26.1206182 3.27272727,17.9998909 C3.27272727,9.87927273 9.87938182,3.27272727 18,3.27272727 C26.1206182,3.27272727 32.7272727,9.87927273 32.7272727,17.9998909 C32.7272727,26.1206182 26.1206182,32.7272727 18,32.7272727 Z" id="Shape"></path>
|
||||
<path d="M17.5135135,25.2972973 C16.4404989,25.2972973 15.5675676,26.1698595 15.5675676,27.2423676 C15.5675676,28.3158486 16.4404989,29.1891892 17.5135135,29.1891892 C18.5865281,29.1891892 19.4594595,28.3158486 19.4594595,27.2423676 C19.4594595,26.1698595 18.5865281,25.2972973 17.5135135,25.2972973 Z" id="Shape"></path>
|
||||
<path d="M17.5134605,6.81081081 C14.2944355,6.81081081 11.6756757,9.55559537 11.6756757,12.9293097 C11.6756757,13.8504649 12.3881854,14.5972633 13.2670465,14.5972633 C14.1459076,14.5972633 14.8584174,13.8504649 14.8584174,12.9293097 C14.8584174,11.3950147 16.0493993,10.1467181 17.5134605,10.1467181 C18.9775216,10.1467181 20.1686097,11.3950147 20.1686097,12.9293097 C20.1686097,14.463827 18.9775216,15.7121236 17.5134605,15.7121236 C16.6345994,15.7121236 15.9220896,16.458922 15.9220896,17.3800772 L15.9220896,20.7104247 C15.9220896,21.6315799 16.6345994,22.3783784 17.5134605,22.3783784 C18.3923215,22.3783784 19.1048313,21.6315799 19.1048313,20.7104247 L19.1048313,18.8151846 C21.5525718,18.0870672 23.3513514,15.7250224 23.3513514,12.9294208 C23.3513514,9.55559537 20.7324854,6.81081081 17.5134605,6.81081081 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
18
website/src/assets/icons/warning.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="39px" height="33px" viewBox="0 0 39 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>warning</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Users" transform="translate(-853.000000, -537.000000)" fill="#F5A623" fill-rule="nonzero">
|
||||
<g id="Right-Side" transform="translate(365.000000, 68.000000)">
|
||||
<g id="warning" transform="translate(488.000000, 469.000000)">
|
||||
<path d="M19.4999611,7 C17.5700586,7 16,8.45799185 16,10.2499819 L16,16.7500181 C16,18.5420804 17.5700586,20 19.4999611,20 C21.4298637,20 23,18.5420081 23,16.7500181 L23,10.2499819 C22.9999222,8.45799185 21.4298637,7 19.4999611,7 Z M20.6666148,16.7500903 C20.6666148,17.3474444 20.143262,17.8334176 19.4999611,17.8334176 C18.8566603,17.8334176 18.3333074,17.3474444 18.3333074,16.7500903 L18.3333074,10.2500542 C18.3333074,9.65270007 18.8566603,9.16672686 19.4999611,9.16672686 C20.143262,9.16672686 20.6666148,9.65270007 20.6666148,10.2500542 L20.6666148,16.7500903 Z" id="Shape"></path>
|
||||
<path d="M19.5,22 C17.570076,22 16,23.5701538 16,25.5 C16,27.4298462 17.5701538,29 19.5,29 C21.4298462,29 23,27.4298462 23,25.5 C23,23.5701538 21.4300018,22 19.5,22 Z M19.5,26.6666667 C18.856692,26.6666667 18.3333333,26.143308 18.3333333,25.5 C18.3333333,24.856692 18.856692,24.3333333 19.5,24.3333333 C20.143308,24.3333333 20.6666667,24.856692 20.6666667,25.5 C20.6666667,26.143308 20.1433858,26.6666667 19.5,26.6666667 Z" id="Shape"></path>
|
||||
<path d="M38.3441753,26.3784639 L23.4689883,2.13326503 C23.4672363,2.1303478 23.4654082,2.12743057 23.4635801,2.12451333 C21.6931217,-0.700972973 17.407322,-0.717455341 15.6227718,2.13078539 L0.656788148,26.3770782 C-1.16729513,29.2936551 1.02340246,33 4.57749698,33 L34.4225524,33 C37.9774086,33 40.1660496,29.2938739 38.3441753,26.3784639 Z M34.4225524,30.8123668 L4.57749698,30.8123668 C2.80109718,30.8123668 1.70361558,28.9586838 2.61820897,27.499192 C2.61958006,27.4971499 2.62079881,27.4950349 2.62209373,27.4929199 L17.5857922,3.24998194 C18.4722021,1.83548845 20.6121693,1.83322759 21.5017022,3.24589781 L36.3764322,27.4903673 C36.3781842,27.4932846 36.3800123,27.4962018 36.3818404,27.499119 C37.2958244,28.9575898 36.2002471,30.8123668 34.4225524,30.8123668 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -9,7 +9,8 @@ export default class BoundButton extends React.Component {
|
||||
text: PropTypes.string,
|
||||
binder: PropTypes.object.isRequired,
|
||||
submit: PropTypes.string,
|
||||
onClick: PropTypes.func
|
||||
onClick: PropTypes.func,
|
||||
width: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
@@ -39,11 +40,12 @@ export default class BoundButton extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, text, submit, onClick } = this.props
|
||||
const { name, text, submit, width, onClick } = this.props
|
||||
const { visible, disabled } = this.state
|
||||
|
||||
return (
|
||||
<Button disabled={disabled} submit={submit} onClick={onClick} name={name} visible={visible} text={text} />
|
||||
<Button disabled={disabled} submit={submit} onClick={onClick}
|
||||
name={name} visible={visible} text={text} width={width} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ export default class BoundEmailIcon extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
// const { value } = this.state
|
||||
const { value } = this.state
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text> </Text>
|
||||
<br />
|
||||
<Icon name='mail' size={30} margin={0} />
|
||||
<Icon name={value ? 'confirmed' : 'warning'} size={30} margin={0} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ export default class BoundInput extends React.Component {
|
||||
name={name}
|
||||
onChange={this.handleChange}
|
||||
placeholder={placeholder} />
|
||||
<br />
|
||||
<Text size='small' color='alert' hidden={valid}>{message}</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,25 +6,27 @@ import React, { Component } from 'react'
|
||||
|
||||
class Box extends Component {
|
||||
static propTypes = {
|
||||
borderTop: PropTypes.string,
|
||||
borderBottom: PropTypes.string,
|
||||
borderRight: PropTypes.string,
|
||||
borderLeft: PropTypes.string,
|
||||
border: PropTypes.string,
|
||||
borderTop: PropTypes.object,
|
||||
borderBottom: PropTypes.object,
|
||||
borderRight: PropTypes.object,
|
||||
borderLeft: PropTypes.object,
|
||||
border: PropTypes.object,
|
||||
radius: PropTypes.number,
|
||||
color: PropTypes.string,
|
||||
background: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, color, borderTop, borderBottom, borderLeft, borderRight, border, radius } = this.props
|
||||
const { children, background, borderTop, borderBottom, borderLeft,
|
||||
borderRight, border, radius } = this.props
|
||||
|
||||
return (
|
||||
<div style={[
|
||||
{ height: '100%', width: '100%' },
|
||||
color && { backgroundColor: color },
|
||||
background && { backgroundColor: background },
|
||||
radius && { borderRadius: radius },
|
||||
border ? { border } : { borderTop, borderBottom, borderLeft, borderRight }]}>
|
||||
border ? { border } : { borderTop, borderBottom, borderLeft, borderRight },
|
||||
]}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Radium from 'radium'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import { colorInfo, sizeInfo } from './style'
|
||||
import { Text } from '.'
|
||||
import { fontInfo, colorInfo, sizeInfo } from './style'
|
||||
|
||||
class Button extends Component {
|
||||
static propTypes = {
|
||||
@@ -22,7 +21,11 @@ class Button extends Component {
|
||||
|
||||
static style = {
|
||||
button: {
|
||||
fontSize: fontInfo.size.medium,
|
||||
fontFamily: fontInfo.family,
|
||||
color: fontInfo.color.inverse,
|
||||
height: sizeInfo.buttonHeight,
|
||||
borderWidth: 0,
|
||||
borderRadius: 10,
|
||||
background: colorInfo.buttonBackgroundHover,
|
||||
verticalAlign: 'middle',
|
||||
@@ -35,7 +38,6 @@ class Button extends Component {
|
||||
background: colorInfo.disabledButtonBackground,
|
||||
},
|
||||
':active': {
|
||||
borderWidth: 0,
|
||||
background: colorInfo.buttonBackgroundActive,
|
||||
}
|
||||
}
|
||||
@@ -45,12 +47,10 @@ class Button extends Component {
|
||||
const { name, submit, visible, disabled, width, text, onClick } = this.props
|
||||
|
||||
return (
|
||||
<button name={name} type={!visible ? 'hidden' : submit ? 'submit' : 'button'}
|
||||
<input name={name} type={!visible ? 'hidden' : submit ? 'submit' : 'button'}
|
||||
disabled={disabled} form={submit}
|
||||
style={[Button.style.button, { width, minWidth: sizeInfo.minButtonWidth }]}
|
||||
onClick={onClick}>
|
||||
<Text color='inverse'>{text}</Text>
|
||||
</button>
|
||||
onClick={onClick} value={text} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
35
website/src/ui/HeaderText.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import Radium from 'radium'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import { sizeInfo, fontInfo } from 'ui/style'
|
||||
|
||||
class HeaderText extends Component {
|
||||
static propTypes = {
|
||||
text: PropTypes.string,
|
||||
}
|
||||
|
||||
static style = {
|
||||
position: 'relative',
|
||||
top: 3,
|
||||
display: 'inline-block',
|
||||
fontSize: fontInfo.size.huge,
|
||||
fontFamily: fontInfo.family,
|
||||
color: fontInfo.color.normal,
|
||||
textAlign: 'left',
|
||||
background: 'transparent',
|
||||
verticalAlign: 'middle',
|
||||
borderWidth: 0,
|
||||
paddingLeft: 10,
|
||||
}
|
||||
|
||||
render() {
|
||||
const height = sizeInfo.headerHeight - sizeInfo.headerBorderWidth
|
||||
const { text } = this.props
|
||||
|
||||
return (
|
||||
<div style={[{ height }, HeaderText.style]}>{text}</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Radium(HeaderText)
|
||||
@@ -19,10 +19,15 @@ export default class Icon extends Component {
|
||||
static svgs = {
|
||||
logout: require('icons/logout.svg'),
|
||||
profile: require('icons/profile.svg'),
|
||||
hold: require('icons/hold.svg'),
|
||||
admin: require('icons/admin.svg'),
|
||||
hand: require('icons/hand.svg'),
|
||||
users: require('icons/users.svg'),
|
||||
teams: require('icons/teams.svg'),
|
||||
system: require('icons/system.svg'),
|
||||
confirmed: require('icons/confirmed.svg'),
|
||||
help: require('icons/help.svg'),
|
||||
warning: require('icons/warning.svg'),
|
||||
edit: require('icons/edit.svg'),
|
||||
placeholder: require('icons/placeholder.svg'),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import Radium from 'radium'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
|
||||
// See https://stackoverflow.com/a/6891562/576235 for why we wrap the <input /> element
|
||||
|
||||
class Input extends Component {
|
||||
static propTypes = {
|
||||
password: PropTypes.bool,
|
||||
@@ -35,7 +37,11 @@ class Input extends Component {
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
outline: 'none',
|
||||
}
|
||||
':disabled': {
|
||||
color: '#AAAAAA',
|
||||
background: '#ffffff'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -11,7 +11,6 @@ class List extends Component {
|
||||
|
||||
render() {
|
||||
const { children } = this.props
|
||||
const listGapItem = (<div style={{ background: 'transparent', height: sizeInfo.listTopBottomGap }} />)
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
@@ -21,12 +20,16 @@ class List extends Component {
|
||||
fontSize: fontInfo.size.large,
|
||||
fontFamily: fontInfo.family,
|
||||
}}>
|
||||
<Box border={`${sizeInfo.listBorderWidth}px solid ${colorInfo.listBorder}`} radius={sizeInfo.formBoxRadius}>
|
||||
{listGapItem}
|
||||
<div style={{ overflow: scroll }}>
|
||||
<Box
|
||||
border={`${sizeInfo.listBorderWidth}px solid ${colorInfo.listBorder}`}
|
||||
radius={sizeInfo.formBoxRadius}>
|
||||
<div style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
overflow: 'scroll'
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
{listGapItem}
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
@@ -93,7 +96,8 @@ List.Text = Radium(class ListText extends Component {
|
||||
color: fontInfo.color.normal,
|
||||
align: 'left',
|
||||
verticalAlign: 'middle',
|
||||
textAlign: 'left'
|
||||
textAlign: 'left',
|
||||
cursor: 'default',
|
||||
}}>{children}</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export { default as Anime } from './Anime'
|
||||
export { default as Box } from './Box'
|
||||
export { default as Button } from './Button'
|
||||
export { default as HeaderButton } from './HeaderButton'
|
||||
export { default as HeaderText } from './HeaderText'
|
||||
export { default as PanelButton } from './PanelButton'
|
||||
export { default as Checkbox } from './Checkbox'
|
||||
export { default as Input } from './Input'
|
||||
|
||||