Skip to content

Commit 1406af4

Browse files
authored
Merge pull request #269 from developmentseed/enhance/display-profile-badges
Add badges to profile modal and other improvements
2 parents a35cc1d + b7cf630 commit 1406af4

File tree

8 files changed

+365
-133
lines changed

8 files changed

+365
-133
lines changed

app/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Set server timezone to UTC to avoid issues with date parsing
2+
process.env.TZ = 'UTC'
3+
14
const path = require('path')
25
const express = require('express')
36
const bodyParser = require('body-parser')

app/manage/index.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,18 @@ function manageRouter (nextApp) {
328328
}
329329
)
330330

331-
// Use same page for two routes
332-
const assignBadgePageRoute = [
333-
can('organization:edit'),
334-
(req, res) => nextApp.render(req, res, '/badges/assign', req.params)
335-
]
331+
// New badge assignment
336332
router.get(
337333
'/organizations/:id/badges/assign/:userId',
338-
...assignBadgePageRoute
334+
can('organization:edit'),
335+
(req, res) => nextApp.render(req, res, '/badges-assignment/new', req.params)
339336
)
337+
338+
// Edit badge assignment
340339
router.get(
341340
'/organizations/:id/badges/:badgeId/assign/:userId',
342-
...assignBadgePageRoute
341+
can('organization:edit'),
342+
(req, res) => nextApp.render(req, res, '/badges-assignment/edit', req.params)
343343
)
344344

345345
return router

components/profile-modal.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isEmpty } from 'ramda'
33
import theme from '../styles/theme'
44
import Popup from 'reactjs-popup'
55
import Button from './button'
6+
import SvgSquare from '../components/svg-square'
67

78
function renderActions (actions) {
89
return (
@@ -43,7 +44,32 @@ function renderActions (actions) {
4344
)
4445
}
4546

46-
export default function ProfileModal ({ user, attributes, onClose, actions }) {
47+
function renderBadges (badges) {
48+
if (!badges || badges.length === 0) {
49+
return null
50+
}
51+
52+
return (
53+
<table>
54+
{badges.map((b) => (
55+
<tr>
56+
<td>
57+
<SvgSquare color={b.color} />
58+
</td>
59+
<td>{b.name}</td>
60+
</tr>
61+
))}
62+
</table>
63+
)
64+
}
65+
66+
export default function ProfileModal ({
67+
user,
68+
attributes,
69+
badges,
70+
onClose,
71+
actions
72+
}) {
4773
actions = actions || []
4874
let profileContent = <dl>User does not have a profile</dl>
4975
if (!isEmpty(attributes)) {
@@ -87,13 +113,18 @@ export default function ProfileModal ({ user, attributes, onClose, actions }) {
87113
`}</style>
88114
</>
89115
}
90-
return <article className='modal__details'>
91-
{ user.img ? <img src={user.img} /> : '' }
92-
<h2 style={{ display: 'flex', 'justifyContent': 'space-between' }}>
93-
<span>{user.name}</span>
94-
{!isEmpty(actions) && renderActions(actions)}
95-
</h2>
96-
{profileContent}
97-
<Button size='small' onClick={() => onClose()}>close</Button>
98-
</article>
116+
return (
117+
<article className='modal__details'>
118+
{user.img ? <img src={user.img} /> : ''}
119+
<h2 style={{ display: 'flex', justifyContent: 'space-between' }}>
120+
<span>{user.name}</span>
121+
{!isEmpty(actions) && renderActions(actions)}
122+
</h2>
123+
{profileContent}
124+
{renderBadges(badges)}
125+
<Button size='small' onClick={() => onClose()}>
126+
close
127+
</Button>
128+
</article>
129+
)
99130
}

components/svg-square.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react'
2+
3+
export default function SvgSquare ({ color, size = 20 }) {
4+
return (
5+
<svg width={size} height={size}>
6+
<rect width={size} height={size} style={{ fill: color }} />
7+
</svg>
8+
)
9+
}

pages/badges/assign.js renamed to pages/badges-assignment/edit.js

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { Component } from 'react'
2+
import * as Yup from 'yup'
23
import { Formik, Field, Form } from 'formik'
34
import APIClient from '../../lib/api-client'
45
import Button from '../../components/button'
@@ -39,13 +40,13 @@ function Section ({ children }) {
3940
)
4041
}
4142

42-
export default class AssignBadge extends Component {
43+
export default class EditBadgeAssignment extends Component {
4344
static async getInitialProps ({ query }) {
4445
if (query) {
4546
return {
4647
orgId: query.id,
47-
badgeId: query.badgeId,
48-
userId: query.userId
48+
badgeId: parseInt(query.badgeId),
49+
userId: parseInt(query.userId)
4950
}
5051
}
5152
}
@@ -64,22 +65,26 @@ export default class AssignBadge extends Component {
6465
}
6566

6667
async loadData () {
67-
const { orgId, badgeId } = this.props
68+
const { orgId, badgeId, userId } = this.props
6869

6970
try {
7071
const org = await apiClient.get(`/organizations/${orgId}`)
71-
let badge, badges
72+
const badge = await apiClient.get(
73+
`/organizations/${orgId}/badges/${badgeId}`
74+
)
75+
let assignment
76+
if (badge && badge.users) {
77+
assignment = badge.users.find((u) => u.id === parseInt(userId))
78+
}
7279

73-
if (badgeId) {
74-
badge = await apiClient.get(`/organizations/${orgId}/badges/${badgeId}`)
75-
} else {
76-
badges = await apiClient.get(`/organizations/${orgId}/badges`)
80+
if (!assignment) {
81+
throw Error('Badge assignment not found.')
7782
}
7883

7984
this.setState({
8085
org,
8186
badge,
82-
badges
87+
assignment
8388
})
8489
} catch (error) {
8590
console.error(error)
@@ -99,8 +104,8 @@ export default class AssignBadge extends Component {
99104
return <div>Loading...</div>
100105
}
101106

102-
const { orgId, userId } = this.props
103-
const { badges, badge, user } = this.state
107+
const { orgId, userId, badgeId } = this.props
108+
const { badge, assignment } = this.state
104109

105110
return (
106111
<>
@@ -111,73 +116,67 @@ export default class AssignBadge extends Component {
111116
<Formik
112117
initialValues={{
113118
assignedAt:
114-
(user && user.assignedAt && user.assignedAt.substring(0, 10)) ||
119+
(assignment &&
120+
assignment.assignedAt &&
121+
assignment.assignedAt.substring(0, 10)) ||
115122
format(Date.now(), 'yyyy-MM-dd'),
116123
validUntil:
117-
(user && user.validUntil && user.validUntil.substring(0, 10)) ||
124+
(assignment &&
125+
assignment.validUntil &&
126+
assignment.validUntil.substring(0, 10)) ||
118127
''
119128
}}
120-
onSubmit={async ({ assignedAt, validUntil, badgeId }) => {
129+
validationSchema={Yup.object().shape({
130+
assignedAt: Yup.date().required(
131+
'Please select an assignment date.'
132+
),
133+
validUntil: Yup.date().when(
134+
'assignedAt',
135+
(assignedAt, schema) =>
136+
assignedAt &&
137+
schema.min(
138+
assignedAt,
139+
'End date must be after the start date.'
140+
)
141+
)
142+
})}
143+
onSubmit={async ({ assignedAt, validUntil }) => {
121144
try {
122145
const payload = {
123146
assigned_at: assignedAt,
124147
valid_until: validUntil !== '' ? validUntil : null
125148
}
126149

127-
if (!user) {
128-
await apiClient.post(
129-
`/organizations/${orgId}/badges/${badgeId}/assign/${userId}`,
130-
payload
131-
)
132-
Router.push(
133-
join(
134-
URL,
135-
`/organizations/${orgId}/badges/${badgeId}/assign/${userId}`
136-
)
137-
)
138-
} else {
139-
await apiClient.patch(
140-
`/organizations/${orgId}/member/${userId}/badge/${badgeId}`,
141-
payload
142-
)
143-
toast.info('Badge updated successfully.')
144-
}
150+
await apiClient.patch(
151+
`/organizations/${orgId}/member/${userId}/badge/${badgeId}`,
152+
payload
153+
)
154+
toast.info('Badge updated successfully.')
145155
this.loadData()
146156
} catch (error) {
147157
console.log(error)
148158
toast.error(`Unexpected error, please try again later.`)
149159
}
150160
}}
151-
render={({ isSubmitting, values }) => {
161+
render={({ isSubmitting, values, errors }) => {
152162
return (
153163
<Form>
154164
<div className='page__heading'>
155165
<h2>User: {userId} (OSM id)</h2>
156166
</div>
157-
{badge ? (
158-
<div className='page__heading'>
159-
<h2>Badge: {badge && badge.name}</h2>
160-
</div>
161-
) : (
162-
<div className='form-control form-control__vertical'>
163-
<label htmlFor='badgeId'>Badge:</label>
164-
<Field as='select' name='badgeId'>
165-
<option value=''>Select a badge</option>
166-
{badges.map((b) => (
167-
<option key={b.id} value={b.id}>
168-
{b.name}
169-
</option>
170-
))}
171-
</Field>
172-
</div>
173-
)}
167+
<div className='page__heading'>
168+
<h2>Badge: {badge && badge.name}</h2>
169+
</div>
174170
<div className='form-control form-control__vertical'>
175171
<label htmlFor='assignedAt'>Assigned At (required)</label>
176172
<Field
177173
name='assignedAt'
178174
type='date'
179175
value={values.assignedAt}
180176
/>
177+
{errors.assignedAt && (
178+
<div className='form--error'>{errors.assignedAt}</div>
179+
)}
181180
</div>
182181
<div className='form-control form-control__vertical'>
183182
<label htmlFor='validUntil'>Valid Until</label>
@@ -186,6 +185,9 @@ export default class AssignBadge extends Component {
186185
type='date'
187186
value={values.validUntil}
188187
/>
188+
{errors.validUntil && (
189+
<div className='form--error'>{errors.validUntil}</div>
190+
)}
189191
</div>
190192
<ButtonWrapper>
191193
<Button

0 commit comments

Comments
 (0)