From 73b5cf6caaae95558cb86192f9e56c3b41474a4d Mon Sep 17 00:00:00 2001 From: John Lyon-Smith Date: Tue, 27 Feb 2018 12:16:27 -0800 Subject: [PATCH] Basic UI elements in place --- mobile/.expo/packager-info.json | 2 +- website/package-lock.json | 46 +++++ website/package.json | 1 + website/src/App.js | 18 +- website/src/Auth/ConfirmEmail.js | 32 +-- website/src/Auth/ForgotPassword.js | 58 +++--- website/src/Auth/Login.js | 62 +++--- website/src/Auth/ResetPassword.js | 56 +++--- website/src/Dialog/index.js | 5 - website/src/Footer/Footer.js | 9 - website/src/Footer/Footer.scss | 14 -- website/src/Footer/index.js | 1 - .../ChangeEmailModal.js} | 24 +-- .../ChangePasswordModal.js} | 34 ++-- .../MessageModal.js} | 2 +- .../WaitDialog.js => Modal/WaitModal.js} | 2 +- .../YesNoMessageModal.js} | 2 +- website/src/Modal/index.js | 5 + website/src/Profile/Profile.js | 76 +++---- website/src/Profile/ProfileForm.js | 32 +-- website/src/Users/UserForm.js | 111 ++++------- website/src/Users/UserForm.scss | 8 - website/src/Users/UserList.js | 1 - website/src/Users/UserList.scss | 21 -- website/src/Users/Users.js | 104 +++++----- .../src/Validated/ValidatedActionsButton.js | 59 ------ website/src/Validated/ValidatedCheckbox.js | 41 ---- website/src/Validated/ValidatedContainer.js | 51 ----- website/src/Validated/ValidatedDropdown.js | 47 ----- website/src/Validated/Validator.js | 188 ------------------ website/src/Validated/index.js | 7 - website/src/assets/icons/logout.svg | 63 +----- website/src/assets/icons/placeholder.svg | 9 + website/src/assets/icons/profile.svg | 9 + website/src/assets/icons/shapes.svg | 55 ----- .../ValidatedButton.js => ui/BoundButton.js} | 25 +-- website/src/ui/BoundCheckbox.js | 39 ++++ .../BoundEmailIcon.js} | 16 +- .../ValidatedInput.js => ui/BoundInput.js} | 22 +- website/src/ui/Box.js | 1 + website/src/ui/Checkbox.js | 4 +- website/src/ui/Column.js | 12 +- website/src/ui/HeaderButton.js | 25 +++ website/src/ui/HeaderButton.style.js | 15 ++ website/src/ui/Icon.js | 20 +- website/src/ui/Image.js | 10 +- website/src/ui/Row.js | 2 +- website/src/ui/index.js | 5 + website/src/ui/style.js | 11 +- 49 files changed, 525 insertions(+), 937 deletions(-) delete mode 100644 website/src/Dialog/index.js delete mode 100644 website/src/Footer/Footer.js delete mode 100644 website/src/Footer/Footer.scss delete mode 100644 website/src/Footer/index.js rename website/src/{Dialog/ChangeEmailDialog.js => Modal/ChangeEmailModal.js} (66%) rename website/src/{Dialog/ChangePasswordDialog.js => Modal/ChangePasswordModal.js} (64%) rename website/src/{Dialog/MessageDialog.js => Modal/MessageModal.js} (93%) rename website/src/{Dialog/WaitDialog.js => Modal/WaitModal.js} (87%) rename website/src/{Dialog/YesNoMessageDialog.js => Modal/YesNoMessageModal.js} (95%) create mode 100644 website/src/Modal/index.js delete mode 100644 website/src/Users/UserForm.scss delete mode 100644 website/src/Users/UserList.scss delete mode 100644 website/src/Validated/ValidatedActionsButton.js delete mode 100644 website/src/Validated/ValidatedCheckbox.js delete mode 100644 website/src/Validated/ValidatedContainer.js delete mode 100644 website/src/Validated/ValidatedDropdown.js delete mode 100644 website/src/Validated/Validator.js delete mode 100644 website/src/Validated/index.js create mode 100644 website/src/assets/icons/placeholder.svg create mode 100644 website/src/assets/icons/profile.svg delete mode 100644 website/src/assets/icons/shapes.svg rename website/src/{Validated/ValidatedButton.js => ui/BoundButton.js} (59%) create mode 100644 website/src/ui/BoundCheckbox.js rename website/src/{Users/ValidatedEmailIcon.js => ui/BoundEmailIcon.js} (56%) rename website/src/{Validated/ValidatedInput.js => ui/BoundInput.js} (62%) create mode 100644 website/src/ui/HeaderButton.js create mode 100644 website/src/ui/HeaderButton.style.js diff --git a/mobile/.expo/packager-info.json b/mobile/.expo/packager-info.json index dca2609..dd224fa 100644 --- a/mobile/.expo/packager-info.json +++ b/mobile/.expo/packager-info.json @@ -1,5 +1,5 @@ { "expoServerPort": 19000, "packagerPort": 19001, - "packagerPid": 76299 + "packagerPid": 12109 } \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index 3a208ef..de4f82c 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -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", diff --git a/website/package.json b/website/package.json index d64d870..b2399e1 100644 --- a/website/package.json +++ b/website/package.json @@ -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" diff --git a/website/src/App.js b/website/src/App.js index 0017797..ff9685f 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -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 ( - - + + - +   - + + @@ -43,7 +49,7 @@ export class App extends React.Component { - + {'v' + versionInfo.version} {versionInfo.copyright} diff --git a/website/src/Auth/ConfirmEmail.js b/website/src/Auth/ConfirmEmail.js index e9634cd..59b0827 100644 --- a/website/src/Auth/ConfirmEmail.js +++ b/website/src/Auth/ConfirmEmail.js @@ -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 (
- + - +
) } diff --git a/website/src/Auth/ForgotPassword.js b/website/src/Auth/ForgotPassword.js index f6c04fa..a00d12c 100644 --- a/website/src/Auth/ForgotPassword.js +++ b/website/src/Auth/ForgotPassword.js @@ -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 { Forgotten Password
- The email address of an existing user to send the password reset link to. - Submit + Submit
- + - + ) } diff --git a/website/src/Auth/Login.js b/website/src/Auth/Login.js index 857a791..dc8d9c9 100644 --- a/website/src/Auth/Login.js +++ b/website/src/Auth/Login.js @@ -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 { - - + @@ -119,8 +119,8 @@ export class Login extends React.Component { - + @@ -129,7 +129,7 @@ export class Login extends React.Component {
- +
@@ -144,12 +144,12 @@ export class Login extends React.Component {
  - + - + ) } diff --git a/website/src/Auth/ResetPassword.js b/website/src/Auth/ResetPassword.js index 67e2b89..dc4e74d 100644 --- a/website/src/Auth/ResetPassword.js +++ b/website/src/Auth/ResetPassword.js @@ -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 { Reset Password - + width={16} binder={this.state.binder} /> - + width={16} binder={this.state.binder} /> @@ -85,19 +85,19 @@ export class ResetPassword extends React.Component { - + - + - + ) } diff --git a/website/src/Dialog/index.js b/website/src/Dialog/index.js deleted file mode 100644 index b90cf5c..0000000 --- a/website/src/Dialog/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { WaitDialog } from './WaitDialog' -export { YesNoMessageDialog } from './YesNoMessageDialog' -export { MessageDialog } from './MessageDialog' -export { ChangePasswordDialog } from './ChangePasswordDialog' -export { ChangeEmailDialog } from './ChangeEmailDialog' diff --git a/website/src/Footer/Footer.js b/website/src/Footer/Footer.js deleted file mode 100644 index 4e77074..0000000 --- a/website/src/Footer/Footer.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -import './Footer.scss' -import { VersionInfo } from '../version' - -export const Footer = () => ( -
- v{VersionInfo.fullVersion}. © {VersionInfo.startYear} Deighton. All rights reserved. -
-) diff --git a/website/src/Footer/Footer.scss b/website/src/Footer/Footer.scss deleted file mode 100644 index 79e5f64..0000000 --- a/website/src/Footer/Footer.scss +++ /dev/null @@ -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; -} diff --git a/website/src/Footer/index.js b/website/src/Footer/index.js deleted file mode 100644 index 0302b4a..0000000 --- a/website/src/Footer/index.js +++ /dev/null @@ -1 +0,0 @@ -export { Footer } from './Footer' diff --git a/website/src/Dialog/ChangeEmailDialog.js b/website/src/Modal/ChangeEmailModal.js similarity index 66% rename from website/src/Dialog/ChangeEmailDialog.js rename to website/src/Modal/ChangeEmailModal.js index e6c13bf..4837ebd 100644 --- a/website/src/Dialog/ChangeEmailDialog.js +++ b/website/src/Modal/ChangeEmailModal.js @@ -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 { Change Email - + binder={this.state.binder} /> - + OK - + diff --git a/website/src/Dialog/ChangePasswordDialog.js b/website/src/Modal/ChangePasswordModal.js similarity index 64% rename from website/src/Dialog/ChangePasswordDialog.js rename to website/src/Modal/ChangePasswordModal.js index 0aae5f9..c3b9690 100644 --- a/website/src/Dialog/ChangePasswordDialog.js +++ b/website/src/Modal/ChangePasswordModal.js @@ -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 { - + width={8} binder={this.state.binder} /> - + width={8} binder={this.state.binder} /> - + width={8} binder={this.state.binder} /> - + OK - + diff --git a/website/src/Dialog/MessageDialog.js b/website/src/Modal/MessageModal.js similarity index 93% rename from website/src/Dialog/MessageDialog.js rename to website/src/Modal/MessageModal.js index d9aa17b..5b144e4 100644 --- a/website/src/Dialog/MessageDialog.js +++ b/website/src/Modal/MessageModal.js @@ -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, diff --git a/website/src/Dialog/WaitDialog.js b/website/src/Modal/WaitModal.js similarity index 87% rename from website/src/Dialog/WaitDialog.js rename to website/src/Modal/WaitModal.js index 027611e..5a3cd57 100644 --- a/website/src/Dialog/WaitDialog.js +++ b/website/src/Modal/WaitModal.js @@ -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 } diff --git a/website/src/Dialog/YesNoMessageDialog.js b/website/src/Modal/YesNoMessageModal.js similarity index 95% rename from website/src/Dialog/YesNoMessageDialog.js rename to website/src/Modal/YesNoMessageModal.js index e1cf7ad..8da4b00 100644 --- a/website/src/Dialog/YesNoMessageDialog.js +++ b/website/src/Modal/YesNoMessageModal.js @@ -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, diff --git a/website/src/Modal/index.js b/website/src/Modal/index.js new file mode 100644 index 0000000..d2ffcfe --- /dev/null +++ b/website/src/Modal/index.js @@ -0,0 +1,5 @@ +export { WaitModal } from './WaitModal' +export { YesNoMessageModal } from './YesNoMessageModal' +export { MessageModal } from './MessageModal' +export { ChangePasswordModal } from './ChangePasswordModal' +export { ChangeEmailModal } from './ChangeEmailModal' diff --git a/website/src/Profile/Profile.js b/website/src/Profile/Profile.js index d8453f7..76ccf45 100644 --- a/website/src/Profile/Profile.js +++ b/website/src/Profile/Profile.js @@ -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} /> - + - + - + - + ) } diff --git a/website/src/Profile/ProfileForm.js b/website/src/Profile/ProfileForm.js index 4488a83..2cf6431 100644 --- a/website/src/Profile/ProfileForm.js +++ b/website/src/Profile/ProfileForm.js @@ -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 {
- + - + - + - ) - } else { - return null - } - } -} diff --git a/website/src/Validated/ValidatedCheckbox.js b/website/src/Validated/ValidatedCheckbox.js deleted file mode 100644 index ba96ec5..0000000 --- a/website/src/Validated/ValidatedCheckbox.js +++ /dev/null @@ -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 ( - - ) - } -} diff --git a/website/src/Validated/ValidatedContainer.js b/website/src/Validated/ValidatedContainer.js deleted file mode 100644 index 3e71fcf..0000000 --- a/website/src/Validated/ValidatedContainer.js +++ /dev/null @@ -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 ( -
- {this.props.children} -
- ) - } else { - return null - } - } -} diff --git a/website/src/Validated/ValidatedDropdown.js b/website/src/Validated/ValidatedDropdown.js deleted file mode 100644 index 5968daf..0000000 --- a/website/src/Validated/ValidatedDropdown.js +++ /dev/null @@ -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 ( -
- - - {this.props.message} -
- ) - } -} diff --git a/website/src/Validated/Validator.js b/website/src/Validated/Validator.js deleted file mode 100644 index a0b0d61..0000000 --- a/website/src/Validated/Validator.js +++ /dev/null @@ -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 - } - }) - } -} diff --git a/website/src/Validated/index.js b/website/src/Validated/index.js deleted file mode 100644 index 7386654..0000000 --- a/website/src/Validated/index.js +++ /dev/null @@ -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' diff --git a/website/src/assets/icons/logout.svg b/website/src/assets/icons/logout.svg index ab6bfca..f431a20 100644 --- a/website/src/assets/icons/logout.svg +++ b/website/src/assets/icons/logout.svg @@ -1,54 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/website/src/assets/icons/placeholder.svg b/website/src/assets/icons/placeholder.svg new file mode 100644 index 0000000..db1f6ad --- /dev/null +++ b/website/src/assets/icons/placeholder.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/src/assets/icons/profile.svg b/website/src/assets/icons/profile.svg new file mode 100644 index 0000000..8fe66d7 --- /dev/null +++ b/website/src/assets/icons/profile.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/src/assets/icons/shapes.svg b/website/src/assets/icons/shapes.svg deleted file mode 100644 index f6b16b6..0000000 --- a/website/src/assets/icons/shapes.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/src/Validated/ValidatedButton.js b/website/src/ui/BoundButton.js similarity index 59% rename from website/src/Validated/ValidatedButton.js rename to website/src/ui/BoundButton.js index 3242a4f..10b4c96 100644 --- a/website/src/Validated/ValidatedButton.js +++ b/website/src/ui/BoundButton.js @@ -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)) } } diff --git a/website/src/ui/BoundCheckbox.js b/website/src/ui/BoundCheckbox.js new file mode 100644 index 0000000..d091f6d --- /dev/null +++ b/website/src/ui/BoundCheckbox.js @@ -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 ( + + ) + } +} diff --git a/website/src/Users/ValidatedEmailIcon.js b/website/src/ui/BoundEmailIcon.js similarity index 56% rename from website/src/Users/ValidatedEmailIcon.js rename to website/src/ui/BoundEmailIcon.js index feff83f..775211c 100644 --- a/website/src/Users/ValidatedEmailIcon.js +++ b/website/src/ui/BoundEmailIcon.js @@ -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 (
  - +
) } else { return (
  -
) diff --git a/website/src/Validated/ValidatedInput.js b/website/src/ui/BoundInput.js similarity index 62% rename from website/src/Validated/ValidatedInput.js rename to website/src/ui/BoundInput.js index d40b4b7..eb7745f 100644 --- a/website/src/Validated/ValidatedInput.js +++ b/website/src/ui/BoundInput.js @@ -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)) } } diff --git a/website/src/ui/Box.js b/website/src/ui/Box.js index c73bbe7..d696035 100644 --- a/website/src/ui/Box.js +++ b/website/src/ui/Box.js @@ -18,6 +18,7 @@ class Box extends Component { return (
{children} diff --git a/website/src/ui/Checkbox.js b/website/src/ui/Checkbox.js index 4db2b76..1d3d72b 100644 --- a/website/src/ui/Checkbox.js +++ b/website/src/ui/Checkbox.js @@ -31,9 +31,9 @@ class Checkbox extends Component {
{this.props.label} diff --git a/website/src/ui/Column.js b/website/src/ui/Column.js index d244695..b6d6ed6 100644 --- a/website/src/ui/Column.js +++ b/website/src/ui/Column.js @@ -12,7 +12,7 @@ class Column extends Component { const { children, minHeight } = this.props return ( -
{children}
+
{children}
) } } @@ -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 ( -
{children}
+
+ {children} +
) } }) diff --git a/website/src/ui/HeaderButton.js b/website/src/ui/HeaderButton.js new file mode 100644 index 0000000..8e80808 --- /dev/null +++ b/website/src/ui/HeaderButton.js @@ -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 ( + + ) + } +} + +export default Radium(HeaderButton) diff --git a/website/src/ui/HeaderButton.style.js b/website/src/ui/HeaderButton.style.js new file mode 100644 index 0000000..21bd69f --- /dev/null +++ b/website/src/ui/HeaderButton.style.js @@ -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, + } + } +} diff --git a/website/src/ui/Icon.js b/website/src/ui/Icon.js index d1d7777..fc8f09e 100644 --- a/website/src/ui/Icon.js +++ b/website/src/ui/Icon.js @@ -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 + let { size, name } = this.props + let source = Icon.svgs[name] || Icon.svgs['placeholder'] + const margin = sizeInfo.iconMargin + + size -= margin * 2 + + return } } diff --git a/website/src/ui/Image.js b/website/src/ui/Image.js index 5d347b9..3523980 100644 --- a/website/src/ui/Image.js +++ b/website/src/ui/Image.js @@ -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 ( - + ) } } diff --git a/website/src/ui/Row.js b/website/src/ui/Row.js index a5cca28..63250fb 100644 --- a/website/src/ui/Row.js +++ b/website/src/ui/Row.js @@ -12,7 +12,7 @@ class Row extends Component { const { children, minWidth } = this.props return ( -
{children}
+
{children}
) } } diff --git a/website/src/ui/index.js b/website/src/ui/index.js index d7fda85..c464e39 100644 --- a/website/src/ui/index.js +++ b/website/src/ui/index.js @@ -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' diff --git a/website/src/ui/style.js b/website/src/ui/style.js index f392d7e..f0f993a 100644 --- a/website/src/ui/style.js +++ b/website/src/ui/style.js @@ -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 = {