Basic UI elements in place

This commit is contained in:
John Lyon-Smith
2018-02-27 12:16:27 -08:00
parent 5faa4600f5
commit 73b5cf6caa
49 changed files with 525 additions and 937 deletions

View File

@@ -1,5 +1,5 @@
{
"expoServerPort": 19000,
"packagerPort": 19001,
"packagerPid": 76299
"packagerPid": 12109
}

View File

@@ -2596,6 +2596,16 @@
"sha.js": "2.4.9"
}
},
"create-react-class": {
"version": "15.6.3",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1"
}
},
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@@ -10198,6 +10208,42 @@
"prop-types": "15.6.0"
}
},
"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==",
"requires": {
"eventemitter3": "2.0.3",
"prop-types": "15.6.0",
"react": "15.6.2",
"react-dom": "15.6.2"
},
"dependencies": {
"react": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz",
"integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=",
"requires": {
"create-react-class": "15.6.3",
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
}
},
"react-dom": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz",
"integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
}
}
}
},
"react-router": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz",

View File

@@ -9,6 +9,7 @@
"radium": "^0.22.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-form-binder": "^1.0.5",
"react-router-dom": "^4.1.1",
"regexp-pattern": "^1.0.4",
"socket.io-client": "^2.0.3"

View File

@@ -3,24 +3,30 @@ import { Login, Logout, ResetPassword, ForgotPassword, ConfirmEmail, ProtectedRo
import { Home } from './Home'
import { Profile } from './Profile'
import { Users } from './Users'
import { Column, Row, Image, Text, Icon, Box } from 'ui'
import { HeaderButton, Column, Row, Image, 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 } from 'ui/style'
export class App extends React.Component {
render() {
const height = sizeInfo.headerHeight - sizeInfo.headerBorderWidth
return (
<Column minHeight='100vh'>
<Column.Item>
<Box color='#FAFAFA' borderBottom='1px solid #B2B2B2'>
<Column.Item height={sizeInfo.headerHeight - sizeInfo.headerBorderWidth}>
<Box color='#FAFAFA' borderBottom={`${sizeInfo.headerBorderWidth}px solid #B2B2B2`}>
<Row minWidth='100vw'>
<Row.Item>
<Image source={logoImage} width={50} height={50} margin={5} />
<Image source={logoImage}
height={height}
margin={sizeInfo.headerMargin} />
</Row.Item>
<Row.Item grow>&nbsp;</Row.Item>
<Row.Item>
<Icon name='logout' />
<HeaderButton icon='profile' size={height} />
<HeaderButton icon='logout' size={height} />
</Row.Item>
</Row>
</Box>
@@ -43,7 +49,7 @@ export class App extends React.Component {
</Column.Item>
<Column.Item grow />
<Column.Item>
<Box color='#FAFAFA' borderTop='1px solid #B2B2B2'>
<Box color='#FAFAFA' borderTop={`${sizeInfo.headerBorderWidth}px solid #B2B2B2`}>
<Text tone='dimmed' margin={10}>{'v' + versionInfo.version} {versionInfo.copyright}</Text>
</Box>
</Column.Item>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { api } from '../helpers'
import PropTypes from 'prop-types'
import { MessageDialog, WaitDialog } from '../Dialog'
import { MessageModal, WaitModal } from '../Modal'
export class ConfirmEmail extends React.Component {
static propTypes = {
@@ -10,18 +10,18 @@ export class ConfirmEmail extends React.Component {
constructor() {
super()
this.state = {
waitDialog: null,
messageDialog: null
waitModal: null,
messageModal: null
}
this.handleMessageDialogDismiss = this.handleMessageDialogDismiss.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
}
componentDidMount(props) {
let emailToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('email-token')
this.setState({ waitDialog: { message: 'Validating Email...' } })
this.setState({ waitModal: { message: 'Validating Email...' } })
if (emailToken) {
api.confirmEmail(emailToken).then((response) => {
this.setState({ waitDialog: null })
this.setState({ waitModal: null })
if (response && response.passwordToken) {
// API will send a password reset token if this is the first time loggin on
this.props.history.replace(`/reset-password?password-token=${response.passwordToken}`)
@@ -35,8 +35,8 @@ export class ConfirmEmail extends React.Component {
? 'This email address may have already been confirmed.'
: `Please contact ${supportEmail} to request a new user invitation`
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
title: 'Error Verifying Email...',
message: `We couldn't complete that request. ${message}`
}
@@ -47,21 +47,21 @@ export class ConfirmEmail extends React.Component {
}
}
handleMessageDialogDismiss() {
this.setState({ messageDialog: null })
handleMessageModalDismiss() {
this.setState({ messageModal: null })
this.props.history.replace('/login')
}
render() {
return (
<div>
<WaitDialog active={!!this.state.waitDialog}
message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
<WaitModal active={!!this.state.waitModal}
message={this.state.waitModal ? this.state.waitModal.message : ''} />
<MessageDialog error open={!!this.state.messageDialog}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal error open={!!this.state.messageModal}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
</div>
)
}

View File

@@ -1,17 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import { regExpPattern } from 'regexp-pattern'
import { Text, Column } from 'ui'
import { MessageDialog, WaitDialog } from '../Dialog'
import { Validator, ValidatedInput, ValidatedButton } from '../Validated'
import { Text, Column, BoundInput, BoundButton } from 'ui'
import { MessageModal, WaitModal } from '../Modal'
import { api } from '../helpers'
import { FormBinder } from 'react-form-binder'
export class ForgotPassword extends React.Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}
static validations = {
static bindings = {
email: {
alwaysGet: true,
isValid: (r, v) => (regExpPattern.email.test(v))
@@ -25,23 +25,23 @@ export class ForgotPassword extends React.Component {
constructor(props) {
super(props)
this.state = {
validator: new Validator({}, ForgotPassword.validations),
messageDialog: null,
waitDialog: null
binder: new FormBinder({}, ForgotPassword.bindings),
messageModal: null,
waitModal: null
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleMessageDialogDismiss = this.handleMessageDialogDismiss.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
}
handleSubmit() {
const obj = this.state.validator.getValues()
const obj = this.state.binder.getValues()
this.setState({ waitDialog: { message: 'Requesting Reset Email' } })
this.setState({ waitModal: { message: 'Requesting Reset Email' } })
api.sendResetPassword(obj.email).then((res) => {
const email = this.state.validator.getField('email').value
const email = this.state.binder.getField('email').value
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: false,
title: 'Password Reset Requested',
message: `An email will be sent to '${email}' with a reset link. Please click on it to finish resetting the password.`
@@ -49,9 +49,9 @@ export class ForgotPassword extends React.Component {
})
}).catch((error) => {
this.setState({
validator: new Validator({}, ForgotPassword.validations), // Reset to avoid rapid retries
waitDialog: null,
messageDialog: {
binder: new FormBinder({}, ForgotPassword.bindings), // Reset to avoid rapid retries
waitModal: null,
messageModal: {
error: true,
title: 'Password Reset Failed',
message: `There was a problem requesting the password reset. ${error ? error.message : ''}`
@@ -60,7 +60,7 @@ export class ForgotPassword extends React.Component {
})
}
handleMessageDialogDismiss() {
handleMessageModalDismiss() {
this.props.history.replace('/')
}
@@ -73,29 +73,29 @@ export class ForgotPassword extends React.Component {
<Text size='large'>Forgotten Password</Text>
</Column.Item>
<Column.Item>
<ValidatedInput label='Email' name='email'
placeholder='example@xyz.com' validator={this.state.validator}
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='A valid email address' />
</Column.Item>
<Column.Item>
<Text>The email address of an existing user to send the password reset link to.</Text>
</Column.Item>
<Column.Item>
<ValidatedButton className='submit' name='submit' content='Submit'
primary submit validator={this.state.validator}>Submit</ValidatedButton>
<BoundButton className='submit' name='submit' content='Submit'
primary submit binder={this.state.binder}>Submit</BoundButton>
</Column.Item>
</Column>
</form>
<WaitDialog active={!!this.state.waitDialog}
message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
<WaitModal active={!!this.state.waitModal}
message={this.state.waitModal ? this.state.waitModal.message : ''} />
<MessageDialog
open={!!this.state.messageDialog}
error={this.state.messageDialog ? this.state.messageDialog.error : true}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal
open={!!this.state.messageModal}
error={this.state.messageModal ? this.state.messageModal.error : true}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
</div>
)
}

View File

@@ -2,18 +2,18 @@ import React from 'react'
import PropTypes from 'prop-types'
import { regExpPattern } from 'regexp-pattern'
import { api } from '../helpers'
import { Validator, ValidatedInput, ValidatedCheckbox, ValidatedButton } from '../Validated'
import { WaitDialog, MessageDialog } from '../Dialog'
import { Image, Link, Text, Row, Column } from 'ui'
import { WaitModal, MessageModal } from '../Modal'
import { Image, Link, Text, Row, Column, BoundInput, BoundCheckbox, BoundButton } from 'ui'
import headerLogo from 'images/deighton.png'
import { versionInfo } from '../version'
import { FormBinder } from 'react-form-binder'
export class Login extends React.Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}
static validations = {
static bindings = {
email: {
alwaysGet: true,
isValid: (r, v) => (regExpPattern.email.test(v))
@@ -35,11 +35,11 @@ export class Login extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleMessageDialogDismiss = this.handleMessageDialogDismiss.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
this.state = {
waitDialog: false,
messageDialog: null,
validator: new Validator({}, Login.validations)
waitModal: false,
messageModal: null,
binder: new FormBinder({}, Login.bindings)
}
}
@@ -47,19 +47,19 @@ export class Login extends React.Component {
e.preventDefault()
e.stopPropagation()
if (this.state.messageDialog) {
this.setState({ messageDialog: null })
if (this.state.messageModal) {
this.setState({ messageModal: null })
return
} else if (this.state.waitDialog) {
} else if (this.state.waitModal) {
return
}
let obj = this.state.validator.getValues()
let obj = this.state.binder.getValues()
if (obj) {
this.setState({ waitDialog: true })
this.setState({ waitModal: true })
api.login(obj.email, obj.password, obj.rememberMe).then((user) => {
this.setState({ waitDialog: false })
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
@@ -75,16 +75,16 @@ export class Login extends React.Component {
elems[0].focus()
}
this.setState({
validator: new Validator({ email: this.state.validator.getField('email').value }, Login.validations),
waitDialog: false,
messageDialog: { title: 'Login Error...', message: `Unable to login. ${error.message}` }
binder: new FormBinder({ email: this.state.binder.getField('email').value }, Login.bindings),
waitModal: false,
messageModal: { title: 'Login Error...', message: `Unable to login. ${error.message}` }
})
})
}
}
handleMessageDialogDismiss() {
this.setState({ messageDialog: null })
handleMessageModalDismiss() {
this.setState({ messageModal: null })
}
render() {
@@ -104,13 +104,13 @@ export class Login extends React.Component {
</Row>
</Column.Item>
<Column.Item>
<ValidatedInput label='Email' name='email'
placeholder='example@xyz.com' validator={this.state.validator}
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='Enter the email address associated with your account.' />
</Column.Item>
<Column.Item>
<ValidatedInput password label='Password' name='password'
validator={this.state.validator} message='Enter your password.' />
<BoundInput password label='Password' name='password'
binder={this.state.binder} message='Enter your password.' />
</Column.Item>
<Column.Item>
<Row>
@@ -119,8 +119,8 @@ export class Login extends React.Component {
</Row.Item>
<Row.Item grow />
<Row.Item>
<ValidatedCheckbox label='Remember Me'
name='rememberMe' onChange={this.handleChange} validator={this.state.validator} />
<BoundCheckbox label='Remember Me'
name='rememberMe' onChange={this.handleChange} binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
@@ -129,7 +129,7 @@ export class Login extends React.Component {
<Row.Item grow />
<Row.Item>
<br />
<ValidatedButton name='submit' content='Login' height={60} submit validator={this.state.validator} />
<BoundButton name='submit' content='Login' height={60} submit binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
@@ -144,12 +144,12 @@ export class Login extends React.Component {
</Row.Item>
<Row.Item grow>&nbsp;</Row.Item>
<WaitDialog active={this.state.waitDialog} message='Logging In' />
<WaitModal active={this.state.waitModal} message='Logging In' />
<MessageDialog error open={!!this.state.messageDialog}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal error open={!!this.state.messageModal}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
</Row>
)
}

View File

@@ -1,16 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Text, Column } from 'ui'
import { Validator, ValidatedInput, ValidatedButton } from '../Validated'
import { MessageDialog, WaitDialog } from '../Dialog'
import { Text, Column, BoundInput, BoundButton } from 'ui'
import { MessageModal, WaitModal } from '../Modal'
import { api } from '../helpers'
import { FormBinder } from 'react-form-binder'
export class ResetPassword extends React.Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}
static validations = {
static bindings = {
newPassword: {
alwaysGet: true,
isValid: (r, v) => (v.length >= 6)
@@ -27,27 +27,27 @@ export class ResetPassword extends React.Component {
constructor(props) {
super(props)
this.state = {
validator: new Validator({}, ResetPassword.validations),
messageDialog: null,
waitDialog: null
binder: new FormBinder({}, ResetPassword.bindings),
messageModal: null,
waitModal: null
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleMessageDialogDismiss = this.handleMessageDialogDismiss.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
}
handleSubmit() {
const obj = this.state.validator.getValues()
const obj = this.state.binder.getValues()
const passwordToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('password-token')
this.setState({ waitDialog: { message: 'Setting Password...' } })
this.setState({ waitModal: { message: 'Setting Password...' } })
api.resetPassword({ newPassword: obj.newPassword, passwordToken }).then(() => {
this.setState({ waitDialog: null })
this.setState({ waitModal: null })
this.props.history.replace('/login')
}).catch((error) => {
this.setState({
validator: new Validator({}, ResetPassword.validations), // Reset to avoid accidental rapid retries
waitDialog: null,
messageDialog: {
binder: new FormBinder({}, ResetPassword.bindings), // Reset to avoid accidental rapid retries
waitModal: null,
messageModal: {
title: 'We had a problem changing your password',
message: error.message
}
@@ -55,8 +55,8 @@ export class ResetPassword extends React.Component {
})
}
handleMessageDialogDismiss() {
this.setState({ messageDialog: null })
handleMessageModalDismiss() {
this.setState({ messageModal: null })
}
render() {
@@ -68,14 +68,14 @@ export class ResetPassword extends React.Component {
<Text size='large'>Reset Password</Text>
</Column.Item>
<Column.Item>
<ValidatedInput label='New Password' password name='newPassword'
<BoundInput label='New Password' password name='newPassword'
message='A new password, cannot be blank or the same as your old password'
width={16} validator={this.state.validator} />
width={16} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Re-entered New Password' password name='reenteredNewPassword'
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
message='The new password again, must match and cannot be blank'
width={16} validator={this.state.validator} />
width={16} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<Text>
@@ -85,19 +85,19 @@ export class ResetPassword extends React.Component {
</Text>
</Column.Item>
<Column.Item>
<ValidatedButton className='submit' name='submit' content='Submit'
submit validator={this.state.validator} />
<BoundButton className='submit' name='submit' content='Submit'
submit binder={this.state.binder} />
</Column.Item>
</Column>
</form>
<MessageDialog error open={!!this.state.messageDialog}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal error open={!!this.state.messageModal}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
<WaitDialog active={!!this.state.waitDialog}
message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
<WaitModal active={!!this.state.waitModal}
message={this.state.waitModal ? this.state.waitModal.message : ''} />
</div>
)
}

View File

@@ -1,5 +0,0 @@
export { WaitDialog } from './WaitDialog'
export { YesNoMessageDialog } from './YesNoMessageDialog'
export { MessageDialog } from './MessageDialog'
export { ChangePasswordDialog } from './ChangePasswordDialog'
export { ChangeEmailDialog } from './ChangeEmailDialog'

View File

@@ -1,9 +0,0 @@
import React from 'react'
import './Footer.scss'
import { VersionInfo } from '../version'
export const Footer = () => (
<footer>
v{VersionInfo.fullVersion}. &copy; {VersionInfo.startYear} Deighton. All rights reserved.
</footer>
)

View File

@@ -1,14 +0,0 @@
footer {
background-color: #0b0b0b;
color: grey;
height: 3em;
line-height: 3em;
padding: 0 2em;
font-size: 1em;
margin-top: 3em;
position: absolute;
right: 0;
bottom: 0;
left: 0;
text-align: left;
}

View File

@@ -1 +0,0 @@
export { Footer } from './Footer'

View File

@@ -1,17 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import { autoBind } from 'auto-bind2'
import { Modal, Button, Icon, Column, Text } from 'ui'
import { ValidatedInput, ValidatedButton, Validator } from '../Validated'
import { Modal, Button, Icon, Column, Text, BoundInput, BoundButton } from 'ui'
import { regExpPattern } from 'regexp-pattern'
import { FormBinder } from 'react-form-binder'
export class ChangeEmailDialog extends React.Component {
export class ChangeEmailModal extends React.Component {
static propTypes = {
open: PropTypes.bool,
onDismiss: PropTypes.func
}
static validations = {
static bindings = {
newEmail: {
isValid: (r, v) => (v !== '' && regExpPattern.email.test(v))
},
@@ -25,12 +25,12 @@ export class ChangeEmailDialog extends React.Component {
super(props)
autoBind(this, (name) => (name.startsWith('handle')))
this.state = {
validator: new Validator({}, ChangeEmailDialog.validations)
binder: new FormBinder({}, ChangeEmailModal.bindings)
}
}
close(newEmail) {
this.state.validator = new Validator({}, ChangeEmailDialog.validations)
this.state.binder = new FormBinder({}, ChangeEmailModal.bindings)
this.props.onDismiss(newEmail)
}
@@ -42,8 +42,8 @@ export class ChangeEmailDialog extends React.Component {
e.preventDefault()
let newEmail = null
if (this.state.validator.anyModified && this.state.validator.allValid) {
newEmail = this.state.validator.getField('newEmail').value
if (this.state.binder.anyModified && this.state.binder.allValid) {
newEmail = this.state.binder.getField('newEmail').value
}
this.close(newEmail)
@@ -63,14 +63,14 @@ export class ChangeEmailDialog extends React.Component {
<Text>Change Email</Text>
</Column.Item>
<Column.Item>
<ValidatedInput label='New Email' name='newEmail' width={16}
<BoundInput label='New Email' name='newEmail' width={16}
message='Your new email address, e.g. xyz@abc.com, cannot be blank'
validator={this.state.validator} />
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedButton primary submit form='emailForm' name='submit' validator={this.state.validator}>
<BoundButton primary submit form='emailForm' name='submit' binder={this.state.binder}>
<Icon name='checkmark' /> OK
</ValidatedButton>
</BoundButton>
<Button color='red' onClick={this.handleClick}>
<Icon name='close' /> Cancel
</Button>

View File

@@ -1,16 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import { autoBind } from 'auto-bind2'
import { Modal, Button, Icon, Column, Row, Text } from 'ui'
import { ValidatedInput, ValidatedActionsButton, Validator } from '../Validated'
import { Modal, Button, Icon, Column, Row, Text, BoundInput, BoundButton } from 'ui'
import { FormBinder } from 'react-form-binder'
export class ChangePasswordDialog extends React.Component {
export class ChangePasswordModal extends React.Component {
static propTypes = {
open: PropTypes.bool,
onDismiss: PropTypes.func
}
static validations = {
static bindings = {
oldPassword: {
alwaysGet: true,
isValid: (r, v) => (v !== '')
@@ -33,12 +33,12 @@ export class ChangePasswordDialog extends React.Component {
super(props)
autoBind(this, (name) => (name.startsWith('handle')))
this.state = {
validator: new Validator({}, ChangePasswordDialog.validations)
binder: new FormBinder({}, ChangePasswordModal.bindings)
}
}
close(passwords) {
this.state.validator = new Validator({}, ChangePasswordDialog.validations)
this.state.binder = new FormBinder({}, ChangePasswordModal.bindings)
this.props.onDismiss(passwords)
}
@@ -50,9 +50,9 @@ export class ChangePasswordDialog extends React.Component {
e.preventDefault()
let passwords = null
if (this.state.validator.allValid) {
const oldPassword = this.state.validator.getField('oldPassword').value
const newPassword = this.state.validator.getField('newPassword').value
if (this.state.binder.allValid) {
const oldPassword = this.state.binder.getField('oldPassword').value
const newPassword = this.state.binder.getField('newPassword').value
passwords = { oldPassword, newPassword }
}
this.close(passwords)
@@ -72,27 +72,27 @@ export class ChangePasswordDialog extends React.Component {
<Column.Item>
<Column>
<Column.Item>
<ValidatedInput label='Current Password' password name='oldPassword'
<BoundInput label='Current Password' password name='oldPassword'
message='Your existing password, cannot be blank'
width={8} validator={this.state.validator} />
width={8} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='New Password' password name='newPassword'
<BoundInput label='New Password' password name='newPassword'
message='A new password, cannot be blank or the same as your old password'
width={8} validator={this.state.validator} />
width={8} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Re-entered New Password' password name='reenteredNewPassword'
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
message='The new password again, must match and cannot be blank'
width={8} validator={this.state.validator} />
width={8} binder={this.state.binder} />
</Column.Item>
</Column>
</Column.Item>
<Column.Item>
<Row>
<ValidatedActionsButton primary submit form='passwordForm' name='submit' validator={this.state.validator}>
<BoundButton primary submit form='passwordForm' name='submit' binder={this.state.binder}>
<Icon name='checkmark' /> OK
</ValidatedActionsButton>
</BoundButton>
<Button color='red' onClick={this.handleClick}>
<Icon name='close' /> Cancel
</Button>

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Modal, Button, Icon, Column, Text } from 'ui'
export class MessageDialog extends React.Component {
export class MessageModal extends React.Component {
static propTypes = {
open: PropTypes.bool,
title: PropTypes.string.isRequired,

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Dimmer, Loader, Text } from 'ui'
export class WaitDialog extends React.Component {
export class WaitModal extends React.Component {
static propTypes = {
message: PropTypes.string.isRequired
}

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Modal, Button, Column, Text, Icon } from 'ui'
export class YesNoMessageDialog extends React.Component {
export class YesNoMessageModal extends React.Component {
static propTypes = {
open: PropTypes.bool,
title: PropTypes.string.isRequired,

View File

@@ -0,0 +1,5 @@
export { WaitModal } from './WaitModal'
export { YesNoMessageModal } from './YesNoMessageModal'
export { MessageModal } from './MessageModal'
export { ChangePasswordModal } from './ChangePasswordModal'
export { ChangeEmailModal } from './ChangeEmailModal'

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { ProfileForm } from './ProfileForm'
import { Constants, api } from '../helpers'
import { WaitDialog, MessageDialog, ChangePasswordDialog, ChangeEmailDialog } from '../Dialog'
import { WaitModal, MessageModal, ChangePasswordModal, ChangeEmailModal } from '../Modal'
import { autoBind } from 'auto-bind2'
export class Profile extends React.Component {
@@ -11,11 +11,11 @@ export class Profile extends React.Component {
const user = api.loggedInUser
this.state = {
messageDialog: null,
waitDialog: null,
changePasswordDialog: null,
changeEmailDialog: null,
progressDialog: null,
messageModal: null,
waitModal: null,
changePasswordModal: null,
changeEmailModal: null,
progressModal: null,
uploadPercent: 0,
user,
userImageUrl: api.makeImageUrl(user.imageId, Constants.bigUserImageSize)
@@ -35,32 +35,32 @@ export class Profile extends React.Component {
}
handleSaved(user) {
this.setState({ waitDialog: { message: 'Updating Profile' } })
this.setState({ waitModal: { message: 'Updating Profile' } })
api.updateUser(user).then((updatedUser) => {
this.setState({
waitDialog: null,
waitModal: null,
user: updatedUser
})
}).catch((error) => {
this.setState({
waitDialog: null,
messageDialog: { title: 'Update Error...', message: `Unable to save the profile changes. ${error.message}` }
waitModal: null,
messageModal: { title: 'Update Error...', message: `Unable to save the profile changes. ${error.message}` }
})
})
}
handleMessageDialogDismiss() {
this.setState({ messageDialog: null })
handleMessageModalDismiss() {
this.setState({ messageModal: null })
}
handleChangePassword() {
this.setState({ changePasswordDialog: true })
this.setState({ changePasswordModal: true })
}
handleSelectImage(file) {
this.setState({ progressDialog: { message: `Uploading image '${file.name}'...`, file }, uploadPercent: 0 })
this.setState({ progressModal: { message: `Uploading image '${file.name}'...`, file }, uploadPercent: 0 })
api.upload(file, this.handleProgress).then((uploadData) => {
this.setState({ progressDialog: null })
this.setState({ progressModal: null })
return api.setUserImage({
_id: api.loggedInUser._id,
imageId: uploadData.assetId,
@@ -70,14 +70,14 @@ export class Profile extends React.Component {
}).catch((error) => {
// TODO: if the upload succeeds but the setUserImage fails, delete the uploaded image
this.setState({
progressDialog: null,
messageDialog: { title: 'Upload Error...', message: `Unable to upload the file. ${error.message}` }
progressModal: null,
messageModal: { title: 'Upload Error...', message: `Unable to upload the file. ${error.message}` }
})
})
}
handleProgress(uploadData) {
if (this.state.progressDialog) {
if (this.state.progressModal) {
this.setState({ uploadPercent: Math.round(uploadData.uploadedChunks / uploadData.numberOfChunks * 100) })
return true
} else {
@@ -86,22 +86,22 @@ export class Profile extends React.Component {
}
handleUploadCancel(result) {
this.setState({ progressDialog: null })
this.setState({ progressModal: null })
}
handleChangePasswordDismiss(passwords) {
this.setState({ changePasswordDialog: false })
this.setState({ changePasswordModal: false })
if (passwords) {
this.setState({
waitDialog: { message: 'Changing Password' }
waitModal: { message: 'Changing Password' }
})
api.changePassword(passwords).then(() => {
this.setState({ waitDialog: false })
this.setState({ waitModal: false })
}).catch((error) => {
this.setState({
waitDialog: false,
messageDialog: {
waitModal: false,
messageModal: {
title: 'Changing Password Error',
message: `Unable to change password. ${error.message}.`
}
@@ -111,21 +111,21 @@ export class Profile extends React.Component {
}
handleChangeEmail() {
this.setState({ changeEmailDialog: {} })
this.setState({ changeEmailModal: {} })
}
handleChangeEmailDismiss(newEmail) {
this.setState({ changeEmailDialog: null })
this.setState({ changeEmailModal: null })
if (!newEmail) {
return
}
this.setState({
waitDialog: { message: 'Requesting Email Change...' }
waitModal: { message: 'Requesting Email Change...' }
})
api.sendConfirmEmail({ newEmail }).then(() => {
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: false,
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.`
@@ -134,8 +134,8 @@ export class Profile extends React.Component {
}).catch((error) => {
this.setState({
error: true,
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Email Change Error...',
message: `Unable to request email change. ${error ? error.message : ''}`
@@ -155,16 +155,16 @@ export class Profile extends React.Component {
onChangeEmail={this.handleChangeEmail}
userImageUrl={this.state.userImageUrl} />
<MessageDialog error open={!!this.state.messageDialog}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal error open={!!this.state.messageModal}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
<ChangeEmailDialog open={!!this.state.changeEmailDialog} onDismiss={this.handleChangeEmailDismiss} />
<ChangeEmailModal open={!!this.state.changeEmailModal} onDismiss={this.handleChangeEmailDismiss} />
<WaitDialog active={!!this.state.waitDialog} message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
<WaitModal active={!!this.state.waitModal} message={this.state.waitModal ? this.state.waitModal.message : ''} />
<ChangePasswordDialog open={!!this.state.changePasswordDialog} onDismiss={this.handleChangePasswordDismiss} />
<ChangePasswordModal open={!!this.state.changePasswordModal} onDismiss={this.handleChangePasswordDismiss} />
</div>
)
}

View File

@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Column, Button } from 'ui'
import { Column, Button, BoundInput, BoundButton } from 'ui'
import { regExpPattern } from 'regexp-pattern'
import { Validator, ValidatedInput, ValidatedButton } from '../Validated'
import { FormBinder } from 'react-form-binder'
export class ProfileForm extends React.Component {
static propTypes = {
@@ -13,7 +13,7 @@ export class ProfileForm extends React.Component {
onChangeEmail: PropTypes.func
}
static validations = {
static bindings = {
email: {
isValid: (r, v) => (v !== ''),
isDisabled: (r) => (!!r._id)
@@ -68,16 +68,16 @@ export class ProfileForm extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
validator: new Validator(
this.props.user, ProfileForm.validations, this.props.onModifiedChanged)
binder: new FormBinder(
this.props.user, ProfileForm.bindings, this.props.onModifiedChanged)
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.props.user) {
this.setState({
validator: new Validator(
nextProps.user, ProfileForm.validations, nextProps.onModifiedChanged)
binder: new FormBinder(
nextProps.user, ProfileForm.bindings, nextProps.onModifiedChanged)
})
}
}
@@ -85,7 +85,7 @@ export class ProfileForm extends React.Component {
handleSubmit(e) {
e.preventDefault()
let obj = this.state.validator.getValues()
let obj = this.state.binder.getValues()
if (obj && this.props.onSaved) {
this.props.onSaved(obj)
@@ -97,16 +97,16 @@ export class ProfileForm extends React.Component {
<form className='profile-form' onSubmit={this.handleSubmit}>
<Column stackable>
<Column.Item>
<ValidatedInput label='First Name' name='firstName' width={8}
validator={this.state.validator} />
<BoundInput label='First Name' name='firstName' width={8}
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Last Name' name='lastName' width={8}
validator={this.state.validator} />
<BoundInput label='Last Name' name='lastName' width={8}
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Email' name='email' width={8} message='Required. Must be a valid email address.'
validator={this.state.validator} />
<BoundInput label='Email' name='email' width={8} message='Required. Must be a valid email address.'
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<Button fluid content={'Change Email'} label='&nbsp;'
@@ -115,8 +115,8 @@ export class ProfileForm extends React.Component {
width={4} onClick={this.props.onChangePassword} />
</Column.Item>
<Column.Item>
<ValidatedButton submit primary width={4} size='medium' content='Save' label='&nbsp;' name='save'
validator={this.state.validator} />
<BoundButton submit primary width={4} size='medium' content='Save' label='&nbsp;' name='save'
binder={this.state.binder} />
</Column.Item>
</Column>
</form>

View File

@@ -1,23 +1,22 @@
import React from 'react'
import PropTypes from 'prop-types'
import { autoBind } from 'auto-bind2'
import { reactAutoBind } from 'auto-bind2'
import { regExpPattern } from 'regexp-pattern'
import { ValidatedEmailIcon } from './ValidatedEmailIcon'
import { Constants, api } from '../helpers'
import { Validator, ValidatedInput, ValidatedButton, ValidatedCheckbox } from '../Validated'
import { Column } from 'ui'
import { Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon } from 'ui'
import { FormBinder } from 'react-form-binder'
export class UserForm extends React.Component {
static propTypes = {
user: PropTypes.object,
onSave: PropTypes.func,
onRemove: PropTypes.func,
onModifiedChanged: PropTypes.func,
onAnyModified: PropTypes.func,
onChangeEmail: PropTypes.func,
onResendEmail: PropTypes.func
}
static validations = {
static bindings = {
email: {
isValid: (r, v) => (regExpPattern.email.test(v)),
isDisabled: (r) => (!!r._id)
@@ -35,45 +34,6 @@ export class UserForm extends React.Component {
lastName: {
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))
},
numHouseholds: {
isValid: true
},
t12: {
isValid: true
},
aum: {
isValid: true
},
role: {
isValid: (r, v) => (v !== ''),
isDisabled: (r) => (api.loggedInUser._id === r._id)
@@ -94,43 +54,40 @@ export class UserForm extends React.Component {
nonValue: true,
isDisabled: (r) => (!r.anyModified && !r.allValid)
},
'broker-fields': {
nonValue: true,
isVisible: (r, v) => (r.getField('role').value === 'broker')
},
'standard-fields': {
nonValue: true,
isVisible: (r, v) => (r.getField('role').value !== 'broker')
}
}
constructor(props) {
super(props)
autoBind(this, (name) => (name.startsWith('handle')))
reactAutoBind(this)
this.state = {
validator: new Validator(this.props.user, UserForm.validations, this.props.onModifiedChanged)
binder: new FormBinder(this.props.user, UserForm.bindings, this.props.onAnyModified)
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.props.user) {
this.setState({
validator: new Validator(nextProps.user, UserForm.validations, nextProps.onModifiedChanged)
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onAnyModified)
})
}
}
handleSubmit(e) {
e.preventDefault()
let obj = this.state.validator.getValues()
let obj = this.state.binder.getValues()
if (obj) {
this.props.onSave(obj)
}
}
handleReset() {
this.setState({ validator: new Validator(this.props.user, UserForm.validations, this.props.onModifiedChanged) })
this.props.onModifiedChanged(false)
const { user, onAnyModified } = this.props
this.setState({ binder: new FormBinder(user, UserForm.bindings, onAnyModified) })
if (onAnyModified) {
onAnyModified(false)
}
}
handleChangeEmail() {
@@ -146,37 +103,37 @@ export class UserForm extends React.Component {
<form onSubmit={this.handleSubmit}>
<Column>
<Column.Item>
<ValidatedCheckbox label={'Deighton Access & Security Level'} width={6} selection
<BoundCheckbox label={'Deighton Access & Security Level'} width={6} selection
options={Constants.accessLevels} name='role' message='The user role and security level'
placeholder='' validator={this.state.validator} />
placeholder='' binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='First Name' name='firstName'
width={8} validator={this.state.validator} />
<BoundInput label='First Name' name='firstName'
width={8} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Last Name' name='lastName'
width={8} validator={this.state.validator} />
<BoundInput label='Last Name' name='lastName'
width={8} binder={this.state.binder} />
</Column.Item>
<Column.Item>
<ValidatedInput label='Email' name='email' width={8} message='Must be a valid email address. Required.'
validator={this.state.validator} />
<ValidatedEmailIcon name='emailValidated' validator={this.state.validator} width={4}
<BoundInput label='Email' name='email' width={8} message='Must be a valid email address. Required.'
binder={this.state.binder} />
<BoundEmailIcon name='emailValidated' binder={this.state.binder} width={4}
onClick={this.handleResendEmail} />
</Column.Item>
<Column.Item>
<ValidatedButton width={4} size='medium' content='Change Email' label='&nbsp;' name='changeEmail'
validator={this.state.validator} onClick={this.handleChangeEmail} />
<BoundButton width={4} size='medium' content='Change Email' label='&nbsp;' name='changeEmail'
binder={this.state.binder} onClick={this.handleChangeEmail} />
</Column.Item>
<Column.Item>
<ValidatedButton color='red' width={4} size='medium' content='Remove' label='&nbsp;' name='remove'
validator={this.state.validator} onClick={this.props.onRemove} />
<ValidatedButton width={4} size='medium' content='Reset' label='&nbsp;' name='reset'
validator={this.state.validator} onClick={this.handleReset} />
<ValidatedButton primary submit width={4} size='medium'
content={this.state.validator._id ? 'Save' : 'Add'} label='&nbsp;' name='submit'
validator={this.state.validator} />
<BoundButton color='red' width={4} size='medium' content='Remove' label='&nbsp;' name='remove'
binder={this.state.binder} onClick={this.props.onRemove} />
<BoundButton width={4} size='medium' content='Reset' label='&nbsp;' name='reset'
binder={this.state.binder} onClick={this.handleReset} />
<BoundButton primary submit width={4} size='medium'
content={this.state.binder._id ? 'Save' : 'Add'} label='&nbsp;' name='submit'
binder={this.state.binder} />
</Column.Item>
</Column>
</form>

View File

@@ -1,8 +0,0 @@
.user-form {
text-align: left;
margin: 3em auto 4em auto;
}
.user-form > .fields {
margin-bottom: 1.5em !important;
}

View File

@@ -2,7 +2,6 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Dropdown, List, Icon, Button, Image } from 'ui'
import { Constants, api } from '../helpers'
import './UserList.scss'
const selectionOptions = Constants.accessLevels.concat([{ value: 'all', text: 'All Levels' }])

View File

@@ -1,21 +0,0 @@
.user-list {
text-align: left;
width: 100%;
height: 100%;
padding: 0.25em !important;
border-radius: 5px;
box-shadow: 0 0 0.25em 2px Silver;
-webkit-box-shadow: 0 0 0.25em 2px Silver;
max-height: 60vh;
min-height: 60vh;
overflow-y: scroll;
}
.user-list > .item {
margin: 5em 0;
}
i.user-update {
position: absolute;
right: 7.5%;
}

View File

@@ -5,7 +5,7 @@ import { UserForm } from './UserForm'
import { UserFormPlaceholder } from './UserFormPlaceholder'
import { api } from '../helpers'
import { Row } from 'ui'
import { YesNoMessageDialog, MessageDialog, ChangeEmailDialog, WaitDialog } from '../Dialog'
import { YesNoMessageModal, MessageModal, ChangeEmailModal, WaitModal } from '../Modal'
export class Users extends React.Component {
constructor() {
@@ -14,10 +14,10 @@ export class Users extends React.Component {
this.state = {
selectedUser: null,
users: [],
yesNoDialog: null,
messageDialog: null,
waitDialog: null,
changeEmailDialog: null
yesNoModal: null,
messageModal: null,
waitModal: null,
changeEmailModal: null
}
}
@@ -27,7 +27,7 @@ export class Users extends React.Component {
this.setState({ users: list.items })
}).catch((error) => {
this.setState({
messageDialog: {
messageModal: {
error: true,
title: 'User List Error',
message: `Unable to get the list of users. ${error.message}`
@@ -50,10 +50,10 @@ export class Users extends React.Component {
if (this.state.modified) {
this.nextSelectedUser = user
this.setState({
yesNoDialog: {
yesNoModal: {
title: 'User Modified',
message: 'This user has been modified. Are you sure you would like to navigate away?',
onDismiss: this.handleModifiedDialogDismiss
onDismiss: this.handleModifiedModalDismiss
}
})
} else {
@@ -64,18 +64,18 @@ export class Users extends React.Component {
handleSave(user) {
if (user._id) {
this.setState({ waitDialog: { message: 'Updating User' } })
this.setState({ waitModal: { message: 'Updating User' } })
api.updateUser(user).then((updatedUser) => {
this.setState({
waitDialog: null,
waitModal: null,
users: this.state.users.map((user) => (user._id === updatedUser._id ? updatedUser : user)),
modified: false,
selectedUser: updatedUser
})
}).catch((error) => {
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Update Error',
message: `Unable to save the user changes. ${error.message}`
@@ -83,18 +83,18 @@ export class Users extends React.Component {
})
})
} else {
this.setState({ waitDialog: { message: 'Creating User' } })
this.setState({ waitModal: { message: 'Creating User' } })
api.createUser(user).then((createdUser) => {
this.setState({
waitDialog: false,
waitModal: false,
users: this.state.users.map((user) => (!user._id ? createdUser : user)),
modified: false,
selectedUser: createdUser
})
}).catch((error) => {
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Create Error',
message: `Unable to create the user. ${error.message}`
@@ -105,17 +105,17 @@ export class Users extends React.Component {
}
handleChangeEmail() {
this.setState({ changeEmailDialog: {} })
this.setState({ changeEmailModal: {} })
}
handleResendEmail() {
this.setState({
waitDialog: { message: 'Resending Email...' }
waitModal: { message: 'Resending Email...' }
})
api.sendConfirmEmail({ existingEmail: this.state.selectedUser.email }).then(() => {
this.setState({
waitDialog: null,
messageDialog: {
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.`
@@ -124,8 +124,8 @@ export class Users extends React.Component {
}).catch((error) => {
this.setState({
error: true,
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Email Change Error...',
message: `Unable to request email change. ${error ? error.message : ''}`
@@ -135,17 +135,17 @@ export class Users extends React.Component {
}
handleChangeEmailDismiss(newEmail) {
this.setState({ changeEmailDialog: null })
this.setState({ changeEmailModal: null })
if (!newEmail) {
return
}
this.setState({
waitDialog: { message: 'Requesting Email Change...' }
waitModal: { message: 'Requesting Email Change...' }
})
api.sendConfirmEmail({ existingEmail: this.state.selectedUser.email, newEmail }).then(() => {
this.setState({
waitDialog: null,
messageDialog: {
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.`
@@ -154,8 +154,8 @@ export class Users extends React.Component {
}).catch((error) => {
this.setState({
error: true,
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Email Change Error...',
message: `Unable to request email change. ${error ? error.message : ''}`
@@ -166,32 +166,32 @@ export class Users extends React.Component {
handleRemove() {
this.setState({
yesNoDialog: {
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?',
onDismiss: this.handleRemoveDialogDismiss
onDismiss: this.handleRemoveModalDismiss
}
})
}
handleRemoveDialogDismiss(yes) {
handleRemoveModalDismiss(yes) {
if (yes) {
// TODO: Pass the _id back from the dialog input data
const selectedUserId = this.state.selectedUser._id
const selectedIndex = this.state.users.findIndex((user) => (user._id === selectedUserId))
if (selectedIndex >= 0) {
this.setState({ waitDialog: { message: 'Removing User' } })
this.setState({ waitModal: { message: 'Removing User' } })
api.deleteUser(selectedUserId).then(() => {
this.setState({
waitDialog: null,
waitModal: null,
users: [...this.state.users.slice(0, selectedIndex), ...this.state.users.slice(selectedIndex + 1)],
selectedUser: null
})
}).catch((error) => {
this.setState({
waitDialog: null,
messageDialog: {
waitModal: null,
messageModal: {
error: true,
title: 'Remove Error',
message: `Unable to remove the user. ${error.message}`
@@ -202,11 +202,11 @@ export class Users extends React.Component {
}
this.setState({
yesNoDialog: null
yesNoModal: null
})
}
handleModifiedDialogDismiss(yes) {
handleModifiedModalDismiss(yes) {
if (yes) {
this.setState({
selectedUser: this.nextSelectedUser,
@@ -217,12 +217,12 @@ export class Users extends React.Component {
}
this.setState({
yesNoDialog: null
yesNoModal: null
})
}
handleMessageDialogDismiss() {
this.setState({ messageDialog: null })
handleMessageModalDismiss() {
this.setState({ messageModal: null })
}
handleModifiedChanged(modified) {
@@ -260,21 +260,21 @@ export class Users extends React.Component {
</Row.Item>
</Row>
<ChangeEmailDialog open={!!this.state.changeEmailDialog} onDismiss={this.handleChangeEmailDismiss} />
<ChangeEmailModal open={!!this.state.changeEmailModal} onDismiss={this.handleChangeEmailDismiss} />
<YesNoMessageDialog open={!!this.state.yesNoDialog}
title={this.state.yesNoDialog ? this.state.yesNoDialog.title : ''}
message={this.state.yesNoDialog ? this.state.yesNoDialog.message : ''}
onDismiss={this.state.yesNoDialog ? this.state.yesNoDialog.onDismiss : null} />
<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} />
<MessageDialog
open={!!this.state.messageDialog}
error={this.state.messageDialog ? this.state.messageDialog.error : false}
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
onDismiss={this.handleMessageDialogDismiss} />
<MessageModal
open={!!this.state.messageModal}
error={this.state.messageModal ? this.state.messageModal.error : false}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
<WaitDialog active={!!this.state.waitDialog} message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
<WaitModal active={!!this.state.waitModal} message={this.state.waitModal ? this.state.waitModal.message : ''} />
</div>
)
}

View File

@@ -1,59 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button } from 'ui'
export class ValidatedActionsButton extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.string,
validator: PropTypes.object.isRequired,
primary: PropTypes.bool,
submit: PropTypes.bool,
color: PropTypes.string,
form: PropTypes.string,
children: PropTypes.node
}
constructor(props) {
super(props)
this.updateValue = this.updateValue.bind(this)
let { name, validator } = this.props
validator.addListener(name, this.updateValue)
this.state = validator.getField(name)
}
updateValue(name) {
this.setState(this.props.validator.getField(name))
}
componentWillUnmount() {
this.props.validator.removeListener(this.props.name, this.updateValue)
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.props.validator.removeListener(this.props.name, this.updateValue)
nextProps.validator.addListener(nextProps.name, this.updateValue)
this.setState(nextProps.validator.getField(nextProps.name))
}
}
render() {
const { children, size, color, primary, submit, name, form } = this.props
if (this.state.visible) {
return (
<Button color={color} primary={primary}
disabled={this.state.disabled}
type={submit ? 'submit' : 'button'}
size={size} name={name} form={form}>
{children}
</Button>
)
} else {
return null
}
}
}

View File

@@ -1,41 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Checkbox } from 'ui'
// This is an example of a validated component with a value that can change itself, that cannot ever be invalid.
export class ValidatedCheckbox extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
validator: PropTypes.object.isRequired,
}
constructor(props) {
super(props)
this.state = props.validator.getField(props.name)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e, data) {
const { validator, name } = this.props
const state = validator.getField(name)
if (!state.readOnly && !state.disabled) {
this.setState(validator.updateValue(name, data.checked))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.setState(nextProps.validator.getField(nextProps.name))
}
}
render() {
return (
<Checkbox value={!!this.state.value} name={this.props.name} label={this.props.label} />
)
}
}

View File

@@ -1,51 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
export class ValidatedContainer extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
validator: PropTypes.object.isRequired,
children: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object
])
}
constructor(props) {
super(props)
this.updateValue = this.updateValue.bind(this)
let { name, validator } = this.props
validator.addListener(name, this.updateValue)
this.state = validator.getField(name)
}
updateValue(name) {
this.setState(this.props.validator.getField(name))
}
componentWillUnmount() {
this.props.validator.removeListener(this.props.name, this.updateValue)
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.props.validator.removeListener(this.props.name, this.updateValue)
nextProps.validator.addListener(nextProps.name, this.updateValue)
this.setState(nextProps.validator.getField(nextProps.name))
}
}
render() {
if (this.state.visible) {
return (
<div>
{this.props.children}
</div>
)
} else {
return null
}
}
}

View File

@@ -1,47 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Dropdown, Text } from 'ui'
export class ValidatedDropdown extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
width: PropTypes.number,
placeholder: PropTypes.string,
options: PropTypes.array,
message: PropTypes.string.isRequired,
validator: PropTypes.object.isRequired,
searchable: PropTypes.bool
}
constructor(props) {
super(props)
this.state = props.validator.getField(props.name)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e, data) {
const { validator, name } = this.props
this.setState(validator.updateValue(name, data.value))
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.setState(nextProps.validator.getField(nextProps.name))
}
}
render() {
return (
<div error={!this.state.valid} width={this.props.width}>
<label>{this.props.label}</label>
<Dropdown selection disabled={this.state.disabled} readOnly={this.state.readOnly}
placeholder={this.props.placeholder} options={this.props.options}
value={this.state.value} onChange={this.handleChange} search={this.props.searchable} />
<Text>{this.props.message}</Text>
</div>
)
}
}

View File

@@ -1,188 +0,0 @@
import EventEmitter from 'eventemitter3'
const any = function(obj, pred) {
for (let name in obj) {
if (pred(obj[name])) {
return true
}
}
return false
}
export class Validator extends EventEmitter {
constructor(originalObj, validations, onModifiedChanged) {
super()
this.updateValue = this.updateValue.bind(this)
this._id = originalObj._id
this.onModifiedChanged = onModifiedChanged
this.fields = {}
this.nonValueFields = {}
for (let name in validations) {
let validation = validations[name]
let field = {
isDisabled: this.normalize(validation.isDisabled, false),
isReadOnly: this.normalize(validation.isReadOnly, false),
isVisible: this.normalize(validation.isVisible, true),
nonValue: validation.nonValue || false
}
if (field.nonValue) {
field.disabled = field.isDisabled(this)
field.readOnly = field.isReadOnly(this)
field.visible = field.isVisible(this)
this.nonValueFields[name] = field
} else {
field.alwaysGet = validation.alwaysGet
field.isValid = this.normalize(validation.isValid, true)
field.initValue = (validation.initValue === undefined ? '' : validation.initValue)
field.originalValue = Validator.getObjectValue(originalObj, name)
this.updateFieldValue(field, field.originalValue || field.initValue)
this.fields[name] = field
}
}
this.updateNonValueFields()
}
normalize(obj, def) {
return obj ? ((obj.constructor === Function) ? obj : () => (!!obj)) : () => (def)
}
static stateSafeField(field) {
return {
value: field.value,
modified: field.modified,
valid: field.valid,
disabled: field.disabled,
readOnly: field.readOnly,
visible: field.visible
}
}
updateValue(name, newValue) {
let lastAnyModified = this.anyModified
let field = this.fields[name]
if (field) {
this.updateFieldValue(field, newValue)
this.updateNonValueFields()
if (lastAnyModified !== this.anyModified && this.onModifiedChanged) {
this.onModifiedChanged(this.anyModified)
}
}
return Validator.stateSafeField(field)
}
updateFieldValue(field, newValue) {
field.value = newValue
field.disabled = field.isDisabled(this)
field.readOnly = field.isReadOnly(this)
field.visible = field.isVisible(this)
field.valid = field.isValid(this, newValue)
field.modified =
field.originalValue !== undefined ? (field.originalValue !== newValue) : (newValue !== field.initValue)
this.anyModified = field.modified || any(this.fields, (field) => (field.modified))
this.allValid = !field.valid ? false : !any(this.fields, (field) => (!field.valid))
}
updateNonValueFields() {
for (let name in this.nonValueFields) {
let field = this.nonValueFields[name]
let disabled = field.isDisabled(this)
let readOnly = field.isReadOnly(this)
let visible = field.isVisible(this)
let b = (disabled !== field.disabled || readOnly !== field.readOnly || visible !== field.visible)
field.disabled = disabled
field.readOnly = readOnly
field.visible = visible
if (b) {
this.emit(name, name)
}
}
}
getField(name) {
let field = this.fields[name] || this.nonValueFields[name]
if (!field) {
throw new Error(`Field '${name}' does not have a validation entry`)
}
return Validator.stateSafeField(field)
}
getValues() {
// Generate an object that has the modified and alwaysGet fields
let obj = {}
if (!this.anyModified && !this.allValid) {
return obj
}
// Will have an _id if updating
if (this._id) {
obj._id = this._id
}
for (let name in this.fields) {
let field = this.fields[name]
if (field.alwaysGet || (!field.nonValue && field.modified)) {
let value = field.value
if (value && value.constructor === 'String') {
value = value.trim()
}
Validator.setObjectValue(obj, name, value)
}
}
return obj
}
getOriginalValues() {
// Generate an object that has the original values of all fields
let obj = {}
if (this._id) {
obj._id = this._id
}
for (let name in this.fields) {
let field = this.fields[name]
if (field.originalValue !== undefined) {
Validator.setObjectValue(obj, name, field.originalValue)
}
}
return obj
}
static getObjectValue(obj, name) {
name.split('.').forEach((namePart) => {
if (obj) {
obj = obj[namePart]
}
})
return obj
}
static setObjectValue(obj, name, value) {
name.split('.').forEach((namePart, i, nameParts) => {
if (i < nameParts.length - 1) {
if (!obj[namePart]) {
obj[namePart] = {}
}
obj = obj[namePart]
} else {
obj[namePart] = value
}
})
}
}

View File

@@ -1,7 +0,0 @@
export { Validator } from './Validator'
export { ValidatedInput } from './ValidatedInput'
export { ValidatedButton } from './ValidatedButton'
export { ValidatedActionsButton } from './ValidatedActionsButton'
export { ValidatedDropdown } from './ValidatedDropdown'
export { ValidatedCheckbox } from './ValidatedCheckbox'
export { ValidatedContainer } from './ValidatedContainer'

View File

@@ -1,54 +1,9 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320.002 320.002" style="enable-background:new 0 0 320.002 320.002;" xml:space="preserve">
<g id="XMLID_6_">
<path id="XMLID_7_" d="M51.213,175.001h173.785c8.284,0,15-6.716,15-15c0-8.284-6.716-15-15-15H51.213l19.394-19.394
c5.858-5.858,5.858-15.355,0-21.213c-5.857-5.858-15.355-5.858-21.213,0L4.396,149.393c-0.351,0.351-0.683,0.719-0.997,1.103
c-0.137,0.167-0.256,0.344-0.385,0.515c-0.165,0.22-0.335,0.435-0.488,0.664c-0.14,0.209-0.261,0.426-0.389,0.64
c-0.123,0.206-0.252,0.407-0.365,0.619c-0.118,0.22-0.217,0.446-0.323,0.67c-0.104,0.219-0.213,0.435-0.306,0.659
c-0.09,0.219-0.164,0.442-0.243,0.664c-0.087,0.24-0.179,0.477-0.253,0.722c-0.067,0.222-0.116,0.447-0.172,0.672
c-0.063,0.249-0.133,0.497-0.183,0.751c-0.051,0.259-0.082,0.521-0.119,0.782c-0.032,0.223-0.075,0.443-0.097,0.669
c-0.048,0.484-0.073,0.971-0.074,1.457c0,0.007-0.001,0.015-0.001,0.022c0,0.007,0.001,0.015,0.001,0.022
c0.001,0.487,0.026,0.973,0.074,1.458c0.022,0.223,0.064,0.44,0.095,0.661c0.038,0.264,0.069,0.528,0.121,0.79
c0.05,0.252,0.119,0.496,0.182,0.743c0.057,0.227,0.107,0.456,0.175,0.681c0.073,0.241,0.164,0.474,0.248,0.71
c0.081,0.226,0.155,0.453,0.247,0.675c0.091,0.22,0.198,0.431,0.3,0.646c0.108,0.229,0.21,0.46,0.33,0.685
c0.11,0.205,0.235,0.4,0.354,0.599c0.131,0.221,0.256,0.444,0.4,0.659c0.146,0.219,0.309,0.424,0.466,0.635
c0.136,0.181,0.262,0.368,0.407,0.544c0.299,0.364,0.616,0.713,0.947,1.048c0.016,0.016,0.029,0.034,0.045,0.05l45,45.001
c2.93,2.929,6.768,4.394,10.607,4.394c3.838-0.001,7.678-1.465,10.606-4.393c5.858-5.858,5.858-15.355,0.001-21.213L51.213,175.001
z"/>
<path id="XMLID_8_" d="M305.002,25h-190c-8.284,0-15,6.716-15,15v60c0,8.284,6.716,15,15,15s15-6.716,15-15V55h160v210.001h-160
v-45.001c0-8.284-6.716-15-15-15s-15,6.716-15,15v60.001c0,8.284,6.716,15,15,15h190c8.284,0,15-6.716,15-15V40
C320.002,31.716,313.286,25,305.002,25z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>
<?xml version='1.0' encoding='utf-8'?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
<g>
<g>
<path d="m88.6,94.4c0.8,0.8 1.8,1.2 2.9,1.2s2.1-0.4 2.9-1.2l27-27c0.2-0.2 0.4-0.4 0.5-0.6 0,0 0.1-0.1 0.1-0.2 0.1-0.2 0.2-0.4 0.3-0.5 0-0.1 0-0.2 0.1-0.2 0.1-0.2 0.1-0.3 0.2-0.5 0.1-0.3 0.1-0.5 0.1-0.8 0-0.3 0-0.5-0.1-0.8 0-0.2-0.1-0.4-0.2-0.5 0-0.1 0-0.2-0.1-0.2-0.1-0.2-0.2-0.4-0.3-0.6 0,0 0-0.1-0.1-0.1-0.1-0.2-0.3-0.4-0.5-0.6l-27-27c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l20,20h-71.1c-2.3,0-4.1,1.8-4.1,4.1 0,2.3 1.8,4.1 4.1,4.1h71.1l-20,20c-1.6,1.4-1.6,4 0,5.6z"/>
<path d="m10.5,122.5h54c2.3,0 4.1-1.8 4.1-4.1v-40.3c0-2.3-1.8-4.1-4.1-4.1s-4.1,1.8-4.1,4.1v36.2h-45.8v-99.7h45.8v36.2c0,2.3 1.8,4.1 4.1,4.1s4.1-1.8 4.1-4.1v-40.3c0-2.3-1.8-4.1-4.1-4.1h-54c-2.3,0-4.1,1.8-4.1,4.1v107.9c0.1,2.3 1.9,4.1 4.1,4.1z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 965 B

View File

@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
<g>
<g>
<path d="m64.5,6.4c-25.5,0-46.3,20.8-46.3,46.4-3.55271e-15,3.1 0.3,6.2 0.9,9.2 0,0.2 0.1,0.4 0.1,0.7 5.1,22.9 41,57.3 42.5,58.7 0.8,0.8 1.8,1.1 2.8,1.1 1,0 2-0.4 2.8-1.1 1.5-1.5 37.2-35.8 42.3-58.2 0-0.2 0.1-0.4 0.1-0.6 0.7-3.2 1-6.5 1-9.8 0.1-25.6-20.7-46.4-46.2-46.4zm37.2,54.7c0,0.2 0,0.1 0,0.3-3.8,16.6-28.7,42.8-37.2,51.3-8.5-8.5-33.5-34.7-37.3-51.7 0-0.2 0-0.3-0.1-0.5-0.5-2.6-0.8-5.2-0.8-7.8 0-21 17.1-38.2 38.1-38.2 21,0 38.2,17.1 38.2,38.2-1.42109e-14,2.9-0.3,5.7-0.9,8.4z"/>
<path d="m64.5,32.5c-8.6,0-15.5,7-15.5,15.5s7,15.5 15.5,15.5 15.5-6.9 15.5-15.5-6.9-15.5-15.5-15.5zm0,22.9c-4.1,0-7.4-3.3-7.4-7.4s3.3-7.4 7.4-7.4c4.1,0 7.4,3.3 7.4,7.4s-3.3,7.4-7.4,7.4z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
<g>
<g>
<path d="m64.3,71.6c18,0 32.6-14.6 32.6-32.6s-14.6-32.5-32.6-32.5-32.6,14.6-32.6,32.5 14.6,32.6 32.6,32.6zm0-56.6c13.2,0 24,10.8 24,24s-10.8,24-24,24-24-10.8-24-24 10.8-24 24-24z"/>
<path d="m7.9,122.5h113.2c2.4,0 4.3-1.9 4.3-4.3 0-22.5-18.3-40.9-40.9-40.9h-40c-22.5,0-40.9,18.3-40.9,40.9-1.33227e-15,2.4 1.9,4.3 4.3,4.3zm36.6-36.6h40c16.4,0 29.9,12.2 32,28h-104c2.1-15.7 15.6-28 32-28z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 634 B

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 210 210" style="enable-background:new 0 0 210 210;" xml:space="preserve">
<g id="XMLID_12_">
<path id="XMLID_13_" d="M195,0h-20c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V15
C210,6.716,203.284,0,195,0z"/>
<path id="XMLID_14_" d="M115,0H95c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V15
C130,6.716,123.284,0,115,0z"/>
<path id="XMLID_15_" d="M35,0H15C6.716,0,0,6.716,0,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V15
C50,6.716,43.284,0,35,0z"/>
<path id="XMLID_16_" d="M195,160h-20c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15v-20
C210,166.716,203.284,160,195,160z"/>
<path id="XMLID_17_" d="M115,160H95c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15v-20
C130,166.716,123.284,160,115,160z"/>
<path id="XMLID_18_" d="M35,160H15c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15v-20
C50,166.716,43.284,160,35,160z"/>
<path id="XMLID_19_" d="M195,80h-20c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V95
C210,86.716,203.284,80,195,80z"/>
<path id="XMLID_20_" d="M115,80H95c-8.284,0-15,6.716-15,15v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V95
C130,86.716,123.284,80,115,80z"/>
<path id="XMLID_21_" d="M35,80H15C6.716,80,0,86.716,0,95v20c0,8.284,6.716,15,15,15h20c8.284,0,15-6.716,15-15V95
C50,86.716,43.284,80,35,80z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,15 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button } from 'ui'
import { reactAutoBind } from 'auto-bind2'
export class ValidatedButton extends React.Component {
export default class BoundButton extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
width: PropTypes.number,
size: PropTypes.string,
content: PropTypes.string,
validator: PropTypes.object.isRequired,
binder: PropTypes.object.isRequired,
submit: PropTypes.bool,
color: PropTypes.string,
onClick: PropTypes.func
@@ -17,27 +18,27 @@ export class ValidatedButton extends React.Component {
constructor(props) {
super(props)
this.updateValue = this.updateValue.bind(this)
reactAutoBind(this)
let { name, validator } = this.props
let { name, binder } = this.props
validator.addListener(name, this.updateValue)
this.state = validator.getField(name)
binder.addListener(name, this.updateValue)
this.state = binder.getFieldState(name)
}
updateValue(name) {
this.setState(this.props.validator.getField(name))
this.setState(this.props.binder.getFieldState(name))
}
componentWillUnmount() {
this.props.validator.removeListener(this.props.name, this.updateValue)
this.props.binder.removeListener(this.props.name, this.updateValue)
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.props.validator.removeListener(this.props.name, this.updateValue)
nextProps.validator.addListener(nextProps.name, this.updateValue)
this.setState(nextProps.validator.getField(nextProps.name))
if (nextProps.binder !== this.props.binder) {
this.props.binder.removeListener(this.props.name, this.updateValue)
nextProps.binder.addListener(nextProps.name, this.updateValue)
this.setState(nextProps.binder.getFieldState(nextProps.name))
}
}

View File

@@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Checkbox } from 'ui'
import { reactAutoBind } from 'auto-bind2'
export default class BoundCheckbox extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
binder: PropTypes.object.isRequired,
}
constructor(props) {
super(props)
reactAutoBind(this)
this.state = props.binder.getFieldState(props.name)
}
handleChange(e, data) {
const { binder, name } = this.props
const state = binder.getFieldState(name)
if (!state.readOnly && !state.disabled) {
this.setState(binder.updateFieldValue(name, data.checked))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.binder !== this.props.binder) {
this.setState(nextProps.binder.getFieldState(nextProps.name))
}
}
render() {
return (
<Checkbox value={!!this.state.value} name={this.props.name} label={this.props.label} />
)
}
}

View File

@@ -2,24 +2,22 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Text, Button, Icon } from 'ui'
// This is a validated component with a value that cannot change itself and is specialized
export class ValidatedEmailIcon extends React.Component {
export default class BoundEmailIcon extends React.Component {
static propTypes = {
name: PropTypes.string,
validator: PropTypes.object,
binder: PropTypes.object,
width: PropTypes.number,
onClick: PropTypes.func
}
constructor(props) {
super(props)
this.state = props.validator.getField('emailValidated')
this.state = props.binder.getField('emailValidated')
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.setState(nextProps.validator.getField(nextProps.name))
if (nextProps.binder !== this.props.binder) {
this.setState(nextProps.binder.getField(nextProps.name))
}
}
@@ -28,14 +26,14 @@ export class ValidatedEmailIcon extends React.Component {
return (
<div width={this.props.width}>
<Text>&nbsp;</Text>
<Icon name='mail' color='green' size='big' className='mail-validated-icon' />
<Icon name='mail' color='green' size='big' />
</div>
)
} else {
return (
<div width={this.props.width}>
<Text>&nbsp;</Text>
<Button fluid icon='mail outline' color='red' labelPosition='left'
<Button icon='mail outline' color='red' labelPosition='left'
content='Resend Email' onClick={this.props.onClick} disabled={this.state.disabled} />
</div>
)

View File

@@ -1,38 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Input, Text } from 'ui'
import { reactAutoBind } from 'auto-bind2'
// This is an example of a validated component with a value that changes itself
export class ValidatedInput extends React.Component {
export default class BoundInput extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
message: PropTypes.string,
label: PropTypes.string,
validator: PropTypes.object.isRequired,
binder: PropTypes.object.isRequired,
password: PropTypes.bool,
placeholder: PropTypes.string
}
constructor(props) {
super(props)
this.state = props.validator.getField(props.name)
this.handleChange = this.handleChange.bind(this)
reactAutoBind(this)
this.state = props.binder.getFieldState(props.name)
}
handleChange(e, data) {
const { validator, name } = this.props
const state = validator.getField(name)
const { binder, name } = this.props
const state = binder.getFieldState(name)
if (!state.readOnly && !state.disabled) {
this.setState(validator.updateValue(name, data.value))
this.setState(binder.updateFieldValue(name, data.value))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.validator !== this.props.validator) {
this.setState(nextProps.validator.getField(nextProps.name))
if (nextProps.binder !== this.props.binder) {
this.setState(nextProps.binder.getFieldState(nextProps.name))
}
}

View File

@@ -18,6 +18,7 @@ class Box extends Component {
return (
<div style={[
{ height: '100%', width: '100%' },
color && { backgroundColor: color },
border ? { border } : { borderTop, borderBottom, borderLeft, borderRight }]}>
{children}

View File

@@ -31,9 +31,9 @@ class Checkbox extends Component {
</div>
<span style={{
verticalAlign: 'top',
fontSize: fontInfo.size['medium'],
fontSize: fontInfo.size.medium,
fontFamily: fontInfo.family,
color: fontInfo.color['normal'],
color: fontInfo.color.normal,
marginLeft: 10
}}>{this.props.label}</span>
</div>

View File

@@ -12,7 +12,7 @@ class Column extends Component {
const { children, minHeight } = this.props
return (
<div style={{ display: 'flex', minHeight, flexDirection: 'column' }}>{children}</div>
<div style={{ width: '100%', display: 'flex', minHeight, flexDirection: 'column' }}>{children}</div>
)
}
}
@@ -21,15 +21,21 @@ Column.Item = Radium(class StackLayoutItem extends Component {
static propTypes = {
children: PropTypes.node,
minHeight: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
height: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
paddingTop: PropTypes.number,
paddingBottom: PropTypes.number,
padding: PropTypes.number,
grow: PropTypes.bool
}
render() {
const { children, grow, minHeight } = this.props
const { children, grow, height, minHeight, padding, paddingTop, paddingBottom } = this.props
const flexGrow = grow ? 1 : null
return (
<div style={{ minHeight, flexGrow }}>{children}</div>
<div style={{ height, minHeight, flexGrow, paddingTap: paddingTop || padding, paddingBottom: paddingBottom || padding }}>
{children}
</div>
)
}
})

View File

@@ -0,0 +1,25 @@
import Radium from 'radium'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import style from './HeaderButton.style'
import { Icon } from 'ui'
class HeaderButton extends Component {
static propTypes = {
icon: PropTypes.string,
onClick: PropTypes.func,
size: PropTypes.number
}
render() {
const { onClick, icon, size } = this.props
return (
<button type='button' style={[{ height: size, width: size }, style.base]} onClick={onClick}>
<Icon name={icon} size={size} />
</button>
)
}
}
export default Radium(HeaderButton)

View File

@@ -0,0 +1,15 @@
import { colorInfo } from './style'
export default {
base: {
background: colorInfo.headerButtonBackground,
verticalAlign: 'middle',
borderWidth: 0,
padding: '0 0 0 0',
outline: 'none',
':hover': {
background: colorInfo.headerButtonBackgroundHover,
}
}
}

View File

@@ -1,28 +1,32 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { sizeInfo } from './style'
// See https://www.flaticon.com/packs/web-button-compilation for more icons
// See https://www.flaticon.com/packs/free-basic-ui-elements
export default class Icon extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
margin: PropTypes.number
}
static defaultProps = {
size: 50,
margin: 5,
name: 'shapes'
size: 50
}
static svgs = {
logout: require('icons/logout.svg'),
shapes: require('icons/shapes.svg')
profile: require('icons/profile.svg'),
placeholder: require('icons/placeholder.svg'),
}
render() {
return <img style={{ width: this.props.size, height: this.props.size, margin: this.props.margin }}
src={Icon.svgs[this.props.name]} />
let { size, name } = this.props
let source = Icon.svgs[name] || Icon.svgs['placeholder']
const margin = sizeInfo.iconMargin
size -= margin * 2
return <img style={{ width: size, height: size, margin }} src={source} />
}
}

View File

@@ -1,18 +1,24 @@
import Radium from 'radium'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { sizeInfo } from './style'
class Image extends Component {
static propTypes = {
source: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
margin: PropTypes.number
}
render() {
let { source, width, height } = this.props
const margin = sizeInfo.imageMargin
width = width ? (width - margin * 2) : null
height = height ? (height - margin * 2) : null
return (
<img src={this.props.source} style={{ width: this.props.width, height: this.props.height, margin: this.props.margin }} />
<img src={source} style={{ width, height, margin }} />
)
}
}

View File

@@ -12,7 +12,7 @@ class Row extends Component {
const { children, minWidth } = this.props
return (
<div style={{ display: 'flex', minWidth, flexDirection: 'row' }}>{children}</div>
<div style={{ height: '100%', display: 'flex', minWidth, flexDirection: 'row' }}>{children}</div>
)
}
}

View File

@@ -1,5 +1,6 @@
export { default as Box } from './Box'
export { default as Button } from './Button'
export { default as HeaderButton } from './HeaderButton'
export { default as Checkbox } from './Checkbox'
export { default as Input } from './Input'
export { default as Image } from './Image'
@@ -13,3 +14,7 @@ export { default as Dimmer } from './Dimmer'
export { default as Loader } from './Loader'
export { default as Row } from './Row'
export { default as Column } from './Column'
export { default as BoundButton } from './BoundButton'
export { default as BoundCheckbox } from './BoundCheckbox'
export { default as BoundInput } from './BoundInput'
export { default as BoundEmailIcon } from './BoundEmailIcon'

View File

@@ -3,7 +3,16 @@ export const colorInfo = {
alertText: '#FF0000',
grayText: '#B0B0B0',
buttonBackground: '#3498DB',
buttonBackgroundHover: '#3CB0FD'
buttonBackgroundHover: '#3CB0FD',
headerButtonBackground: '#FAFAFA',
headerButtonBackgroundHover: '#DADADA',
}
export const sizeInfo = {
headerHeight: 60,
imageMargin: 5, // The margin around images
iconMargin: 10, // The margin around icons
headerBorderWidth: 1
}
export const fontInfo = {