diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch new file mode 100644 index 0000000..d174cc0 Binary files /dev/null and b/design/Deighton AR Design.sketch differ diff --git a/server/src/api/routes/ActivityRoutes.js b/server/src/api/routes/ActivityRoutes.js index de4207b..d482aa4 100644 --- a/server/src/api/routes/ActivityRoutes.js +++ b/server/src/api/routes/ActivityRoutes.js @@ -24,11 +24,11 @@ export class ActivityRoutes { listActivitys(req, res, next) { const Activity = this.db.Activity - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch - let query = {} + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial + const branch = req.query.branch + const query = {} if (branch) { query.branch = branch diff --git a/server/src/api/routes/TeamRoutes.js b/server/src/api/routes/TeamRoutes.js index 38fd999..6c487be 100644 --- a/server/src/api/routes/TeamRoutes.js +++ b/server/src/api/routes/TeamRoutes.js @@ -22,19 +22,15 @@ export class TeamRoutes { .delete(passport.authenticate('bearer', { session: false }), this.deleteTeam) } - listTeams(req, res, next) { + async listTeams(req, res, next) { const Team = this.db.Team - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch + let limit = req.query.limit || 20 + let skip = req.query.skip || 0 + let partial = !!req.query.partial let query = {} - if (branch) { - query.branch = branch - } - - Team.count({}).then((total) => { + try { + const total = await Team.count({}) let teams = [] let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(partial) @@ -52,93 +48,120 @@ export class TeamRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw err }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - createTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } - - // Create a new Team template then assign it to a value in the req.body - const Team = this.db.Team - let team = new Team(req.body) - - // Save the team (with promise) - If it doesnt, catch and throw error - team.save().then((newTeam) => { - res.json(newTeam.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - updateTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } - - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - return next(createError.BadRequest('No _id given in body')) - } - - let Team = this.db.Team - let teamUpdates = null - + async createTeam(req, res, next) { try { - teamUpdates = new Team(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + if (!req.user.administrator) { + throw createError.Forbidden() + } + + // Create a new Team template then assign it to a value in the req.body + const Team = this.db.Team + let team = new Team(req.body) + + const newTeam = await team.save() + + res.json(newTeam.toClient()) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } + } + + async updateTeam(req, res, next) { + try { + if (!req.user.administrator) { + throw createError.Forbidden() + } + + // Do this here because Mongoose will add it automatically otherwise + if (!req.body._id) { + throw createError.BadRequest('No _id given in body') + } + + let Team = this.db.Team + let teamUpdates = null + + try { + teamUpdates = new Team(req.body) + } catch (err) { + throw createError.BadRequest('Invalid data') + } + + const foundTeam = await Team.findById(teamUpdates._id) - Team.findById(teamUpdates._id).then((foundTeam) => { if (!foundTeam) { - return next(createError.NotFound(`Team with _id ${_id} was not found`)) + throw createError.NotFound(`Team with _id ${_id} was not found`) } foundTeam.merge(teamUpdates) - return foundTeam.save() - }).then((savedTeam) => { + const savedTeam = await foundTeam.save() + res.json(savedTeam.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - getTeam(req, res, next) { + async getTeam(req, res, next) { const Team = this.db.Team const _id = req.params._id - Team.findById(_id).then((team) => { + try { + const team = await Team.findById(_id) + if (!team) { - return next(createError.NotFound(`Team with _id ${_id} not found`)) + throw createError.NotFound(`Team with _id ${_id} not found`) } res.json(team.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - deleteTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } + async deleteTeam(req, res, next) { + try { + if (!req.user.administrator) { + throw createError.Forbidden() + } - const Team = this.db.Team - const _id = req.params._id + const Team = this.db.Team + const _id = req.params._id - Team.remove({ _id }).then((team) => { - if (!project) { - return next(createError.NotFound(`Team with _id ${_id} not found`)) + const removedTeam = await Team.remove({ _id }) + + if (!removedTeam) { + throw createError.NotFound(`Team with _id ${_id} not found`) } res.json({}) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } } diff --git a/server/src/api/routes/UserRoutes.js b/server/src/api/routes/UserRoutes.js index ecb4e65..f7eb204 100644 --- a/server/src/api/routes/UserRoutes.js +++ b/server/src/api/routes/UserRoutes.js @@ -28,9 +28,6 @@ export class UserRoutes { .get(passport.authenticate('bearer', { session: false }), this.getUser) .delete(passport.authenticate('bearer', { session: false }), this.deleteUser) - app.route('/users/set-image') - .put(passport.authenticate('bearer', { session: false }), this.setImage) - app.route('/users/enter-room/:roomName') .put(passport.authenticate('bearer', { session: false }), this.enterRoom) @@ -38,19 +35,27 @@ export class UserRoutes { .put(passport.authenticate('bearer', { session: false }), this.leaveRoom) } - listUsers(req, res, next) { + async listUsers(req, res, next) { const User = this.db.User - const limit = req.params.limit || 20 - const skip = req.params.skip || 0 + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial const isAdmin = !!req.user.administrator + const team = req.query.team + let query = {} - if (!isAdmin) { - return next(new createError.Forbidden()) - } + try { + if (team) { + query = { team } + } - User.count({}).then((total) => { + if (!isAdmin) { + throw createError.Forbidden() + } + + const total = await User.count({}) let users = [] - let cursor = User.find({}).limit(limit).skip(skip).cursor().map((doc) => { + let cursor = User.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(req.user) }) @@ -66,56 +71,64 @@ export class UserRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw err }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - getUser(req, res, next) { - let User = this.db.User - const _id = req.params._id - const isSelf = (_id === req.user._id) - const isAdmin = req.user.administrator - - // User can see themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - User.findById(_id).then((user) => { - if (!user) { - return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`)) - } - - res.json(user.toClient(req.user)) - }).catch((err) => { + } catch(err) { if (err instanceof createError.HttpError) { next(err) } else { next(createError.InternalServerError(err.message)) } - }) + } } - createUser(req, res, next) { + async getUser(req, res, next) { + let User = this.db.User + const _id = req.params._id + const isSelf = (_id === req.user._id) const isAdmin = req.user.administrator - if (!isAdmin) { - return next(new createError.Forbidden()) - } + try { + // User can see themselves, otherwise must be super user + if (!isSelf && !isAdmin) { + throw createError.Forbidden() + } + + const user = await User.findById(_id) + + if (!user) { + return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`)) + } + + res.json(user.toClient(req.user)) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } + } + + async createUser(req, res, next) { + const isAdmin = req.user.administrator + + try { + if (!isAdmin) { + throw new createError.Forbidden() + } + + let User = this.db.User + let user = new User(req.body) + + // Add email confirmation required token + const buf = await util.promisify(crypto.randomBytes)(32) - let User = this.db.User - let user = new User(req.body) - // Add email confirmation required token - util.promisify(crypto.randomBytes)(32).then((buf) => { user.emailToken = { value: urlSafeBase64.encode(buf), created: new Date() } - return user.save() - }).then((savedUser) => { + const savedUser = await user.save() const userFullName = `${savedUser.firstName} ${savedUser.lastName}` const senderFullName = `${req.user.firstName} ${req.user.lastName}` const siteUrl = url.parse(req.headers.referer) @@ -130,137 +143,70 @@ export class UserRoutes { supportEmail: this.supportEmail } } + res.json(savedUser.toClient()) - return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve() - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + + if (this.sendEmail) { + await this.mq.request('dar-email', 'sendEmail', msg) + } + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - updateUser(req, res, next) { + async updateUser(req, res, next) { const isAdmin = req.user.administrator - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - return next(createError.BadRequest('No user _id given in body')) - } - - const isSelf = (req.body._id === req.user._id.toString()) - - // User can change themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - const User = this.db.User - let userUpdates = null - try { - userUpdates = new User(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + // Do this here because Mongoose will add it automatically otherwise + if (!req.body._id) { + throw createError.BadRequest('No user _id given in body') + } - if (isSelf && !isAdmin) { - return next(createError.BadRequest('Cannot modify own administrator level')) - } + const isSelf = (req.body._id === req.user._id.toString()) + + // User can change themselves, otherwise must be super user + if (!isSelf && !isAdmin) { + throw createError.Forbidden() + } + + const User = this.db.User + let userUpdates = null + + try { + userUpdates = new User(req.body) + } catch (err) { + throw createError.BadRequest('Invalid data') + } + + if (isSelf && !isAdmin) { + throw createError.BadRequest('Cannot modify own administrator level') + } + + const foundUser = await User.findById(userUpdates._id) - User.findById(userUpdates._id).then((foundUser) => { if (!foundUser) { - return Promise.reject(createError.NotFound(`User with _id ${user._id} was not found`)) + throw createError.NotFound(`User with _id ${user._id} was not found`) } // We don't allow direct updates to the email field so remove it if present delete userUpdates.email foundUser.merge(userUpdates) - return foundUser.save() - }).then((savedUser) => { + const savedUser = await foundUser.save() + res.json(savedUser.toClient(req.user)) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - setImage(req, res, next) { - const isAdmin = req.user.administrator - const { _id, imageId } = req.body - - if (!_id || !imageId) { - return next(createError.BadRequest('Must specify _id and imageId')) - } - - const isSelf = (_id === req.user._id.toString()) - - // User can change themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - const { bigSize = {}, smallSize = {} } = req.body - - Promise.all([ - this.mq.request('dar-image', 'scaleImage', { - newWidth: bigSize.width || 200, - newHeight: bigSize.height || 200, - scaleMode: 'aspectFill', - inputAssetId: imageId, - passback: { - userId: _id, - rooms: [ req.user._id ], - routerName: 'userRoutes', - funcName: 'completeSetBigImage' - } - }), - this.mq.request('dar-image', 'scaleImage', { - newWidth: smallSize.width || 25, - newHeight: smallSize.height || 25, - scaleMode: 'aspectFill', - inputAssetId: imageId, - passback: { - userId: _id, - rooms: [ req.user._id, 'users' ], - routerName: 'userRoutes', - funcName: 'completeSetSmallImage' - } - }) - ]).then((correlationIds) => { - res.json({ correlationIds }) - }).catch((err) => { - next(createError.InternalServerError('Unable to scale user images')) - }) - } - - completeSetBigImage(passback, error, data) { - if (error || !passback.userId || !passback.rooms) { - return - } - - const User = this.db.User - - User.findByIdAndUpdate(passback.userId, { imageId: data.outputAssetId }).then((foundUser) => { - if (foundUser) { - this.ws.notify(passback.rooms, 'newProfileImage', { imageId: data.outputAssetId }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) } - }).catch((err) => { - this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new image'`) - }) - } - - completeSetSmallImage(passback, error, data) { - if (error || !passback.userId || !passback.rooms) { - return } - - const User = this.db.User - - User.findByIdAndUpdate(passback.userId, { thumbnailImageId: data.outputAssetId }).then((foundUser) => { - if (foundUser) { - this.ws.notify(passback.rooms, 'newThumbnailImage', { imageId: data.outputAssetId }) - } - }).catch((err) => { - this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new thumbnail image'`) - }) } enterRoom(req, res, next) { @@ -273,19 +219,20 @@ export class UserRoutes { res.json({}) } - deleteUser(req, res, next) { + async deleteUser(req, res, next) { const isAdmin = req.user.administrator - if (!isAdmin) { - return new createError.Forbidden() - } + try { + if (!isAdmin) { + throw createError.Forbidden() + } - let User = this.db.User - const _id = req.params._id + let User = this.db.User + const _id = req.params._id + const deletedUser = await User.remove({ _id }) - User.remove({ _id }).then((deletedUser) => { if (!deletedUser) { - return next(createError.NotFound(`User with _id ${_id} was not found`)) + throw createError.NotFound(`User with _id ${_id} was not found`) } const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}` @@ -299,11 +246,17 @@ export class UserRoutes { supportEmail: this.supportEmail } } - return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve() - }).then(() => { res.json({}) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + + if (this.sendEmail) { + await this.mq.request('dar-email', 'sendEmail', msg) + } + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } } diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index 0769d5b..f26dc75 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -24,16 +24,11 @@ export class WorkItemRoutes { listWorkItems(req, res, next) { const WorkItem = this.db.WorkItem - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial let query = {} - if (branch) { - query.branch = branch - } - WorkItem.count({}).then((total) => { let workItems = [] let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => { diff --git a/website/src/API.js b/website/src/API.js index fba0264..d78993a 100644 --- a/website/src/API.js +++ b/website/src/API.js @@ -243,6 +243,9 @@ class API extends EventEmitter { listUsers() { return this.get('/users') } + listUsersForTeam(teamId) { + return this.get(`/users?team=${teamId}`) + } createUser(user) { return this.post('/users', user) } @@ -280,7 +283,7 @@ class API extends EventEmitter { return this.post('/teams', team) } updateTeam(team) { - this.put('/teams', team) + return this.put('/teams', team) } deleteTeam(id) { return this.delete('/teams/' + id) diff --git a/website/src/Teams/TeamForm.js b/website/src/Teams/TeamForm.js index 82e1072..5fe2b15 100644 --- a/website/src/Teams/TeamForm.js +++ b/website/src/Teams/TeamForm.js @@ -4,6 +4,7 @@ import autobind from 'autobind-decorator' import { Row, Column, BoundInput, BoundButton, List } from 'ui' import { FormBinder } from 'react-form-binder' import { sizeInfo } from 'ui/style' +import { api } from 'src/API' export class TeamForm extends React.Component { static propTypes = { @@ -36,14 +37,23 @@ export class TeamForm extends React.Component { constructor(props) { super(props) - this.users = [ - { id: 0, name: 'Meryll Streep' }, - { id: 1, name: 'Scarlett Johansenn' }, - { id: 2, name: 'John Lyon-Smith' }, - ] - this.state = { - binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged) + binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged), + users: [] + } + + this.getUsersForTeam(props.team._id) + } + + @autobind + getUsersForTeam(teamId) { + if (teamId) { + api.listUsersForTeam(teamId).then((list) => { + list.items.sort((a, b) => (a.lastName.localeCompare(b.lastName))) + this.setState({ users: list.items }) + }).catch(() => { + this.setState({ users: [] }) + }) } } @@ -52,6 +62,8 @@ export class TeamForm extends React.Component { this.setState({ binder: new FormBinder(nextProps.team, TeamForm.bindings, nextProps.onModifiedChanged) }) + + this.getUsersForTeam(nextProps.team._id) } } @@ -78,7 +90,7 @@ export class TeamForm extends React.Component { } render() { - const { binder } = this.state + const { binder, users } = this.state return (
@@ -93,10 +105,10 @@ export class TeamForm extends React.Component { - ( - + ( + - {item.name} + {item.firstName + ' ' + item.lastName} )} /> diff --git a/website/src/Teams/Teams.js b/website/src/Teams/Teams.js index 7108069..17c36c8 100644 --- a/website/src/Teams/Teams.js +++ b/website/src/Teams/Teams.js @@ -31,8 +31,8 @@ export class Teams extends Component { this.props.changeTitle('Teams') api.listTeams().then((list) => { - list.items.sort((teamA, teamB) => (teamA.lastName.localeCompare(teamB.lastName))) - this.setState({ teams: list.items }) // TODO: <- Remove + list.items.sort((teamA, teamB) => (teamA.name.localeCompare(teamB.name))) + this.setState({ teams: list.items }) }).catch((error) => { this.setState({ messageModal: { @@ -101,7 +101,7 @@ export class Teams extends Component { this.setState({ waitModal: false, teams: this.state.teams.map((team) => (!team._id ? createdTeam : team)).sort((a, b) => ( - a.lastName < b.lastName ? -1 : a.lastName > b.lastName : 0 + a.name < b.name ? -1 : a.name > b.name ? 1 : 0 )), modified: false, selectedTeam: createdTeam diff --git a/website/src/Users/UserForm.js b/website/src/Users/UserForm.js index c2dfdba..b56230a 100644 --- a/website/src/Users/UserForm.js +++ b/website/src/Users/UserForm.js @@ -3,7 +3,9 @@ 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 { + Row, Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon, BoundDropdown, +} from 'ui' import { FormBinder } from 'react-form-binder' import { sizeInfo } from 'ui/style' @@ -40,6 +42,9 @@ export class UserForm extends React.Component { lastName: { isValid: (r, v) => (v !== '') }, + team: { + isValid: true + }, administrator: { isValid: (r, v) => true, initValue: false, @@ -66,8 +71,21 @@ export class UserForm extends React.Component { constructor(props) { super(props) this.state = { - binder: new FormBinder(this.props.user, UserForm.bindings, this.props.onModifiedChanged) + binder: new FormBinder(props.user, UserForm.bindings, props.onModifiedChanged), + teams: [] } + + this.getTeams() + } + + // TODO: This is not very efficient. Better to get the teams in User.js and pass them in + // This however will always be up-to-date. Need to use the WebSocket to refresh. + getTeams() { + api.listTeams().then((list) => { + this.setState({ teams: list.items.map((item) => ({ value: item._id, text: item.name, icon: 'team' })).sort((a, b) => (a.text.localeCompare(b.text))) }) + }).catch(() => { + this.setState({ teams: [] }) + }) } componentWillReceiveProps(nextProps) { @@ -75,6 +93,8 @@ export class UserForm extends React.Component { this.setState({ binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onModifiedChanged) }) + + this.getTeams() } } @@ -111,17 +131,7 @@ export class UserForm extends React.Component { } 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' }, - ] + const { binder, teams } = this.state return ( @@ -155,12 +165,7 @@ export class UserForm extends React.Component { - ( - - - {item.name} - - )} /> + diff --git a/website/src/assets/icons/profile.png b/website/src/assets/icons/profile.png new file mode 100644 index 0000000..ed009dd Binary files /dev/null and b/website/src/assets/icons/profile.png differ diff --git a/website/src/assets/icons/profile.svg b/website/src/assets/icons/profile.svg index 8fe66d7..f2fa4b3 100644 --- a/website/src/assets/icons/profile.svg +++ b/website/src/assets/icons/profile.svg @@ -1,9 +1,17 @@ - - - - - - + + + + profile + Created with Sketch. + + + + + + + + + + - - + \ No newline at end of file diff --git a/website/src/assets/icons/users.svg b/website/src/assets/icons/users.svg index b2d963c..f947931 100644 --- a/website/src/assets/icons/users.svg +++ b/website/src/assets/icons/users.svg @@ -1,24 +1,30 @@ - + users Created with Sketch. - + - - - + + + + + - - - + + + + + - - - + + + + + diff --git a/website/src/ui/BoundButton.js b/website/src/ui/BoundButton.js index b09c752..5e76867 100644 --- a/website/src/ui/BoundButton.js +++ b/website/src/ui/BoundButton.js @@ -41,9 +41,13 @@ export class BoundButton extends React.Component { const { name, text, submit, width, onClick } = this.props const { visible, disabled } = this.state + if (!visible) { + return null + } + return ( + name={name} text={text} width={width} /> ) } } diff --git a/website/src/ui/BoundCheckbox.js b/website/src/ui/BoundCheckbox.js index 440cfc1..0c9a087 100644 --- a/website/src/ui/BoundCheckbox.js +++ b/website/src/ui/BoundCheckbox.js @@ -36,8 +36,12 @@ export class BoundCheckbox extends React.Component { const { name, label } = this.props const { visible, disabled, value } = this.state + if (!visible) { + return null + } + return ( -
+
+ ) + } +} diff --git a/website/src/ui/BoundDropdownList.js b/website/src/ui/BoundDropdownList.js deleted file mode 100644 index e69de29..0000000 diff --git a/website/src/ui/BoundInput.js b/website/src/ui/BoundInput.js index 1a3f8d7..f55ba32 100644 --- a/website/src/ui/BoundInput.js +++ b/website/src/ui/BoundInput.js @@ -38,12 +38,16 @@ export class BoundInput extends React.Component { const { label, password, name, placeholder, message } = this.props const { visible, disabled, value, valid } = this.state + if (!visible) { + return null + } + return (
{label}
0) ? props.items[0] : {}, + selectedItem: this.getSelectedFromValue(props.items, props.value) + } + } + + @autobind + getSelectedFromValue(items, value) { + return items.find((item) => (item.value === value)) || this.props.emptyItem + } + + componentWillReceiveProps(nextProps) { + if (nextProps.value !== this.props.value || nextProps.items !== this.props.items) { + this.setState({ + selectedItem: this.getSelectedFromValue(nextProps.items, nextProps.value) + }) } } @@ -30,6 +50,9 @@ export class DropdownList extends Component { @autobind handleItemClick(e, item) { this.setState({ selectedItem: item, open: false }) + if (this.props.onChange) { + this.props.onChange(item) + } } render() { @@ -100,7 +123,7 @@ export class DropdownList extends Component { } } -DropdownList.Item = Radium(class ListItem extends Component { +Dropdown.Item = Radium(class ListItem extends Component { static propTypes = { children: PropTypes.node, active: PropTypes.bool, @@ -124,7 +147,7 @@ DropdownList.Item = Radium(class ListItem extends Component { } }) -DropdownList.Icon = Radium(class DropdownIcon extends Component { +Dropdown.Icon = Radium(class DropdownIcon extends Component { static propTypes = { name: PropTypes.string, size: PropTypes.number, @@ -149,7 +172,7 @@ DropdownList.Icon = Radium(class DropdownIcon extends Component { } }) -DropdownList.Text = Radium(class DropdownText extends Component { +Dropdown.Text = Radium(class DropdownText extends Component { static propTypes = { children: PropTypes.node, } diff --git a/website/src/ui/index.js b/website/src/ui/index.js index 0931ce9..409173f 100644 --- a/website/src/ui/index.js +++ b/website/src/ui/index.js @@ -11,7 +11,7 @@ export { Text } from './Text' export { Link } from './Link' export { Icon } from './Icon' export { List } from './List' -export { DropdownList } from './DropdownList' +export { Dropdown } from './Dropdown' export { Modal } from './Modal' export { Dimmer } from './Dimmer' export { Loader } from './Loader' @@ -21,3 +21,4 @@ export { BoundButton } from './BoundButton' export { BoundCheckbox } from './BoundCheckbox' export { BoundInput } from './BoundInput' export { BoundEmailIcon } from './BoundEmailIcon' +export { BoundDropdown } from './BoundDropdown'