Hook up team dropdowns and lists
This commit is contained in:
BIN
design/Deighton AR Design.sketch
Normal file
BIN
design/Deighton AR Design.sketch
Normal file
Binary file not shown.
@@ -24,11 +24,11 @@ export class ActivityRoutes {
|
|||||||
|
|
||||||
listActivitys(req, res, next) {
|
listActivitys(req, res, next) {
|
||||||
const Activity = this.db.Activity
|
const Activity = this.db.Activity
|
||||||
let limit = req.params.limit || 20
|
const limit = req.query.limit || 20
|
||||||
let skip = req.params.skip || 0
|
const skip = req.query.skip || 0
|
||||||
let partial = !!req.params.partial
|
const partial = !!req.query.partial
|
||||||
let branch = req.params.branch
|
const branch = req.query.branch
|
||||||
let query = {}
|
const query = {}
|
||||||
|
|
||||||
if (branch) {
|
if (branch) {
|
||||||
query.branch = branch
|
query.branch = branch
|
||||||
|
|||||||
@@ -22,19 +22,15 @@ export class TeamRoutes {
|
|||||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteTeam)
|
.delete(passport.authenticate('bearer', { session: false }), this.deleteTeam)
|
||||||
}
|
}
|
||||||
|
|
||||||
listTeams(req, res, next) {
|
async listTeams(req, res, next) {
|
||||||
const Team = this.db.Team
|
const Team = this.db.Team
|
||||||
let limit = req.params.limit || 20
|
let limit = req.query.limit || 20
|
||||||
let skip = req.params.skip || 0
|
let skip = req.query.skip || 0
|
||||||
let partial = !!req.params.partial
|
let partial = !!req.query.partial
|
||||||
let branch = req.params.branch
|
|
||||||
let query = {}
|
let query = {}
|
||||||
|
|
||||||
if (branch) {
|
try {
|
||||||
query.branch = branch
|
const total = await Team.count({})
|
||||||
}
|
|
||||||
|
|
||||||
Team.count({}).then((total) => {
|
|
||||||
let teams = []
|
let teams = []
|
||||||
let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||||
return doc.toClient(partial)
|
return doc.toClient(partial)
|
||||||
@@ -52,93 +48,120 @@ export class TeamRoutes {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
cursor.on('error', (err) => {
|
cursor.on('error', (err) => {
|
||||||
next(createError.InternalServerError(err.message))
|
throw err
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createTeam(req, res, next) {
|
async 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
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
teamUpdates = new Team(req.body)
|
if (!req.user.administrator) {
|
||||||
} catch (err) {
|
throw createError.Forbidden()
|
||||||
return next(createError.BadRequest('Invalid data'))
|
}
|
||||||
}
|
|
||||||
|
// 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) {
|
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)
|
foundTeam.merge(teamUpdates)
|
||||||
return foundTeam.save()
|
const savedTeam = await foundTeam.save()
|
||||||
}).then((savedTeam) => {
|
|
||||||
res.json(savedTeam.toClient())
|
res.json(savedTeam.toClient())
|
||||||
}).catch((err) => {
|
} catch (err) {
|
||||||
next(createError.InternalServerError(err.message))
|
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 Team = this.db.Team
|
||||||
const _id = req.params._id
|
const _id = req.params._id
|
||||||
|
|
||||||
Team.findById(_id).then((team) => {
|
try {
|
||||||
|
const team = await Team.findById(_id)
|
||||||
|
|
||||||
if (!team) {
|
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())
|
res.json(team.toClient())
|
||||||
}).catch((err) => {
|
} catch (err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTeam(req, res, next) {
|
async deleteTeam(req, res, next) {
|
||||||
if (!req.user.administrator) {
|
try {
|
||||||
return next(new createError.Forbidden())
|
if (!req.user.administrator) {
|
||||||
}
|
throw createError.Forbidden()
|
||||||
|
}
|
||||||
|
|
||||||
const Team = this.db.Team
|
const Team = this.db.Team
|
||||||
const _id = req.params._id
|
const _id = req.params._id
|
||||||
|
|
||||||
Team.remove({ _id }).then((team) => {
|
const removedTeam = await Team.remove({ _id })
|
||||||
if (!project) {
|
|
||||||
return next(createError.NotFound(`Team with _id ${_id} not found`))
|
if (!removedTeam) {
|
||||||
|
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ export class UserRoutes {
|
|||||||
.get(passport.authenticate('bearer', { session: false }), this.getUser)
|
.get(passport.authenticate('bearer', { session: false }), this.getUser)
|
||||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteUser)
|
.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')
|
app.route('/users/enter-room/:roomName')
|
||||||
.put(passport.authenticate('bearer', { session: false }), this.enterRoom)
|
.put(passport.authenticate('bearer', { session: false }), this.enterRoom)
|
||||||
|
|
||||||
@@ -38,19 +35,27 @@ export class UserRoutes {
|
|||||||
.put(passport.authenticate('bearer', { session: false }), this.leaveRoom)
|
.put(passport.authenticate('bearer', { session: false }), this.leaveRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
listUsers(req, res, next) {
|
async listUsers(req, res, next) {
|
||||||
const User = this.db.User
|
const User = this.db.User
|
||||||
const limit = req.params.limit || 20
|
const limit = req.query.limit || 20
|
||||||
const skip = req.params.skip || 0
|
const skip = req.query.skip || 0
|
||||||
|
const partial = !!req.query.partial
|
||||||
const isAdmin = !!req.user.administrator
|
const isAdmin = !!req.user.administrator
|
||||||
|
const team = req.query.team
|
||||||
|
let query = {}
|
||||||
|
|
||||||
if (!isAdmin) {
|
try {
|
||||||
return next(new createError.Forbidden())
|
if (team) {
|
||||||
}
|
query = { team }
|
||||||
|
}
|
||||||
|
|
||||||
User.count({}).then((total) => {
|
if (!isAdmin) {
|
||||||
|
throw createError.Forbidden()
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = await User.count({})
|
||||||
let users = []
|
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)
|
return doc.toClient(req.user)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -66,56 +71,64 @@ export class UserRoutes {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
cursor.on('error', (err) => {
|
cursor.on('error', (err) => {
|
||||||
next(createError.InternalServerError(err.message))
|
throw err
|
||||||
})
|
})
|
||||||
}).catch((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) => {
|
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(err.message))
|
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
|
const isAdmin = req.user.administrator
|
||||||
|
|
||||||
if (!isAdmin) {
|
try {
|
||||||
return next(new createError.Forbidden())
|
// 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 = {
|
user.emailToken = {
|
||||||
value: urlSafeBase64.encode(buf),
|
value: urlSafeBase64.encode(buf),
|
||||||
created: new Date()
|
created: new Date()
|
||||||
}
|
}
|
||||||
return user.save()
|
const savedUser = await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||||
const siteUrl = url.parse(req.headers.referer)
|
const siteUrl = url.parse(req.headers.referer)
|
||||||
@@ -130,137 +143,70 @@ export class UserRoutes {
|
|||||||
supportEmail: this.supportEmail
|
supportEmail: this.supportEmail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(savedUser.toClient())
|
res.json(savedUser.toClient())
|
||||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve()
|
|
||||||
}).catch((err) => {
|
if (this.sendEmail) {
|
||||||
next(createError.InternalServerError(err.message))
|
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
|
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 {
|
try {
|
||||||
userUpdates = new User(req.body)
|
// Do this here because Mongoose will add it automatically otherwise
|
||||||
} catch (err) {
|
if (!req.body._id) {
|
||||||
return next(createError.BadRequest('Invalid data'))
|
throw createError.BadRequest('No user _id given in body')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelf && !isAdmin) {
|
const isSelf = (req.body._id === req.user._id.toString())
|
||||||
return next(createError.BadRequest('Cannot modify own administrator level'))
|
|
||||||
}
|
// 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) {
|
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
|
// We don't allow direct updates to the email field so remove it if present
|
||||||
delete userUpdates.email
|
delete userUpdates.email
|
||||||
|
|
||||||
foundUser.merge(userUpdates)
|
foundUser.merge(userUpdates)
|
||||||
return foundUser.save()
|
const savedUser = await foundUser.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
res.json(savedUser.toClient(req.user))
|
res.json(savedUser.toClient(req.user))
|
||||||
}).catch((err) => {
|
} catch (err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
}
|
} else {
|
||||||
|
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) => {
|
|
||||||
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) {
|
enterRoom(req, res, next) {
|
||||||
@@ -273,19 +219,20 @@ export class UserRoutes {
|
|||||||
res.json({})
|
res.json({})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteUser(req, res, next) {
|
async deleteUser(req, res, next) {
|
||||||
const isAdmin = req.user.administrator
|
const isAdmin = req.user.administrator
|
||||||
|
|
||||||
if (!isAdmin) {
|
try {
|
||||||
return new createError.Forbidden()
|
if (!isAdmin) {
|
||||||
}
|
throw createError.Forbidden()
|
||||||
|
}
|
||||||
|
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
const _id = req.params._id
|
const _id = req.params._id
|
||||||
|
const deletedUser = await User.remove({ _id })
|
||||||
|
|
||||||
User.remove({ _id }).then((deletedUser) => {
|
|
||||||
if (!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}`
|
const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}`
|
||||||
@@ -299,11 +246,17 @@ export class UserRoutes {
|
|||||||
supportEmail: this.supportEmail
|
supportEmail: this.supportEmail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve()
|
|
||||||
}).then(() => {
|
|
||||||
res.json({})
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,16 +24,11 @@ export class WorkItemRoutes {
|
|||||||
|
|
||||||
listWorkItems(req, res, next) {
|
listWorkItems(req, res, next) {
|
||||||
const WorkItem = this.db.WorkItem
|
const WorkItem = this.db.WorkItem
|
||||||
let limit = req.params.limit || 20
|
const limit = req.query.limit || 20
|
||||||
let skip = req.params.skip || 0
|
const skip = req.query.skip || 0
|
||||||
let partial = !!req.params.partial
|
const partial = !!req.query.partial
|
||||||
let branch = req.params.branch
|
|
||||||
let query = {}
|
let query = {}
|
||||||
|
|
||||||
if (branch) {
|
|
||||||
query.branch = branch
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkItem.count({}).then((total) => {
|
WorkItem.count({}).then((total) => {
|
||||||
let workItems = []
|
let workItems = []
|
||||||
let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ class API extends EventEmitter {
|
|||||||
listUsers() {
|
listUsers() {
|
||||||
return this.get('/users')
|
return this.get('/users')
|
||||||
}
|
}
|
||||||
|
listUsersForTeam(teamId) {
|
||||||
|
return this.get(`/users?team=${teamId}`)
|
||||||
|
}
|
||||||
createUser(user) {
|
createUser(user) {
|
||||||
return this.post('/users', user)
|
return this.post('/users', user)
|
||||||
}
|
}
|
||||||
@@ -280,7 +283,7 @@ class API extends EventEmitter {
|
|||||||
return this.post('/teams', team)
|
return this.post('/teams', team)
|
||||||
}
|
}
|
||||||
updateTeam(team) {
|
updateTeam(team) {
|
||||||
this.put('/teams', team)
|
return this.put('/teams', team)
|
||||||
}
|
}
|
||||||
deleteTeam(id) {
|
deleteTeam(id) {
|
||||||
return this.delete('/teams/' + id)
|
return this.delete('/teams/' + id)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import autobind from 'autobind-decorator'
|
|||||||
import { Row, Column, BoundInput, BoundButton, List } from 'ui'
|
import { Row, Column, BoundInput, BoundButton, List } from 'ui'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo } from 'ui/style'
|
||||||
|
import { api } from 'src/API'
|
||||||
|
|
||||||
export class TeamForm extends React.Component {
|
export class TeamForm extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -36,14 +37,23 @@ export class TeamForm extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.users = [
|
|
||||||
{ id: 0, name: 'Meryll Streep' },
|
|
||||||
{ id: 1, name: 'Scarlett Johansenn' },
|
|
||||||
{ id: 2, name: 'John Lyon-Smith' },
|
|
||||||
]
|
|
||||||
|
|
||||||
this.state = {
|
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({
|
this.setState({
|
||||||
binder: new FormBinder(nextProps.team, TeamForm.bindings, nextProps.onModifiedChanged)
|
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() {
|
render() {
|
||||||
const { binder } = this.state
|
const { binder, users } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='teamForm' onSubmit={this.handleSubmit}>
|
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='teamForm' onSubmit={this.handleSubmit}>
|
||||||
@@ -93,10 +105,10 @@ export class TeamForm extends React.Component {
|
|||||||
<BoundInput label='Name' name='name' message='Must not be empty' binder={binder} />
|
<BoundInput label='Name' name='name' message='Must not be empty' binder={binder} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item height={300}>
|
<Column.Item height={300}>
|
||||||
<List items={this.users} render={(item) => (
|
<List items={users} render={(item) => (
|
||||||
<List.Item key={item.id}>
|
<List.Item key={item._id}>
|
||||||
<List.Icon name='profile' size={sizeInfo.dropdownIconSize} />
|
<List.Icon name='profile' size={sizeInfo.dropdownIconSize} />
|
||||||
<List.Text>{item.name}</List.Text>
|
<List.Text>{item.firstName + ' ' + item.lastName}</List.Text>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)} />
|
)} />
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
|
|||||||
@@ -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((teamA, teamB) => (teamA.lastName.localeCompare(teamB.lastName)))
|
list.items.sort((teamA, teamB) => (teamA.name.localeCompare(teamB.name)))
|
||||||
this.setState({ teams: list.items }) // TODO: <- Remove
|
this.setState({ teams: list.items })
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
messageModal: {
|
messageModal: {
|
||||||
@@ -101,7 +101,7 @@ export class Teams extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
waitModal: false,
|
waitModal: false,
|
||||||
teams: this.state.teams.map((team) => (!team._id ? createdTeam : team)).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.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
||||||
)),
|
)),
|
||||||
modified: false,
|
modified: false,
|
||||||
selectedTeam: createdTeam
|
selectedTeam: createdTeam
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import PropTypes from 'prop-types'
|
|||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
import { regExpPattern } from 'regexp-pattern'
|
import { regExpPattern } from 'regexp-pattern'
|
||||||
import { api } from 'src/API'
|
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 { FormBinder } from 'react-form-binder'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo } from 'ui/style'
|
||||||
|
|
||||||
@@ -40,6 +42,9 @@ export class UserForm extends React.Component {
|
|||||||
lastName: {
|
lastName: {
|
||||||
isValid: (r, v) => (v !== '')
|
isValid: (r, v) => (v !== '')
|
||||||
},
|
},
|
||||||
|
team: {
|
||||||
|
isValid: true
|
||||||
|
},
|
||||||
administrator: {
|
administrator: {
|
||||||
isValid: (r, v) => true,
|
isValid: (r, v) => true,
|
||||||
initValue: false,
|
initValue: false,
|
||||||
@@ -66,8 +71,21 @@ export class UserForm extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
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) {
|
componentWillReceiveProps(nextProps) {
|
||||||
@@ -75,6 +93,8 @@ export class UserForm extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onModifiedChanged)
|
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onModifiedChanged)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.getTeams()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,17 +131,7 @@ export class UserForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder } = this.state
|
const { binder, teams } = 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='userForm' onSubmit={this.handleSubmit}>
|
||||||
@@ -155,12 +165,7 @@ export class UserForm extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<DropdownList items={teams} render={(item) => (
|
<BoundDropdown name='team' label='Team' icon='team' items={teams} binder={binder} />
|
||||||
<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}>
|
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||||
|
|||||||
BIN
website/src/assets/icons/profile.png
Normal file
BIN
website/src/assets/icons/profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,9 +1,17 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
|
<svg width="55px" height="52px" viewBox="0 0 55 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<g>
|
<!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
|
||||||
<g>
|
<title>profile</title>
|
||||||
<path d="m64.3,71.6c18,0 32.6-14.6 32.6-32.6s-14.6-32.5-32.6-32.5-32.6,14.6-32.6,32.5 14.6,32.6 32.6,32.6zm0-56.6c13.2,0 24,10.8 24,24s-10.8,24-24,24-24-10.8-24-24 10.8-24 24-24z"/>
|
<desc>Created with Sketch.</desc>
|
||||||
<path d="m7.9,122.5h113.2c2.4,0 4.3-1.9 4.3-4.3 0-22.5-18.3-40.9-40.9-40.9h-40c-22.5,0-40.9,18.3-40.9,40.9-1.33227e-15,2.4 1.9,4.3 4.3,4.3zm36.6-36.6h40c16.4,0 29.9,12.2 32,28h-104c2.1-15.7 15.6-28 32-28z"/>
|
<defs></defs>
|
||||||
|
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Home" transform="translate(-147.000000, -479.000000)">
|
||||||
|
<g id="profile" transform="translate(147.000000, 479.000000)">
|
||||||
|
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
|
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</svg>
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 1.7 KiB |
@@ -1,24 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="240px" height="240px" viewBox="0 0 240 240" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="240px" height="240px" viewBox="0 0 240 240" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>users</title>
|
<title>users</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<defs></defs>
|
<defs></defs>
|
||||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g id="Home" transform="translate(-158.000000, -105.000000)" fill="#000000" fill-rule="nonzero">
|
<g id="Home" transform="translate(-158.000000, -105.000000)">
|
||||||
<g id="Users" transform="translate(122.000000, 94.000000)">
|
<g id="Users" transform="translate(122.000000, 94.000000)">
|
||||||
<g id="users" transform="translate(36.000000, 11.000000)">
|
<g id="users" transform="translate(36.000000, 11.000000)">
|
||||||
<g id="profile" transform="translate(16.000000, 85.000000)">
|
<g id="profile" transform="translate(93.000000, 85.000000)">
|
||||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
|
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="profile-copy" transform="translate(170.000000, 85.000000)">
|
<g id="profile-copy" transform="translate(31.000000, 85.000000)">
|
||||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
|
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="profile-copy-2" transform="translate(93.000000, 85.000000)">
|
<g id="profile-copy-2" transform="translate(156.000000, 85.000000)">
|
||||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
|
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -41,9 +41,13 @@ export class BoundButton extends React.Component {
|
|||||||
const { name, text, submit, width, onClick } = this.props
|
const { name, text, submit, width, onClick } = this.props
|
||||||
const { visible, disabled } = this.state
|
const { visible, disabled } = this.state
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button disabled={disabled} submit={submit} onClick={onClick}
|
<Button disabled={disabled} submit={submit} onClick={onClick}
|
||||||
name={name} visible={visible} text={text} width={width} />
|
name={name} text={text} width={width} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ export class BoundCheckbox extends React.Component {
|
|||||||
const { name, label } = this.props
|
const { name, label } = this.props
|
||||||
const { visible, disabled, value } = this.state
|
const { visible, disabled, value } = this.state
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={visible ? {} : { display: 'none' }}>
|
<div>
|
||||||
<Checkbox id={name} disabled={disabled} value={value} onChange={this.handleChange} />
|
<Checkbox id={name} disabled={disabled} value={value} onChange={this.handleChange} />
|
||||||
<label
|
<label
|
||||||
htmlFor={name}
|
htmlFor={name}
|
||||||
|
|||||||
75
website/src/ui/BoundDropdown.js
Normal file
75
website/src/ui/BoundDropdown.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Dropdown } from 'ui'
|
||||||
|
import autobind from 'autobind-decorator'
|
||||||
|
import { sizeInfo, fontInfo } from 'ui/style'
|
||||||
|
|
||||||
|
export class BoundDropdown extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string,
|
||||||
|
binder: PropTypes.object.isRequired,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string })),
|
||||||
|
icon: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = props.binder.getFieldState(props.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleChange(item) {
|
||||||
|
const { binder, name } = this.props
|
||||||
|
const state = binder.getFieldState(name)
|
||||||
|
|
||||||
|
if (!state.readOnly && !state.disabled) {
|
||||||
|
this.setState(binder.updateFieldValue(name, item.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.binder !== this.props.binder) {
|
||||||
|
this.setState(nextProps.binder.getFieldState(nextProps.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextProps.items !== this.props.items) {
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, label, icon, items } = this.props
|
||||||
|
const { visible, disabled, value } = this.state
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor={name}
|
||||||
|
style={{
|
||||||
|
fontSize: fontInfo.size.medium,
|
||||||
|
fontFamily: fontInfo.family,
|
||||||
|
color: disabled ? fontInfo.color.dimmed : fontInfo.color.normal,
|
||||||
|
}}>
|
||||||
|
{label}
|
||||||
|
<Dropdown
|
||||||
|
items={items}
|
||||||
|
value={value}
|
||||||
|
emptyItem={{ value: null, text: '', icon: 'team' }}
|
||||||
|
icon={icon}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
render={(item) => (
|
||||||
|
<Dropdown.Item key={item.value}>
|
||||||
|
<Dropdown.Icon name={item.icon} size={sizeInfo.dropdownIconSize} />
|
||||||
|
<Dropdown.Text>{item.text}</Dropdown.Text>
|
||||||
|
</Dropdown.Item>
|
||||||
|
)} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,12 +38,16 @@ export class BoundInput extends React.Component {
|
|||||||
const { label, password, name, placeholder, message } = this.props
|
const { label, password, name, placeholder, message } = this.props
|
||||||
const { visible, disabled, value, valid } = this.state
|
const { visible, disabled, value, valid } = this.state
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<Text>{label}</Text>
|
<Text>{label}</Text>
|
||||||
<br />
|
<br />
|
||||||
<Input value={value}
|
<Input value={value}
|
||||||
visible={visible}
|
visible
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
password={password}
|
password={password}
|
||||||
name={name}
|
name={name}
|
||||||
|
|||||||
@@ -8,17 +8,37 @@ import upArrowImage from './images/up-arrow.svg'
|
|||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
@Radium
|
@Radium
|
||||||
export class DropdownList extends Component {
|
export class Dropdown extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
items: PropTypes.array,
|
items: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string })),
|
||||||
|
value: PropTypes.string,
|
||||||
render: PropTypes.func.isRequired,
|
render: PropTypes.func.isRequired,
|
||||||
|
emptyItem: PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string }),
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
emptyItem: { value: null, text: '', icon: null },
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
open: false,
|
open: false,
|
||||||
selectedItem: (props.items && props.items.length > 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
|
@autobind
|
||||||
handleItemClick(e, item) {
|
handleItemClick(e, item) {
|
||||||
this.setState({ selectedItem: item, open: false })
|
this.setState({ selectedItem: item, open: false })
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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 = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
active: PropTypes.bool,
|
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 = {
|
static propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
size: PropTypes.number,
|
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 = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ export { Text } from './Text'
|
|||||||
export { Link } from './Link'
|
export { Link } from './Link'
|
||||||
export { Icon } from './Icon'
|
export { Icon } from './Icon'
|
||||||
export { List } from './List'
|
export { List } from './List'
|
||||||
export { DropdownList } from './DropdownList'
|
export { Dropdown } from './Dropdown'
|
||||||
export { Modal } from './Modal'
|
export { Modal } from './Modal'
|
||||||
export { Dimmer } from './Dimmer'
|
export { Dimmer } from './Dimmer'
|
||||||
export { Loader } from './Loader'
|
export { Loader } from './Loader'
|
||||||
@@ -21,3 +21,4 @@ export { BoundButton } from './BoundButton'
|
|||||||
export { BoundCheckbox } from './BoundCheckbox'
|
export { BoundCheckbox } from './BoundCheckbox'
|
||||||
export { BoundInput } from './BoundInput'
|
export { BoundInput } from './BoundInput'
|
||||||
export { BoundEmailIcon } from './BoundEmailIcon'
|
export { BoundEmailIcon } from './BoundEmailIcon'
|
||||||
|
export { BoundDropdown } from './BoundDropdown'
|
||||||
|
|||||||
Reference in New Issue
Block a user