Skip to content

Commit

Permalink
Merge pull request #256 from developmentseed/feature/badges-frontend
Browse files Browse the repository at this point in the history
Add badges management to organization view
  • Loading branch information
vgeorge authored Apr 11, 2022
2 parents cc2fa4e + 5b3e0f0 commit 759c366
Show file tree
Hide file tree
Showing 17 changed files with 842 additions and 37 deletions.
11 changes: 4 additions & 7 deletions app/db/migrations/20220302104250_add_user_badges.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
exports.up = async function (knex) {
await knex.schema.createTable('user_badge', (table) => {
await knex.schema.createTable('user_badges', (table) => {
table
.integer('badge_id')
.references('id')
.inTable('organization_badge')
.onDelete('CASCADE')
table
.integer('user_id')
.references('id')
.inTable('organization_badge')
.onDelete('CASCADE')
table.integer('user_id')
table.datetime('assigned_at').defaultTo(knex.fn.now())
table.datetime('valid_until')
table.unique(['badge_id', 'user_id'])
})
}

exports.down = async function (knex) {
await knex.schema.dropTable('user_badge')
await knex.schema.dropTable('user_badges')
}
6 changes: 3 additions & 3 deletions app/lib/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ async function getUserManageToken (id) {

async function getUserBadges (id) {
const conn = await db()
return conn('user_badge')
return conn('user_badges')
.select([
'id',
'assigned_at',
Expand All @@ -267,10 +267,10 @@ async function getUserBadges (id) {
])
.leftJoin(
'organization_badge',
'user_badge.badge_id',
'user_badges.badge_id',
'organization_badge.id'
)
.where('user_badge.user_id', id)
.where('user_badges.user_id', id)
}

module.exports = {
Expand Down
14 changes: 13 additions & 1 deletion app/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,21 @@ function checkRequiredProperties (requiredProperties, object) {
})
}

/**
* Converts a date to the browser locale string
*
* @param {Number or String} timestamp
* @returns
*/
function toDateString (timestamp) {
const dateFormat = new Intl.DateTimeFormat(navigator.language).format
return dateFormat(new Date(timestamp))
}

module.exports = {
unpack,
ValidationError,
PropertyRequiredError,
checkRequiredProperties
checkRequiredProperties,
toDateString
}
117 changes: 97 additions & 20 deletions app/manage/badges.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const yup = require('yup')
const organization = require('../lib/organization')
const profile = require('../lib/profile')
const { routeWrapper } = require('./utils')
const team = require('../lib/team')

/**
* Get the list of badges of an organization
Expand All @@ -21,6 +22,7 @@ const listBadges = routeWrapper({
const badges = await conn('organization_badge')
.select('*')
.where('organization_id', req.params.id)
.orderBy('id')
reply.send(badges)
} catch (err) {
console.log(err)
Expand Down Expand Up @@ -63,6 +65,68 @@ const createBadge = routeWrapper({
}
})

/**
* Get organization badge
*/
const getBadge = routeWrapper({
validate: {
params: yup
.object({
id: yup.number().required().positive().integer(),
badgeId: yup.number().required().positive().integer()
})
.required()
},
handler: async function (req, reply) {
try {
const conn = await db()
const [badge] = await conn('organization_badge')
.select('*')
.where('id', req.params.badgeId)
.returning('*')

let users = await conn('user_badges')
.select({
id: 'user_badges.user_id',
assignedAt: 'user_badges.assigned_at',
validUntil: 'user_badges.valid_until'
})
.leftJoin(
'organization_badge',
'user_badges.badge_id',
'organization_badge.id'
)
.where('badge_id', req.params.badgeId)
.returning('*')

if (users.length > 0) {
// Get user profiles
const userProfiles = (
await team.resolveMemberNames(users.map((u) => u.id))
).reduce((acc, u) => {
acc[u.id] = u
return acc
}, {})

users = users.map((u) => ({
id: u.id,
assignedAt: u.assignedAt,
validUntil: u.validUntil,
displayName: userProfiles[u.id] ? userProfiles[u.id].name : ''
}))
}

reply.send({
...badge,
users
})
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
}
}
})

/**
* Edit organization badge
*/
Expand Down Expand Up @@ -110,10 +174,11 @@ const deleteBadge = routeWrapper({
handler: async function (req, reply) {
try {
const conn = await db()
await conn('organization_badge')
.delete()
.where('id', req.params.badgeId)
return reply.sendStatus(200)
await conn('organization_badge').delete().where('id', req.params.badgeId)
return reply.send({
status: 200,
message: `Badge ${req.params.badgeId} deleted successfully.`
})
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
Expand All @@ -133,21 +198,27 @@ const assignUserBadge = routeWrapper({
userId: yup.number().required().positive().integer()
})
.required(),
body: yup
.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
body: yup.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
},
handler: async function (req, reply) {
try {
const conn = await db()

// user is member
await organization.isMember(req.params.id, req.params.userId)
// user is related to org?
const isMemberOrStaff = await organization.isMemberOrStaff(
req.params.id,
req.params.userId
)

if (!isMemberOrStaff) {
return reply.boom.badRequest('User is not part of the organization.')
}

// assign badge
const [badge] = await conn('user_badge')
const [badge] = await conn('user_badges')
.insert({
user_id: req.params.userId,
badge_id: req.params.badgeId,
Expand All @@ -159,7 +230,13 @@ const assignUserBadge = routeWrapper({
reply.send(badge)
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
if (err.code === '23505') {
return reply.boom.badRequest('User is already assigned to badge.')
} else {
return reply.boom.badRequest(
'Unexpected error, please try again later.'
)
}
}
}
})
Expand Down Expand Up @@ -197,18 +274,17 @@ const updateUserBadge = routeWrapper({
userId: yup.number().required().positive().integer()
})
.required(),
body: yup
.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
body: yup.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
},
handler: async function (req, reply) {
try {
const conn = await db()

// assign badge
const [badge] = await conn('user_badge')
const [badge] = await conn('user_badges')
.update({
assigned_at: req.body.assigned_at,
valid_until: req.body.valid_until
Expand Down Expand Up @@ -244,7 +320,7 @@ const removeUserBadge = routeWrapper({
const conn = await db()

// delete user badge
await conn('user_badge').delete().where({
await conn('user_badges').delete().where({
user_id: req.params.userId,
badge_id: req.params.badgeId
})
Expand All @@ -260,6 +336,7 @@ const removeUserBadge = routeWrapper({
module.exports = {
listBadges,
createBadge,
getBadge,
patchBadge,
deleteBadge,
assignUserBadge,
Expand Down
27 changes: 27 additions & 0 deletions app/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const {

const {
createBadge,
getBadge,
patchBadge,
deleteBadge,
listBadges,
Expand Down Expand Up @@ -152,6 +153,11 @@ function manageRouter (nextApp) {
can('organization:edit'),
createBadge
)
router.get(
'/api/organizations/:id/badges/:badgeId',
can('organization:edit'),
getBadge
)
router.patch(
'/api/organizations/:id/badges/:badgeId',
can('organization:edit'),
Expand Down Expand Up @@ -275,6 +281,27 @@ function manageRouter (nextApp) {
return nextApp.render(req, res, '/org-edit-team-profile', { id: req.params.id })
})

/**
* Badge pages
* */
router.get(
'/organizations/:id/badges/add',
can('organization:edit'),
(req, res) => {
return nextApp.render(req, res, '/badges/add', { id: req.params.id })
}
)
router.get(
'/organizations/:id/badges/:badgeId',
can('organization:edit'),
(req, res) => {
return nextApp.render(req, res, '/badges/edit', {
id: req.params.id,
badgeId: req.params.badgeId
})
}
)

return router
}

Expand Down
2 changes: 1 addition & 1 deletion app/tests/api/badges-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test.before(async () => {

// Add team member
await orgOwner.agent
.put(`/api/team/${orgTeam1.id}/${orgTeamMember.id}`)
.put(`/api/teams/add/${orgTeam1.id}/${orgTeamMember.id}`)
.expect(200)

// Add manager
Expand Down
6 changes: 6 additions & 0 deletions components/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ function Layout (props) {
padding: 0.5rem 1rem 0.5rem 0.25rem;
margin-right: 1rem;
border: 2px solid ${theme.colors.primaryColor};
}
.form-control :global(input[type="color"]) {
padding: 3px;
height: 2.5rem;
}
.status--alert {
Expand Down
8 changes: 4 additions & 4 deletions components/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function TableHead ({ columns }) {
column.onClick && column.onClick()
}}
>
{column.key}
{column.label || column.key}
</th>
)
})}
Expand Down Expand Up @@ -102,12 +102,12 @@ export default function Table ({ columns, rows, onRowClick }) {
tbody tr {
background: #fff;
cursor: pointer;
${onRowClick && 'cursor: pointer'}
}
tbody tr:hover {
${onRowClick && `tbody tr:hover {
background: ${theme.colors.primaryLite};
}
}`}
`}
</style>
</table>
Expand Down
Loading

0 comments on commit 759c366

Please sign in to comment.