Basic team page

This commit is contained in:
John Lyon-Smith
2018-03-26 12:56:45 -07:00
parent 4c4f899c6a
commit ad32653633
5 changed files with 63 additions and 154 deletions

View File

@@ -1,55 +1,25 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import autobind from 'autobind-decorator' import autobind from 'autobind-decorator'
import { regExpPattern } from 'regexp-pattern' import { Row, Column, BoundInput, BoundButton, List } from 'ui'
import { api } from 'src/API'
import { Row, Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon, DropdownList } from 'ui'
import { FormBinder } from 'react-form-binder' import { FormBinder } from 'react-form-binder'
import { sizeInfo } from 'ui/style' import { sizeInfo } from 'ui/style'
export class TeamForm extends React.Component { export class TeamForm extends React.Component {
static propTypes = { static propTypes = {
user: PropTypes.object, team: PropTypes.object,
onSave: PropTypes.func, onSave: PropTypes.func,
onRemove: PropTypes.func, onRemove: PropTypes.func,
onModifiedChanged: PropTypes.func, onModifiedChanged: PropTypes.func,
onChangeEmail: PropTypes.func,
onResendEmail: PropTypes.func
} }
static bindings = { static bindings = {
email: { name: {
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 !== '') 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: { remove: {
noValue: true, noValue: true,
isVisible: (r) => (r._id), isVisible: (r) => (r._id),
isDisabled: (r) => (api.loggedInTeam._id === r._id)
}, },
reset: { reset: {
noValue: true, noValue: true,
@@ -66,14 +36,14 @@ export class TeamForm extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
binder: new FormBinder(this.props.user, TeamForm.bindings, this.props.onModifiedChanged) binder: new FormBinder(this.props.team, TeamForm.bindings, this.props.onModifiedChanged)
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.props.user) { if (nextProps.team !== this.props.team) {
this.setState({ this.setState({
binder: new FormBinder(nextProps.user, TeamForm.bindings, nextProps.onModifiedChanged) binder: new FormBinder(nextProps.team, TeamForm.bindings, nextProps.onModifiedChanged)
}) })
} }
} }
@@ -91,40 +61,21 @@ export class TeamForm extends React.Component {
@autobind @autobind
handleReset() { handleReset() {
const { user, onModifiedChanged } = this.props const { team, onModifiedChanged } = this.props
this.setState({ binder: new FormBinder(user, TeamForm.bindings, onModifiedChanged) }) this.setState({ binder: new FormBinder(team, TeamForm.bindings, onModifiedChanged) })
if (onModifiedChanged) { if (onModifiedChanged) {
onModifiedChanged(false) onModifiedChanged(false)
} }
} }
@autobind
handleChangeEmail() {
this.props.onChangeEmail()
}
@autobind
handleResendEmail() {
this.props.onResendEmail()
}
render() { render() {
const { team } = this.props
const { binder } = this.state 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 ( return (
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='userForm' onSubmit={this.handleSubmit}> <form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='teamForm' onSubmit={this.handleSubmit}>
<Column> <Column>
<Column.Item height={sizeInfo.formColumnSpacing} /> <Column.Item height={sizeInfo.formColumnSpacing} />
<Row> <Row>
@@ -132,60 +83,18 @@ export class TeamForm extends React.Component {
<Row.Item grow> <Row.Item grow>
<Column.Item> <Column.Item>
<Column> <Column>
<Column.Item> <Column.Item grow>
<Row> <BoundInput label='Name' name='name' message='Must not be empty' binder={binder} />
<Row.Item grow>
<BoundInput label='First Name' name='firstName' message='Must not be empty' binder={binder} />
</Row.Item>
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item grow>
<BoundInput label='Last Name' name='lastName' binder={binder} />
</Row.Item>
</Row>
</Column.Item> </Column.Item>
<Column.Item> <Column.Item>
<Row> <List items={team.users} render={(item) => (
<Row.Item grow> <List.Item key={item.id}>
<BoundInput label='Email' name='email' message='Must be a valid email address. Required.' binder={binder} /> <List.Icon name='team' size={sizeInfo.dropdownIconSize} />
</Row.Item> <List.Text>{item.name}</List.Text>
<Row.Item width={sizeInfo.formRowSpacing} /> </List.Item>
<Row.Item>
<BoundEmailIcon name='emailValidated' binder={binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item>
<DropdownList items={teams} render={(item) => (
<DropdownList.Item key={item.id}>
<DropdownList.Icon name='team' size={sizeInfo.dropdownIconSize} />
<DropdownList.Text>{item.name}</DropdownList.Text>
</DropdownList.Item>
)} /> )} />
</Column.Item> </Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} /> <Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item minHeight={sizeInfo.buttonHeight}>
<Row>
<Row.Item>
<BoundButton text='Change Email' name='changeEmail' binder={binder}
width={sizeInfo.buttonWideWidth} onClick={this.handleChangeEmail} />
</Row.Item>
<Row.Item grow />
<Row.Item>
<BoundButton text='Resend Confirmation Email' name='resendEmail' binder={binder}
width={sizeInfo.buttonWideWidth} onClick={this.handleResendEmail} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Row>
<Row.Item>
<BoundCheckbox label={'Administrator'} name='administrator' binder={this.state.binder} />
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item minHeight={sizeInfo.buttonHeight}> <Column.Item minHeight={sizeInfo.buttonHeight}>
<Row> <Row>
<Row.Item> <Row.Item>
@@ -197,7 +106,7 @@ export class TeamForm extends React.Component {
</Row.Item> </Row.Item>
<Row.Item width={sizeInfo.formRowSpacing} /> <Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item> <Row.Item>
<BoundButton submit='userForm' text={binder._id ? 'Save' : 'Add'} name='submit' binder={binder} /> <BoundButton submit='teamForm' text={binder._id ? 'Save' : 'Add'} name='submit' binder={binder} />
</Row.Item> </Row.Item>
</Row> </Row>
</Column.Item> </Column.Item>

View File

@@ -5,49 +5,49 @@ import { sizeInfo } from 'ui/style'
export class TeamList extends React.Component { export class TeamList extends React.Component {
static propTypes = { static propTypes = {
users: PropTypes.array, teams: PropTypes.array,
onUserListClick: PropTypes.func, onTeamListClick: PropTypes.func,
selectedUser: PropTypes.object, selectedTeam: PropTypes.object,
selectionModified: PropTypes.bool, selectionModified: PropTypes.bool,
onAddNewUser: PropTypes.func onAddNewTeam: PropTypes.func
} }
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
users: null teams: null
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.users !== this.props.users) { if (nextProps.teams !== this.props.teams) {
this.setState({ users: nextProps.users }) this.setState({ teams: nextProps.teams })
} }
} }
render() { render() {
const { selectedUser, selectionModified } = this.props const { selectedTeam, selectionModified } = this.props
const { users } = this.state const { teams } = this.state
return ( return (
<Column fillParent> <Column fillParent>
<Column.Item grow> <Column.Item grow>
<List data={users} render={(user, index) => { <List items={teams} render={(user, index) => {
return ( return (
<List.Item key={user._id || '0'} onClick={(e) => (this.props.onUserListClick(e, index))} <List.Item key={user._id || '0'} onClick={(e) => (this.props.onTeamListClick(e, index))}
active={user === this.props.selectedUser}> active={user === this.props.selectedTeam}>
<List.Icon name={user.administrator ? 'admin' : 'profile'} size={sizeInfo.listIcon} /> <List.Icon name={user.administrator ? 'admin' : 'profile'} size={sizeInfo.listIcon} />
<List.Text> <List.Text>
{ user._id ? user.firstName + ' ' + user.lastName : '[New User]' } { user._id ? user.firstName + ' ' + user.lastName : '[New Team]' }
</List.Text> </List.Text>
{ user === selectedUser && selectionModified ? <List.Icon name='edit' size={sizeInfo.listIcon} /> : null } { user === selectedTeam && selectionModified ? <List.Icon name='edit' size={sizeInfo.listIcon} /> : null }
</List.Item> </List.Item>
) )
}} /> }} />
</Column.Item> </Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} /> <Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item height={sizeInfo.buttonHeight}> <Column.Item height={sizeInfo.buttonHeight}>
<Button width='100%' color='inverse' onClick={this.props.onAddNewUser} text='Add New User' /> <Button width='100%' color='inverse' onClick={this.props.onAddNewTeam} text='Add New Team' />
</Column.Item> </Column.Item>
</Column> </Column>
) )

View File

@@ -19,7 +19,7 @@ export class Teams extends Component {
this.state = { this.state = {
modified: false, modified: false,
selectedTeam: null, selectedTeam: null,
users: [], teams: [],
yesNoModal: null, yesNoModal: null,
messageModal: null, messageModal: null,
waitModal: null, waitModal: null,
@@ -31,8 +31,8 @@ export class Teams extends Component {
this.props.changeTitle('Teams') this.props.changeTitle('Teams')
api.listTeams().then((list) => { api.listTeams().then((list) => {
list.items.sort((userA, userB) => (userA.lastName.localeCompare(userB.lastName))) list.items.sort((teamA, teamB) => (teamA.lastName.localeCompare(teamB.lastName)))
this.setState({ users: list.items, selectedTeam: list.items[0] }) // TODO: <- Remove this.setState({ teams: list.items, selectedTeam: list.items[0] }) // TODO: <- Remove
}).catch((error) => { }).catch((error) => {
this.setState({ this.setState({
messageModal: { messageModal: {
@@ -49,39 +49,39 @@ export class Teams extends Component {
} }
removeUnfinishedNewTeam() { removeUnfinishedNewTeam() {
let users = this.state.users let teams = this.state.teams
if (users.length > 0 && !users[0]._id) { if (teams.length > 0 && !teams[0]._id) {
this.setState({ users: this.state.users.slice(1) }) this.setState({ teams: this.state.teams.slice(1) })
} }
} }
@autobind @autobind
handleTeamListClick(e, index) { handleTeamListClick(e, index) {
let user = this.state.users[index] let team = this.state.teams[index]
if (this.state.modified) { if (this.state.modified) {
this.nextSelectedTeam = user this.nextSelectedTeam = team
this.setState({ this.setState({
yesNoModal: { yesNoModal: {
question: 'This user has been modified. Are you sure you would like to navigate away?', question: 'This team has been modified. Are you sure you would like to navigate away?',
onDismiss: this.handleModifiedModalDismiss onDismiss: this.handleModifiedModalDismiss
} }
}) })
} else { } else {
this.setState({ selectedTeam: user }) this.setState({ selectedTeam: team })
this.removeUnfinishedNewTeam() this.removeUnfinishedNewTeam()
} }
} }
@autobind @autobind
handleSave(user) { handleSave(team) {
if (user._id) { if (team._id) {
this.setState({ waitModal: { message: 'Updating Team' } }) this.setState({ waitModal: { message: 'Updating Team' } })
api.updateTeam(user).then((updatedTeam) => { api.updateTeam(team).then((updatedTeam) => {
this.setState({ this.setState({
waitModal: null, waitModal: null,
users: this.state.users.map((user) => (user._id === updatedTeam._id ? updatedTeam : user)), teams: this.state.teams.map((team) => (team._id === updatedTeam._id ? updatedTeam : team)),
modified: false, modified: false,
selectedTeam: updatedTeam selectedTeam: updatedTeam
}) })
@@ -90,17 +90,17 @@ export class Teams extends Component {
waitModal: null, waitModal: null,
messageModal: { messageModal: {
icon: 'hand', icon: 'hand',
message: 'Unable to save the user changes', message: 'Unable to save the team changes',
detail: error.message, detail: error.message,
} }
}) })
}) })
} else { } else {
this.setState({ waitModal: { message: 'Creating Team' } }) this.setState({ waitModal: { message: 'Creating Team' } })
api.createTeam(user).then((createdTeam) => { api.createTeam(team).then((createdTeam) => {
this.setState({ this.setState({
waitModal: false, waitModal: false,
users: this.state.users.map((user) => (!user._id ? createdTeam : user)).sort((a, b) => ( teams: this.state.teams.map((team) => (!team._id ? createdTeam : team)).sort((a, b) => (
a.lastName < b.lastName ? -1 : a.lastName > b.lastName : 0 a.lastName < b.lastName ? -1 : a.lastName > b.lastName : 0
)), )),
modified: false, modified: false,
@@ -111,7 +111,7 @@ export class Teams extends Component {
waitModal: null, waitModal: null,
messageModal: { messageModal: {
icon: 'hand', icon: 'hand',
message: 'Unable to create the user.', message: 'Unable to create the team.',
detail: error.message, detail: error.message,
} }
}) })
@@ -186,7 +186,7 @@ export class Teams extends Component {
handleRemove() { handleRemove() {
this.setState({ this.setState({
yesNoModal: { yesNoModal: {
question: 'Are you sure you want to remove this user? This will also remove them from any teams they belong to.', question: 'Are you sure you want to remove this team? This will also remove them from any teams they belong to.',
onDismiss: this.handleRemoveModalDismiss onDismiss: this.handleRemoveModalDismiss
} }
}) })
@@ -196,14 +196,14 @@ export class Teams extends Component {
handleRemoveModalDismiss(yes) { handleRemoveModalDismiss(yes) {
if (yes) { if (yes) {
const selectedTeamId = this.state.selectedTeam._id const selectedTeamId = this.state.selectedTeam._id
const selectedIndex = this.state.users.findIndex((user) => (user._id === selectedTeamId)) const selectedIndex = this.state.teams.findIndex((team) => (team._id === selectedTeamId))
if (selectedIndex >= 0) { if (selectedIndex >= 0) {
this.setState({ waitModal: { message: 'Removing Team' } }) this.setState({ waitModal: { message: 'Removing Team' } })
api.deleteTeam(selectedTeamId).then(() => { api.deleteTeam(selectedTeamId).then(() => {
this.setState({ this.setState({
waitModal: null, waitModal: null,
users: [...this.state.users.slice(0, selectedIndex), ...this.state.users.slice(selectedIndex + 1)], teams: [...this.state.teams.slice(0, selectedIndex), ...this.state.teams.slice(selectedIndex + 1)],
selectedTeam: null selectedTeam: null
}) })
}).catch((error) => { }).catch((error) => {
@@ -211,7 +211,7 @@ export class Teams extends Component {
waitModal: null, waitModal: null,
messageModal: { messageModal: {
icon: 'hand', icon: 'hand',
message: 'Unable to remove the user.', message: 'Unable to remove the team.',
detail: error.message, detail: error.message,
} }
}) })
@@ -253,8 +253,8 @@ export class Teams extends Component {
@autobind @autobind
handleAddNewTeam() { handleAddNewTeam() {
let newTeam = {} let newTeam = {}
let newTeams = [newTeam].concat(this.state.users) let newTeams = [newTeam].concat(this.state.teams)
this.setState({ users: newTeams, selectedTeam: newTeam }) this.setState({ teams: newTeams, selectedTeam: newTeam })
} }
render() { render() {
@@ -267,7 +267,7 @@ export class Teams extends Component {
<Row fillParent> <Row fillParent>
<Row.Item width={sizeInfo.formRowSpacing} /> <Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item width='25vw'> <Row.Item width='25vw'>
<TeamList users={this.state.users} selectedTeam={this.state.selectedTeam} <TeamList teams={this.state.teams} selectedTeam={this.state.selectedTeam}
selectionModified={this.state.modified} onTeamListClick={this.handleTeamListClick} selectionModified={this.state.modified} onTeamListClick={this.handleTeamListClick}
onAddNewTeam={this.handleAddNewTeam} /> onAddNewTeam={this.handleAddNewTeam} />
</Row.Item> </Row.Item>
@@ -276,7 +276,7 @@ export class Teams extends Component {
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}> <Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
{ {
this.state.selectedTeam this.state.selectedTeam
? <TeamForm user={this.state.selectedTeam} onSave={this.handleSave} ? <TeamForm team={this.state.selectedTeam} onSave={this.handleSave}
onRemove={this.handleRemove} onModifiedChanged={this.handleModifiedChanged} onRemove={this.handleRemove} onModifiedChanged={this.handleModifiedChanged}
onChangeEmail={this.handleChangeEmail} onResendEmail={this.handleResendEmail} /> onChangeEmail={this.handleChangeEmail} onResendEmail={this.handleResendEmail} />
: <TeamFormPlaceholder /> : <TeamFormPlaceholder />

View File

@@ -32,7 +32,7 @@ export class UserList extends React.Component {
return ( return (
<Column fillParent> <Column fillParent>
<Column.Item grow> <Column.Item grow>
<List data={users} render={(user, index) => { <List items={users} render={(user, index) => {
return ( return (
<List.Item key={user._id || '0'} onClick={(e) => (this.props.onUserListClick(e, index))} <List.Item key={user._id || '0'} onClick={(e) => (this.props.onUserListClick(e, index))}
active={user === this.props.selectedUser}> active={user === this.props.selectedUser}>

View File

@@ -7,12 +7,12 @@ import { sizeInfo, colorInfo, fontInfo } from './style'
@Radium @Radium
export class List extends Component { export class List extends Component {
static propTypes = { static propTypes = {
data: PropTypes.array, items: PropTypes.array,
render: PropTypes.func.isRequired, render: PropTypes.func.isRequired,
} }
render() { render() {
const { data, render } = this.props const { items, render } = this.props
return ( return (
<Box <Box
@@ -24,7 +24,7 @@ export class List extends Component {
fontFamily: fontInfo.family, fontFamily: fontInfo.family,
overflow: 'scroll', overflow: 'scroll',
}}> }}>
{data ? data.map(render) : null} {items ? items.map(render) : null}
</Box> </Box>
) )
} }