diff --git a/app/manage/badges.js b/app/manage/badges.js
index 191b4a6b..66c9055d 100644
--- a/app/manage/badges.js
+++ b/app/manage/badges.js
@@ -199,8 +199,8 @@ const assignUserBadge = routeWrapper({
})
.required(),
body: yup.object({
- assigned_at: yup.date().optional(),
- valid_until: yup.date().optional()
+ assigned_at: yup.date().required(),
+ valid_until: yup.date().nullable()
})
},
handler: async function (req, reply) {
@@ -218,12 +218,13 @@ const assignUserBadge = routeWrapper({
}
// assign badge
+ const { assigned_at, valid_until } = req.body
const [badge] = await conn('user_badges')
.insert({
user_id: req.params.userId,
badge_id: req.params.badgeId,
- assigned_at: req.body.assigned_at,
- valid_until: req.body.valid_until
+ assigned_at: assigned_at.toISOString(),
+ valid_until: valid_until ? valid_until.toISOString() : null
})
.returning('*')
@@ -275,19 +276,22 @@ const updateUserBadge = routeWrapper({
})
.required(),
body: yup.object({
- assigned_at: yup.date().optional(),
- valid_until: yup.date().optional()
+ assigned_at: yup.date().required(),
+ valid_until: yup.date().nullable()
})
},
handler: async function (req, reply) {
try {
const conn = await db()
- // assign badge
+ const { assigned_at, valid_until } = req.body
+
+ // Yup validation returns time-zoned dates, update query use UTC strings
+ // to avoid that.
const [badge] = await conn('user_badges')
.update({
- assigned_at: req.body.assigned_at,
- valid_until: req.body.valid_until
+ assigned_at: assigned_at.toISOString(),
+ valid_until: valid_until ? valid_until.toISOString() : null
})
.where({
user_id: req.params.userId,
diff --git a/app/manage/index.js b/app/manage/index.js
index 553d9d90..9b74da64 100644
--- a/app/manage/index.js
+++ b/app/manage/index.js
@@ -301,6 +301,13 @@ function manageRouter (nextApp) {
})
}
)
+ router.get(
+ '/organizations/:id/badges/:badgeId/assign/:userId',
+ can('organization:edit'),
+ (req, res) => {
+ return nextApp.render(req, res, '/badges/assign', req.params)
+ }
+ )
return router
}
diff --git a/app/tests/api/badges-api.test.js b/app/tests/api/badges-api.test.js
index d6e112ec..ea994f4f 100644
--- a/app/tests/api/badges-api.test.js
+++ b/app/tests/api/badges-api.test.js
@@ -366,6 +366,7 @@ test('Update badge', async (t) => {
const badgeAssignment = (await orgOwner.agent
.patch(updateBadgeRoute)
.send({
+ assigned_at: '2020-01-01Z',
valid_until: '2021-01-01Z'
})
.expect(200)).body
@@ -373,6 +374,7 @@ test('Update badge', async (t) => {
t.like(badgeAssignment, {
badge_id: badge2.id,
user_id: orgTeamMember.id,
+ assigned_at: '2020-01-01T00:00:00.000Z',
valid_until: '2021-01-01T00:00:00.000Z'
})
})
diff --git a/components/button.js b/components/button.js
index 77472078..15db8c78 100644
--- a/components/button.js
+++ b/components/button.js
@@ -78,7 +78,7 @@ export default function Button ({ name, id, value, variant, type, disabled, href
if (href) {
let fullUrl
(href.startsWith('http')) ? (fullUrl = href) : (fullUrl = join(publicRuntimeConfig.APP_URL, href))
- return {children}
+ return {children || value}
}
return
{children}
}
diff --git a/package.json b/package.json
index ed5ecb03..daf9c176 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"connect-session-knex": "^2.1.1",
"cors": "^2.8.5",
"csurf": "^1.11.0",
+ "date-fns": "^2.28.0",
"dotenv": "^6.2.0",
"dotenv-webpack": "^1.8.0",
"express": "^4.17.2",
diff --git a/pages/badges/assign.js b/pages/badges/assign.js
new file mode 100644
index 00000000..3ee4b7aa
--- /dev/null
+++ b/pages/badges/assign.js
@@ -0,0 +1,171 @@
+import React, { Component } from 'react'
+import { Formik, Field, Form } from 'formik'
+import APIClient from '../../lib/api-client'
+import Button from '../../components/button'
+import { format } from 'date-fns'
+import { toast } from 'react-toastify'
+
+const apiClient = new APIClient()
+
+function ButtonWrapper ({ children }) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+export default class AssignBadge extends Component {
+ static async getInitialProps ({ query }) {
+ if (query) {
+ return {
+ orgId: query.id,
+ badgeId: query.badgeId,
+ userId: query.userId
+ }
+ }
+ }
+
+ constructor (props) {
+ super(props)
+ this.state = {}
+
+ this.loadData = this.loadData.bind(this)
+ }
+
+ async componentDidMount () {
+ this.loadData()
+ }
+
+ async loadData () {
+ const { orgId, badgeId, userId } = this.props
+ try {
+ const [org, badge] = await Promise.all([
+ apiClient.get(`/organizations/${orgId}`),
+ apiClient.get(`/organizations/${orgId}/badges/${badgeId}`)
+ ])
+
+ // Check if user already has the badge
+ const user =
+ badge.users && badge.users.find((u) => u.id === parseInt(userId))
+
+ this.setState({
+ org,
+ badge,
+ user
+ })
+ } catch (error) {
+ console.error(error)
+ this.setState({
+ error,
+ loading: false
+ })
+ }
+ }
+
+ renderPageInner () {
+ if (this.state.error) {
+ return An unexpected error occurred, please try again later.
+ }
+
+ if (!this.state.org && !this.state.badge) {
+ return Loading...
+ }
+
+ const { orgId, badgeId, userId } = this.props
+ const { badge, user } = this.state
+
+ return (
+ <>
+
+
{badge.name} Badge
+
+
+
+
User: {userId} (OSM id)
+
+ {
+ try {
+ const payload = {
+ assigned_at: assignedAt,
+ valid_until: validUntil !== '' ? validUntil : null
+ }
+
+ if (!user) {
+ await apiClient.post(
+ `/organizations/${orgId}/badges/${badgeId}/assign/${userId}`,
+ payload
+ )
+ toast.info('Badge assigned successfully.')
+ } else {
+ await apiClient.patch(
+ `/organizations/${orgId}/member/${userId}/badge/${badgeId}`,
+ payload
+ )
+ toast.info('Badge updated successfully.')
+ }
+ this.loadData()
+ } catch (error) {
+ console.log(error)
+ toast.error(`Unexpected error, please try again later.`)
+ }
+ }}
+ render={({ isSubmitting, values, errors }) => {
+ return (
+
+ )
+ }}
+ />
+
+ >
+ )
+ }
+
+ render () {
+ return {this.renderPageInner()}
+ }
+}
diff --git a/pages/badges/edit.js b/pages/badges/edit.js
index 0c55b89c..917b2ceb 100644
--- a/pages/badges/edit.js
+++ b/pages/badges/edit.js
@@ -9,7 +9,6 @@ import getConfig from 'next/config'
import { toast } from 'react-toastify'
import theme from '../../styles/theme'
import Table from '../../components/table'
-import AddMemberForm from '../../components/add-member-form'
import { toDateString } from '../../app/lib/utils'
const { publicRuntimeConfig } = getConfig()
@@ -62,13 +61,21 @@ export default class EditBadge extends Component {
async loadData () {
const { orgId, badgeId } = this.props
try {
- const [org, badge] = await Promise.all([
- getOrg(orgId),
- apiClient.get(`/organizations/${orgId}/badges/${badgeId}`)
- ])
+ const [org, badge, { members }, { managers, owners }] = await Promise.all(
+ [
+ getOrg(orgId),
+ apiClient.get(`/organizations/${orgId}/badges/${badgeId}`),
+ apiClient.get(`/organizations/${orgId}/members`),
+ apiClient.get(`/organizations/${orgId}/staff`)
+ ]
+ )
+
+ const assignablePeople = members.concat(managers).concat(owners)
+
this.setState({
org,
- badge
+ badge,
+ assignablePeople
})
} catch (error) {
console.error(error)
@@ -83,31 +90,57 @@ export default class EditBadge extends Component {
const columns = [
{ key: 'id', label: 'OSM ID' },
{ key: 'displayName', label: 'Display Name' },
- { key: 'assignedAt', label: 'Assigned At' }
+ { key: 'assignedAt', label: 'Assigned At' },
+ { key: 'validUntil', label: 'Valid Until' }
]
- const { badge } = this.state
+ const { badge, assignablePeople } = this.state
const users = (badge && badge.users) || []
return (
-
-
Assigned Members
+
+
+
Assigned Members
+ {
+ const user = assignablePeople.find(
+ (p) =>
+ p.id === osmIdentifier ||
+ p.name.toLowerCase() === osmIdentifier.toLowerCase()
+ )
+ if (!user) {
+ toast.error('User is not part of this organization.')
+ } else {
+ Router.push(
+ join(
+ URL,
+ `/organizations/${orgId}/badges/${badgeId}/assign/${user.id}`
+ )
+ )
+ }
+ }}
+ render={({ values }) => {
+ return (
+
+ )
+ }}
+ />
+
-
{
- try {
- await apiClient.post(
- `/organizations/${orgId}/badges/${badgeId}/assign/${osmId}`
- )
- this.loadData()
- } catch (error) {
- toast.error(error.message)
- }
- }}
- />
-
{users.length > 0 && (
({
@@ -116,6 +149,14 @@ export default class EditBadge extends Component {
validUntil: u.validUntil && toDateString(u.validUntil)
}))}
columns={columns}
+ onRowClick={({ id }) =>
+ Router.push(
+ join(
+ URL,
+ `/organizations/${orgId}/badges/${badgeId}/assign/${id}`
+ )
+ )
+ }
/>
)}
@@ -163,7 +204,7 @@ export default class EditBadge extends Component {
color
}
)
- Router.push(join(URL, `/organizations/${orgId}`))
+ toast.success('Badge updated successfully.')
} catch (error) {
toast.error(
`There was an error editing badge '${name}'. Please try again later.`
@@ -207,13 +248,8 @@ export default class EditBadge extends Component {
/>