From d9ddc3251605922c64d9c9c3697808ff602e46b1 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Thu, 28 Apr 2022 12:33:10 +0100 Subject: [PATCH 1/8] Fetch and display badges in org profile modal --- components/profile-modal.js | 51 +++++++++++++++++++++++++++++-------- components/svg-square.js | 9 +++++++ pages/organization.js | 16 ++++++++---- 3 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 components/svg-square.js diff --git a/components/profile-modal.js b/components/profile-modal.js index bb83481d..18ae1c02 100644 --- a/components/profile-modal.js +++ b/components/profile-modal.js @@ -3,6 +3,7 @@ import { isEmpty } from 'ramda' import theme from '../styles/theme' import Popup from 'reactjs-popup' import Button from './button' +import SvgSquare from '../components/svg-square' function renderActions (actions) { return ( @@ -43,7 +44,32 @@ function renderActions (actions) { ) } -export default function ProfileModal ({ user, attributes, onClose, actions }) { +function renderBadges (badges) { + if (!badges || badges.length === 0) { + return null + } + + return ( + + {badges.map((b) => ( + + + + + ))} +
+ + {b.name}
+ ) +} + +export default function ProfileModal ({ + user, + attributes, + badges, + onClose, + actions +}) { actions = actions || [] let profileContent =
User does not have a profile
if (!isEmpty(attributes)) { @@ -87,13 +113,18 @@ export default function ProfileModal ({ user, attributes, onClose, actions }) { `} } - return
- { user.img ? : '' } -

- {user.name} - {!isEmpty(actions) && renderActions(actions)} -

- {profileContent} - -
+ return ( +
+ {user.img ? : ''} +

+ {user.name} + {!isEmpty(actions) && renderActions(actions)} +

+ {profileContent} + {renderBadges(badges)} + +
+ ) } diff --git a/components/svg-square.js b/components/svg-square.js new file mode 100644 index 00000000..0c33d9bc --- /dev/null +++ b/components/svg-square.js @@ -0,0 +1,9 @@ +import React from 'react' + +export default function SvgSquare ({ color, size = 20 }) { + return ( + + + + ) +} diff --git a/pages/organization.js b/pages/organization.js index ff68fa15..e9a58849 100644 --- a/pages/organization.js +++ b/pages/organization.js @@ -8,6 +8,7 @@ import SectionHeader from '../components/section-header' import Table from '../components/table' import theme from '../styles/theme' import AddMemberForm from '../components/add-member-form' +import SvgSquare from '../components/svg-square' import Button from '../components/button' import Modal from 'react-modal' import ProfileModal from '../components/profile-modal' @@ -74,10 +75,18 @@ export default class Organization extends Component { const { id } = this.props try { + // Fetch profile attributes const profileInfo = await getUserOrgProfile(id, user.id) + + // Fetch badges for this organization + const profileBadges = ( + await apiClient.get(`/user/${user.id}/badges`) + ).badges.filter((b) => b.organization_id === parseInt(id)) + this.setState({ profileInfo, profileMeta: user, + profileBadges, modalIsOpen: true }) } catch (e) { @@ -233,11 +242,7 @@ export default class Organization extends Component { { return { ...row, - color: () => ( - - - - ) + color: () => } })} columns={columns} onRowClick={ ({ id: badgeId }) => Router.push( @@ -415,6 +420,7 @@ export default class Organization extends Component { }} isOpen={this.state.modalIsOpen}> Date: Fri, 29 Apr 2022 10:58:08 +0100 Subject: [PATCH 2/8] Disable badge assignment by osm id --- pages/badges/edit.js | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/pages/badges/edit.js b/pages/badges/edit.js index 917b2ceb..73970f01 100644 --- a/pages/badges/edit.js +++ b/pages/badges/edit.js @@ -94,7 +94,7 @@ export default class EditBadge extends Component { { key: 'validUntil', label: 'Valid Until' } ] - const { badge, assignablePeople } = this.state + const { badge } = this.state const users = (badge && badge.users) || [] return ( @@ -102,42 +102,6 @@ export default class EditBadge extends Component {

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 ( -
- - - - ) - }} - />
From 75027dea07d100884ee8b3599ba18ed3ddcc0943 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Fri, 29 Apr 2022 11:00:56 +0100 Subject: [PATCH 3/8] Display empty table message when a badge has no members assigned. --- pages/badges/edit.js | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pages/badges/edit.js b/pages/badges/edit.js index 73970f01..6a705ee0 100644 --- a/pages/badges/edit.js +++ b/pages/badges/edit.js @@ -105,24 +105,23 @@ export default class EditBadge extends Component { - {users.length > 0 && ( -
({ - ...u, - assignedAt: u.assignedAt && toDateString(u.assignedAt), - validUntil: u.validUntil && toDateString(u.validUntil) - }))} - columns={columns} - onRowClick={({ id }) => - Router.push( - join( - URL, - `/organizations/${orgId}/badges/${badgeId}/assign/${id}` - ) +
({ + ...u, + assignedAt: u.assignedAt && toDateString(u.assignedAt), + validUntil: u.validUntil && toDateString(u.validUntil) + }))} + emptyPlaceHolder='No members have this badge assigned. Badges can be assigned via user profile actions.' + columns={columns} + onRowClick={({ id }) => + Router.push( + join( + URL, + `/organizations/${orgId}/badges/${badgeId}/assign/${id}` ) - } - /> - )} + ) + } + /> ) } From 0eb74fbe375680edeef657982a547d6db2956c31 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Fri, 29 Apr 2022 13:06:19 +0100 Subject: [PATCH 4/8] Create separated pages for badge assignments actions (new/edit) --- app/manage/index.js | 14 +- .../assign.js => badges-assignment/edit.js} | 105 ++++----- pages/badges-assignment/new.js | 213 ++++++++++++++++++ 3 files changed, 274 insertions(+), 58 deletions(-) rename pages/{badges/assign.js => badges-assignment/edit.js} (71%) create mode 100644 pages/badges-assignment/new.js diff --git a/app/manage/index.js b/app/manage/index.js index a44ca0be..4fd7b603 100644 --- a/app/manage/index.js +++ b/app/manage/index.js @@ -302,18 +302,18 @@ function manageRouter (nextApp) { } ) - // Use same page for two routes - const assignBadgePageRoute = [ - can('organization:edit'), - (req, res) => nextApp.render(req, res, '/badges/assign', req.params) - ] + // New badge assignment router.get( '/organizations/:id/badges/assign/:userId', - ...assignBadgePageRoute + can('organization:edit'), + (req, res) => nextApp.render(req, res, '/badges-assignment/new', req.params) ) + + // Edit badge assignment router.get( '/organizations/:id/badges/:badgeId/assign/:userId', - ...assignBadgePageRoute + can('organization:edit'), + (req, res) => nextApp.render(req, res, '/badges-assignment/edit', req.params) ) return router diff --git a/pages/badges/assign.js b/pages/badges-assignment/edit.js similarity index 71% rename from pages/badges/assign.js rename to pages/badges-assignment/edit.js index 9298cb7b..f65e2b4c 100644 --- a/pages/badges/assign.js +++ b/pages/badges-assignment/edit.js @@ -1,4 +1,5 @@ import React, { Component } from 'react' +import * as Yup from 'yup' import { Formik, Field, Form } from 'formik' import APIClient from '../../lib/api-client' import Button from '../../components/button' @@ -7,6 +8,7 @@ import { toast } from 'react-toastify' import join from 'url-join' import Router from 'next/router' import getConfig from 'next/config' +import { id } from 'date-fns/locale' const { publicRuntimeConfig } = getConfig() const URL = publicRuntimeConfig.APP_URL @@ -39,13 +41,13 @@ function Section ({ children }) { ) } -export default class AssignBadge extends Component { +export default class EditBadgeAssignment extends Component { static async getInitialProps ({ query }) { if (query) { return { orgId: query.id, - badgeId: query.badgeId, - userId: query.userId + badgeId: parseInt(query.badgeId), + userId: parseInt(query.userId) } } } @@ -64,22 +66,26 @@ export default class AssignBadge extends Component { } async loadData () { - const { orgId, badgeId } = this.props + const { orgId, badgeId, userId } = this.props try { const org = await apiClient.get(`/organizations/${orgId}`) - let badge, badges + const badge = await apiClient.get( + `/organizations/${orgId}/badges/${badgeId}` + ) + let assignment + if (badge && badge.users) { + assignment = badge.users.find((u) => u.id === parseInt(userId)) + } - if (badgeId) { - badge = await apiClient.get(`/organizations/${orgId}/badges/${badgeId}`) - } else { - badges = await apiClient.get(`/organizations/${orgId}/badges`) + if (!assignment) { + throw Error('Badge assignment not found.') } this.setState({ org, badge, - badges + assignment }) } catch (error) { console.error(error) @@ -99,8 +105,8 @@ export default class AssignBadge extends Component { return
Loading...
} - const { orgId, userId } = this.props - const { badges, badge, user } = this.state + const { orgId, userId, badgeId } = this.props + const { badge, assignment } = this.state return ( <> @@ -111,66 +117,57 @@ export default class AssignBadge extends Component { { + validationSchema={Yup.object().shape({ + assignedAt: Yup.date().required( + 'Please select an assignment date.' + ), + validUntil: Yup.date().when( + 'assignedAt', + (assignedAt, schema) => + assignedAt && + schema.min( + assignedAt, + 'End date must be after the start date.' + ) + ) + })} + 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 - ) - Router.push( - join( - URL, - `/organizations/${orgId}/badges/${badgeId}/assign/${userId}` - ) - ) - } else { - await apiClient.patch( - `/organizations/${orgId}/member/${userId}/badge/${badgeId}`, - payload - ) - toast.info('Badge updated successfully.') - } + 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 }) => { + render={({ isSubmitting, values, errors, touched }) => { return (

User: {userId} (OSM id)

- {badge ? ( -
-

Badge: {badge && badge.name}

-
- ) : ( -
- - - - {badges.map((b) => ( - - ))} - -
- )} +
+

Badge: {badge && badge.name}

+
+ {errors.assignedAt && ( +
{errors.assignedAt}
+ )}
@@ -186,6 +186,9 @@ export default class AssignBadge extends Component { type='date' value={values.validUntil} /> + {errors.validUntil && ( +
{errors.validUntil}
+ )}