Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve badge assignment #263

Merged
merged 6 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions app/manage/badges.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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('*')

Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions app/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions app/tests/api/badges-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,15 @@ 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

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'
})
})
Expand Down
2 changes: 1 addition & 1 deletion components/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href={fullUrl} className={[`button`, variant, size].join(' ')} disabled={disabled} name={name} id={id}>{children}<style jsx>{style}</style></a>
return <a href={fullUrl} className={[`button`, variant, size].join(' ')} disabled={disabled} name={name} id={id}>{children || value}<style jsx>{style}</style></a>
}
return <div onClick={onClick} className={[`button`, variant, size].join(' ')} disabled={disabled}>{children}<style jsx>{style}</style></div>
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
171 changes: 171 additions & 0 deletions pages/badges/assign.js
Original file line number Diff line number Diff line change
@@ -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 (
<div>
{children}
<style jsx global>{`
.button {
margin-right: 10px;
}
}`}</style>
</div>
)
}

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 <div>An unexpected error occurred, please try again later.</div>
}

if (!this.state.org && !this.state.badge) {
return <div>Loading...</div>
}

const { orgId, badgeId, userId } = this.props
const { badge, user } = this.state

return (
<>
<div className='page__heading'>
<h1>{badge.name} Badge</h1>
</div>
<section>
<div className='page__heading'>
<h2>User: {userId} (OSM id)</h2>
</div>
<Formik
initialValues={{
assignedAt:
(user && user.assignedAt && user.assignedAt.substring(0, 10)) ||
format(Date.now(), 'yyyy-MM-dd'),
validUntil:
(user && user.validUntil && user.validUntil.substring(0, 10)) ||
''
}}
onSubmit={async ({ assignedAt, validUntil }) => {
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 (
<Form>
<div className='form-control form-control__vertical'>
<label htmlFor='assignedAt'>Assigned At (required)</label>
<Field
name='assignedAt'
type='date'
value={values.assignedAt}
/>
</div>
<div className='form-control form-control__vertical'>
<label htmlFor='validUntil'>Valid Until</label>
<Field
name='validUntil'
type='date'
value={values.validUntil}
/>
</div>
<ButtonWrapper>
<Button
disabled={isSubmitting}
variant='primary'
type='submit'
value={user ? 'Update' : 'Assign'}
/>
<Button
variant='small'
href={`/organizations/${orgId}/badges/${badgeId}`}
value='Go to badge view'
/>
</ButtonWrapper>
</Form>
)
}}
/>
</section>
</>
)
}

render () {
return <article className='inner page'>{this.renderPageInner()}</article>
}
}
Loading