diff --git a/server/package-lock.json b/server/package-lock.json index 215ff09..bfde3d1 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -223,6 +223,11 @@ "resolved": "https://registry.npmjs.org/auto-bind2/-/auto-bind2-1.0.3.tgz", "integrity": "sha512-+br9nya9M8ayHjai7m9rdpRxuEr8xcYRDrIp7HybNe0ixUHbc1kDiWXKMb0ldsfWb9Zi+SqJ9JfjW8nTkYD0QQ==" }, + "autobind-decorator": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.1.0.tgz", + "integrity": "sha512-bgyxeRi1R2Q8kWpHsb1c+lXCulbIAHsyZRddaS+agAUX3hFUVZMociwvRgeZi1zWvfqEEjybSv4zxWvFV8ydQQ==" + }, "aws-sdk": { "version": "2.197.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.197.0.tgz", diff --git a/server/package.json b/server/package.json index cf36377..cbdb303 100644 --- a/server/package.json +++ b/server/package.json @@ -22,6 +22,7 @@ "amqplib": "^0.5.1", "app-root-path": "^2.0.1", "auto-bind2": "^1.0.3", + "autobind-decorator": "^2.1.0", "aws-sdk": "^2.98.0", "body-parser": "^1.17.1", "canvas": "^1.6.7", diff --git a/website/src/API.js b/website/src/API.js index 81aa944..fba0264 100644 --- a/website/src/API.js +++ b/website/src/API.js @@ -237,15 +237,12 @@ class API extends EventEmitter { return this.post('/auth/password/reset', passwords) } - getUser(_id) { - return this.get('/users/' + _id) + getUser(id) { + return this.get('/users/' + id) } listUsers() { return this.get('/users') } - listBrokerUsers() { - return this.get('/users/brokers') - } createUser(user) { return this.post('/users', user) } @@ -263,11 +260,8 @@ class API extends EventEmitter { }) }) } - deleteUser(_id) { - return this.delete('/users/' + _id) - } - setUserImage(details) { - return this.put('/users/set-image', details) + deleteUser(id) { + return this.delete('/users/' + id) } enterRoom(roomName) { return this.put('/users/enter-room/' + (roomName || '')) @@ -276,6 +270,22 @@ class API extends EventEmitter { return this.put('/users/leave-room') } + getTeam(id) { + return this.get('/teams/' + id) + } + listTeams() { + return this.get('/teams') + } + createTeam(team) { + return this.post('/teams', team) + } + updateTeam(team) { + this.put('/teams', team) + } + deleteTeam(id) { + return this.delete('/teams/' + id) + } + upload(file, progressCallback) { return new Promise((resolve, reject) => { const chunkSize = 32 * 1024 diff --git a/website/src/App.js b/website/src/App.js index fc6bea7..0f165c0 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -3,6 +3,7 @@ import { Login, Logout, ResetPassword, ForgotPassword, ConfirmEmail, ProtectedRo import { Home } from './Home' import { Profile } from './Profile' import { Users } from './Users' +import { Teams } from './Teams' import { HeaderButton, HeaderText, Column, Row, Text, Box } from 'ui' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import logoImage from 'images/logo.png' @@ -99,7 +100,7 @@ export class App extends Component { ()} /> ()} /> - + ()} /> ()} /> diff --git a/website/src/Home/Home.js b/website/src/Home/Home.js index e6ce9ad..37fe67a 100644 --- a/website/src/Home/Home.js +++ b/website/src/Home/Home.js @@ -29,7 +29,7 @@ export class Home extends Component { - + (this.props.history.push('/teams'))} /> diff --git a/website/src/Teams/TeamForm.js b/website/src/Teams/TeamForm.js new file mode 100644 index 0000000..a928662 --- /dev/null +++ b/website/src/Teams/TeamForm.js @@ -0,0 +1,214 @@ +import React from 'react' +import PropTypes from 'prop-types' +import autobind from 'autobind-decorator' +import { regExpPattern } from 'regexp-pattern' +import { api } from 'src/API' +import { Row, Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon, DropdownList } from 'ui' +import { FormBinder } from 'react-form-binder' +import { sizeInfo } from 'ui/style' + +export class TeamForm extends React.Component { + static propTypes = { + user: PropTypes.object, + onSave: PropTypes.func, + onRemove: PropTypes.func, + onModifiedChanged: PropTypes.func, + onChangeEmail: PropTypes.func, + onResendEmail: PropTypes.func + } + + static bindings = { + email: { + isValid: (r, v) => (regExpPattern.email.test(v)), + isDisabled: (r) => (r._id) + }, + emailValidated: { + initValue: false, + isDisabled: (r) => (!r._id) + }, + changeEmail: { + noValue: true, + isDisabled: (r) => (!r._id) + }, + resendEmail: { + noValue: true, + isDisabled: (r) => (!r._id || !!r.getFieldValue('emailValidated')) + }, + firstName: { + isValid: (r, v) => (v !== '') + }, + lastName: { + isValid: (r, v) => (v !== '') + }, + administrator: { + isValid: (r, v) => true, + initValue: false, + isDisabled: (r) => (api.loggedInTeam._id === r._id), // Adding a new user + alwaysGet: true, + }, + remove: { + noValue: true, + isVisible: (r) => (r._id), + isDisabled: (r) => (api.loggedInTeam._id === r._id) + }, + reset: { + noValue: true, + isDisabled: (r) => { + return !r.anyModified + } + }, + submit: { + noValue: true, + isDisabled: (r) => (!r.anyModified || !r.allValid), + }, + } + + constructor(props) { + super(props) + this.state = { + binder: new FormBinder(this.props.user, TeamForm.bindings, this.props.onModifiedChanged) + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.user !== this.props.user) { + this.setState({ + binder: new FormBinder(nextProps.user, TeamForm.bindings, nextProps.onModifiedChanged) + }) + } + } + + @autobind + handleSubmit(e) { + e.preventDefault() + + let obj = this.state.binder.getModifiedFieldValues() + + if (obj) { + this.props.onSave(obj) + } + } + + @autobind + handleReset() { + const { user, onModifiedChanged } = this.props + + this.setState({ binder: new FormBinder(user, TeamForm.bindings, onModifiedChanged) }) + + if (onModifiedChanged) { + onModifiedChanged(false) + } + } + + @autobind + handleChangeEmail() { + this.props.onChangeEmail() + } + + @autobind + handleResendEmail() { + this.props.onResendEmail() + } + + render() { + const { binder } = this.state + const teams = [ + { id: 1, name: 'Sign of the Times' }, + { id: 2, name: 'Trash Monsters' }, + { id: 3, name: 'The Bigger Picker Uppers' }, + { id: 4, name: 'Carcass Masters' }, + { id: 5, name: 'Dust Bunnies' }, + { id: 6, name: 'Pavement Busters' }, + { id: 7, name: 'Don\'t Hug That Tree' }, + { id: 8, name: 'Broken Swingers' }, + ] + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + + {item.name} + + )} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) + } +} diff --git a/website/src/Teams/TeamFormPlaceholder.js b/website/src/Teams/TeamFormPlaceholder.js new file mode 100644 index 0000000..3f54941 --- /dev/null +++ b/website/src/Teams/TeamFormPlaceholder.js @@ -0,0 +1,14 @@ +import React from 'react' +import { Column, Text } from 'ui' + +export const TeamFormPlaceholder = () => ( + + + + Select a team to view details here +
+ Or 'Add New Team' +
+ +
+) diff --git a/website/src/Teams/TeamList.js b/website/src/Teams/TeamList.js new file mode 100644 index 0000000..8871976 --- /dev/null +++ b/website/src/Teams/TeamList.js @@ -0,0 +1,55 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Column, List, Button } from 'ui' +import { sizeInfo } from 'ui/style' + +export class TeamList extends React.Component { + static propTypes = { + users: PropTypes.array, + onUserListClick: PropTypes.func, + selectedUser: PropTypes.object, + selectionModified: PropTypes.bool, + onAddNewUser: PropTypes.func + } + + constructor(props) { + super(props) + this.state = { + users: null + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.users !== this.props.users) { + this.setState({ users: nextProps.users }) + } + } + + render() { + const { selectedUser, selectionModified } = this.props + const { users } = this.state + + return ( + + + { + return ( + (this.props.onUserListClick(e, index))} + active={user === this.props.selectedUser}> + + + { user._id ? user.firstName + ' ' + user.lastName : '[New User]' } + + { user === selectedUser && selectionModified ? : null } + + ) + }} /> + + + +