From d76cee24d78279ff0c647c2285b979f47344c615 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 24 Sep 2019 13:28:12 +0300 Subject: [PATCH 01/27] Create travel grant module --- backend/modules/routes.js | 2 + backend/modules/travel-grant/controller.js | 14 +++++ backend/modules/travel-grant/model.js | 31 +++++++++++ backend/modules/travel-grant/routes.js | 52 +++++++++++++++++++ .../OrganiserEditEventGrants.module.scss | 0 .../OrganiserEditEventGrants/index.js | 1 + shared/constants/misc.js | 26 ++++++++++ 7 files changed, 126 insertions(+) create mode 100644 backend/modules/travel-grant/controller.js create mode 100644 backend/modules/travel-grant/model.js create mode 100644 backend/modules/travel-grant/routes.js create mode 100644 frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/OrganiserEditEventGrants.module.scss create mode 100644 frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js diff --git a/backend/modules/routes.js b/backend/modules/routes.js index cdd954964..d5de48690 100644 --- a/backend/modules/routes.js +++ b/backend/modules/routes.js @@ -7,6 +7,7 @@ const newsletterRouter = require('./newsletter/routes'); const teamRouter = require('./team/routes'); const emailRouter = require('./email-task/routes'); const devToolsRouter = require('./devtools/routes'); +const travelGrantRouter = require('./travel-grant/routes'); module.exports = function(app) { app.get('/api', (req, res) => { @@ -24,6 +25,7 @@ module.exports = function(app) { app.use('/api/teams', teamRouter); app.use('/api/user-profiles', userProfileRouter); app.use('/api/registrations', registrationRouter); + app.use('/api/travel-grants', travelGrantRouter); /** Admin tools (development only) */ if (global.gConfig.DEVTOOLS_ENABLED) { diff --git a/backend/modules/travel-grant/controller.js b/backend/modules/travel-grant/controller.js new file mode 100644 index 000000000..cb642c46a --- /dev/null +++ b/backend/modules/travel-grant/controller.js @@ -0,0 +1,14 @@ +const TravelGrant = require('./model'); +const { InsufficientPrivilegesError, ForbiddenError, NotFoundError } = require('../../common/errors/errors'); + +const controller = {}; + +controller.getTravelGrantsForEvent = eventId => { + return TravelGrant.find({ event: eventId }); +}; + +controller.getTravelGrantForUser = (userId, eventId) => { + return TravelGrant.findOne({ event: eventId, user: userId }); +}; + +module.exports = controller; diff --git a/backend/modules/travel-grant/model.js b/backend/modules/travel-grant/model.js new file mode 100644 index 000000000..86a833fa6 --- /dev/null +++ b/backend/modules/travel-grant/model.js @@ -0,0 +1,31 @@ +const mongoose = require('mongoose'); +const { Misc } = require('@hackjunction/shared'); +const CloudinaryImageSchema = require('../../common/schemas/CloudinaryImage'); + +const TravelGrantSchema = new mongoose.Schema({ + event: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + user: { + type: String, + required: true + }, + travelsFrom: { + type: String, + required: true + }, + status: { + type: String, + enum: Misc.travelGrantStatuses.ids + }, + receipt: CloudinaryImageSchema +}); + +TravelGrantSchema.index({ event: 1, user: 1 }, { unique: true }); +TravelGrantSchema.set('timestamps', true); + +const TravelGrant = mongoose.model('TravelGrant', TravelGrantSchema); + +module.exports = TravelGrant; diff --git a/backend/modules/travel-grant/routes.js b/backend/modules/travel-grant/routes.js new file mode 100644 index 000000000..12a2ad270 --- /dev/null +++ b/backend/modules/travel-grant/routes.js @@ -0,0 +1,52 @@ +const express = require('express'); +const router = express.Router(); +const asyncHandler = require('express-async-handler'); + +const { Auth } = require('@hackjunction/shared'); + +const TravelGrantController = require('./controller'); + +const { hasToken } = require('../../common/middleware/token'); +const { hasPermission } = require('../../common/middleware/permissions'); +const { hasRegisteredToEvent, isEventOrganiser } = require('../../common/middleware/events'); + +const getTravelGrantsForEvent = asyncHandler(async (req, res) => { + const travelGrants = await TravelGrantController.getTravelGrantsForEvent(req.event._id.toString()); + return res.status(200).json(travelGrants); +}); + +const getTravelGrantForUser = asyncHandler(async (req, res) => { + const travelGrant = await TravelGrantController.getTravelGrantForUser(req.user.sub, req.event._id.toString()); + return res.status(200).json(travelGrant); +}); + +router.route('/:slug').get(hasToken, hasRegisteredToEvent, getTravelGrantForUser); + +router + .route('/:slug/all') + .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getTravelGrantsForEvent); + +// /** Organiser routes */ +// router +// .route('/organiser/:slug') +// .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getTeamsForEvent); + +// /** User-facing routes */ +// router +// .route('/:slug') +// .get(hasToken, hasRegisteredToEvent, getTeamForEvent) +// .post(hasToken, hasRegisteredToEvent, createTeamForEvent); + +// router +// .route('/:slug/:code') +// .delete(hasToken, hasRegisteredToEvent, deleteTeamForEvent) +// .patch(hasToken, hasRegisteredToEvent, lockTeamForEvent); + +// router +// .route('/:slug/:code/members') +// .post(hasToken, hasRegisteredToEvent, joinTeamForEvent) +// .delete(hasToken, hasRegisteredToEvent, leaveTeamForEvent); + +// router.route('/:slug/:code/members/:userId').delete(hasToken, hasRegisteredToEvent, removeMemberFromTeam); + +module.exports = router; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/OrganiserEditEventGrants.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/OrganiserEditEventGrants.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js new file mode 100644 index 000000000..aaf404641 --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js @@ -0,0 +1 @@ +import React from 'react'; diff --git a/shared/constants/misc.js b/shared/constants/misc.js index 7d7c3c49f..9ae1fef4d 100644 --- a/shared/constants/misc.js +++ b/shared/constants/misc.js @@ -57,6 +57,25 @@ const relocationOptions = { } }; +const travelGrantStatuses = { + pending: { + id: 'pending', + label: 'Pending' + }, + accepted: { + id: 'accepted', + label: 'Accepted' + }, + rejected: { + id: 'rejected', + label: 'Rejected' + }, + confirmed: { + id: 'confirmed', + label: 'Confirmed' + } +}; + const dietaryRestrictions = [ 'Vegan', 'Vegetarian', @@ -91,6 +110,13 @@ Misc.relocationOptions = { getLabelForValue: value => (relocationOptions.hasOwnProperty(value) ? relocationOptions[value].label : '') }; +Misc.travelGrantStatuses = { + items: travelGrantStatuses, + ids: Object.keys(travelGrantStatuses), + asArray: Object.keys(travelGrantStatuses).map(id => travelGrantStatuses[id]), + getLabelForValue: value => (travelGrantStatuses.hasOwnProperty(value) ? travelGrantStatuses[value].label : '') +}; + Misc.tShirtSizes = tShirtSizes; Misc.dietaryRestrictions = dietaryRestrictions; From d80dccd704b70b5da5680c60470e3a65b661ea56 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 24 Sep 2019 14:40:30 +0300 Subject: [PATCH 02/27] Placeholder for travel grants page, test including material-ui --- frontend/package-lock.json | 257 ++++++++++++++++++ frontend/package.json | 1 + frontend/src/components/PageWrapper/index.js | 20 +- frontend/src/index.js | 5 +- frontend/src/material-ui-theme.js | 10 + .../OrganiserEditEventGrants/index.js | 36 ++- .../OrganiserEditEvent/index.js | 7 + frontend/src/redux/organiser/actionTypes.js | 2 + frontend/src/redux/organiser/actions.js | 15 + frontend/src/redux/organiser/reducer.js | 11 + frontend/src/redux/organiser/selectors.js | 25 +- frontend/src/services/travelGrants.js | 21 ++ 12 files changed, 403 insertions(+), 7 deletions(-) create mode 100644 frontend/src/material-ui-theme.js create mode 100644 frontend/src/services/travelGrants.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 93ea280a1..a127c13fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1019,6 +1019,11 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz", "integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==" }, + "@emotion/hash": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", + "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" + }, "@hackjunction/shared": { "version": "file:../shared" }, @@ -1378,6 +1383,114 @@ "@types/yargs": "^12.0.9" } }, + "@material-ui/core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.4.3.tgz", + "integrity": "sha512-Lz8sMFeCrtq5/pbhqClWFHpveL0huixjca0tw7uvh9xKKB7VyyYOyTu7RamSZLxb34UCSMPlobR+KK25Nqzkqw==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.4.3", + "@material-ui/system": "^4.4.3", + "@material-ui/types": "^4.1.1", + "@material-ui/utils": "^4.4.0", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.2", + "convert-css-length": "^2.0.1", + "deepmerge": "^4.0.0", + "hoist-non-react-statics": "^3.2.1", + "is-plain-object": "^3.0.0", + "normalize-scroll-left": "^0.2.0", + "popper.js": "^1.14.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.3.0" + }, + "dependencies": { + "deepmerge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz", + "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==" + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" + } + } + }, + "@material-ui/styles": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.4.3.tgz", + "integrity": "sha512-kNUdHFWsrvWKIEPx8Xy2/qayqsGMrYmCMq+FIiJiYczVZl5hiS8j5+KayonnpVta/O+Dktk+cxWkVcgwtxMrHg==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.7.1", + "@material-ui/types": "^4.1.1", + "@material-ui/utils": "^4.1.0", + "clsx": "^1.0.2", + "csstype": "^2.5.2", + "deepmerge": "^4.0.0", + "hoist-non-react-statics": "^3.2.1", + "jss": "10.0.0-alpha.25", + "jss-plugin-camel-case": "10.0.0-alpha.25", + "jss-plugin-default-unit": "10.0.0-alpha.25", + "jss-plugin-global": "10.0.0-alpha.25", + "jss-plugin-nested": "10.0.0-alpha.25", + "jss-plugin-props-sort": "10.0.0-alpha.25", + "jss-plugin-rule-value-function": "10.0.0-alpha.25", + "jss-plugin-vendor-prefixer": "10.0.0-alpha.25", + "prop-types": "^15.7.2" + }, + "dependencies": { + "deepmerge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz", + "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==" + } + } + }, + "@material-ui/system": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.4.3.tgz", + "integrity": "sha512-Cb05vLXsaCzssXD/iZKa0/qC6YOwbFWnYdnOEdkXZ3Fn2Ytz7rsnMgFejUSQV1luVhUBlEIm8DVz40N25WwW7w==", + "requires": { + "@babel/runtime": "^7.4.4", + "deepmerge": "^4.0.0", + "prop-types": "^15.7.2" + }, + "dependencies": { + "deepmerge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz", + "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==" + } + } + }, + "@material-ui/types": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz", + "integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==", + "requires": { + "@types/react": "*" + } + }, + "@material-ui/utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz", + "integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.6" + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1735,6 +1848,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.2.tgz", + "integrity": "sha512-YfoaTNqBwbIqpiJ5NNfxfgg5kyFP1Hqf/jqBtSWNv0E+EkkxmN+3VD6U2fu86tlQvdAc1o0SdWhnWFwcRMTn9A==", + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -4121,6 +4242,11 @@ "prop-types": "^15.6.2" } }, + "clsx": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", + "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4371,6 +4497,11 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "convert-css-length": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-2.0.1.tgz", + "integrity": "sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg==" + }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", @@ -4715,6 +4846,15 @@ "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=" }, + "css-vendor": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.6.tgz", + "integrity": "sha512-buv8FoZh84iMrtPHYGYll00/qSNV0gYO6E/GUCjUPTsSPj7uf/wot/QZwig+7qdFGxJ7HjOSJoclbhag09TVUQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "is-in-browser": "^1.0.2" + } + }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", @@ -5203,6 +5343,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.0.tgz", + "integrity": "sha512-zRRYDhpiKuAJHasOqCm7lBnsd22nrM4+OYI4ASWCxen+ocTMl7BIAKgGag97TlLiTl6rrau5aPe1VGUm9jQBng==", + "requires": { + "@babel/runtime": "^7.5.5", + "csstype": "^2.6.6" + } + }, "dom-matches": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz", @@ -7317,6 +7466,11 @@ "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.1.1.tgz", "integrity": "sha512-S6KSoGZWPutjTB7koZ9Ci9xzETBa7GVlNe42r0hF+rhoE/6lLHwEvi2FQSEhyWIjVo46tV94vn96hcsuum1THg==" }, + "hyphenate-style-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7759,6 +7913,11 @@ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==" }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -9904,6 +10063,83 @@ "verror": "1.10.0" } }, + "jss": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.25.tgz", + "integrity": "sha512-zqKnXv181B9vue2yYhmVhc+6ggbbxHF/33rjXfXEjaa22nOvknTI21QDfq3oZ8uCC50kcFp3Z8KU1ghUXdFvIA==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-camel-case": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.25.tgz", + "integrity": "sha512-J5ZEGDTy9ddqdTUPAF4SJQ25u5kiG1ORP8F+ZPEZAkkiMQJp+/Aol4I7xhTS2aW1Lhg8xNxdhdRfBi5yU7wOvg==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.0.0-alpha.25" + } + }, + "jss-plugin-default-unit": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0-alpha.25.tgz", + "integrity": "sha512-auOG459B+yEqkojgaXH02SYO9+xjmAxlmP+WbzhVpXqOFJ2CN/kaxd8P4NJZLdj3BQxHiM7WIyMVh786StE+EA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0-alpha.25" + } + }, + "jss-plugin-global": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0-alpha.25.tgz", + "integrity": "sha512-cS98Q8X8jwltuaBZd9eYuxMXxkUL+mJGl2Ok3/nmJzH9nLzj6i7kLxSoDtuJNqsRmbP7ogIXVozJUq9lUu2hlQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0-alpha.25" + } + }, + "jss-plugin-nested": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0-alpha.25.tgz", + "integrity": "sha512-7sk7/6mX1YTgXe+AyeD1zEyKTgIGbbhYtg+wWQcHJlE1flW2JHfcQ5mw84FgHcHQRQ8Dq3l9I3aEY51ev0J1Wg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0-alpha.25", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0-alpha.25.tgz", + "integrity": "sha512-8B/6QLQuUX8cIlZbXdjEm5l0jCX4EgacYMcFJhdKwDKEZYeAghpgQQrCKl0/CYHW7iFge5wim67P+uL6QxMzyw==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0-alpha.25" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0-alpha.25.tgz", + "integrity": "sha512-CQQtWO+/OZRGaFRBSGQUgAci9YlVtdoXcWQKBNo70tmpp+kaXKlFNCYaL3jmHbJHMiwKQYG2RYFQNIrwJ9SGmA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0-alpha.25" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.0.0-alpha.25", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0-alpha.25.tgz", + "integrity": "sha512-5FXpB/TiwckbrkoDCmd27YsWCESl1K4hAX/oro2/geEXgnVQvDgQOf2eWCsjYO2K1lYPPXtskMfws/Q3eKmbYg==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.6", + "jss": "10.0.0-alpha.25" + } + }, "jsx-ast-utils": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", @@ -10846,6 +11082,11 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" }, + "normalize-scroll-left": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz", + "integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA==" + }, "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", @@ -11498,6 +11739,11 @@ "tslib": "^1.10.0" } }, + "popper.js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", + "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" + }, "portfinder": { "version": "1.0.21", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", @@ -13756,6 +14002,17 @@ "prop-types": "^15.6.0" } }, + "react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 689cf0911..a061ac992 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@hackjunction/shared": "file:../shared", + "@material-ui/core": "^4.4.3", "@react-pdf/renderer": "^1.6.4", "antd": "^3.18.2", "auth0-js": "^9.10.0", diff --git a/frontend/src/components/PageWrapper/index.js b/frontend/src/components/PageWrapper/index.js index a478874e9..215b87d05 100644 --- a/frontend/src/components/PageWrapper/index.js +++ b/frontend/src/components/PageWrapper/index.js @@ -26,6 +26,24 @@ class PageWrapper extends PureComponent { wrapperProps: {} }; + constructor(props) { + super(props); + + this.state = { + error: false + }; + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { error: true }; + } + + componentDidCatch(error, errorInfo) { + console.error('PageWrapper error', error); + console.error(errorInfo); + } + renderContent() { if (this.props.loading) { return ( @@ -35,7 +53,7 @@ class PageWrapper extends PureComponent { ); } - if (this.props.error) { + if (this.props.error || this.state.error) { return (
diff --git a/frontend/src/index.js b/frontend/src/index.js index 8c673a36e..1421dcedb 100755 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'; import App from './App'; import * as serviceWorker from './serviceWorker'; +import { ThemeProvider } from '@material-ui/styles'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import { CloudinaryContext } from 'cloudinary-react'; @@ -25,7 +26,9 @@ ReactDOM.render( } persistor={persistor}> - + + + , diff --git a/frontend/src/material-ui-theme.js b/frontend/src/material-ui-theme.js new file mode 100644 index 000000000..82f177e2a --- /dev/null +++ b/frontend/src/material-ui-theme.js @@ -0,0 +1,10 @@ +import { createMuiTheme } from '@material-ui/core/styles'; + +const theme = createMuiTheme({ + palette: { + primary: 'blue', + secondary: 'orange' + } +}); + +export default theme; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js index aaf404641..20008e11b 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js @@ -1 +1,35 @@ -import React from 'react'; +import React, { useMemo } from 'react'; +import styles from './OrganiserEditEventGrants.module.scss'; + +import { connect } from 'react-redux'; +import { Table } from 'antd'; + +import * as OrganiserSelectors from 'redux/organiser/selectors'; +import PageWrapper from 'components/PageWrapper'; + +const OrganiserEditEventGrants = ({ registrations, travelGrantsByUser, loading, error }) => { + const data = useMemo(() => { + console.log('BY USER', travelGrantsByUser); + return registrations; + }, [registrations, travelGrantsByUser]); + + return ( + + + + + + +
+
+ ); +}; + +const mapState = state => ({ + registrations: OrganiserSelectors.registrations(state), + travelGrantsByUser: OrganiserSelectors.travelGrantsMap(state), + loading: OrganiserSelectors.registrationsLoading(state) || OrganiserSelectors.travelGrantsLoading(state), + error: OrganiserSelectors.registrationsError(state) || OrganiserSelectors.travelGrantsError(state) +}); + +export default connect(mapState)(OrganiserEditEventGrants); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js index 9fcf64bd3..034737b3a 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js @@ -13,6 +13,7 @@ import OrganiserEditEventDetails from './OrganiserEditEventDetails'; import OrganiserEditEventStats from './OrganiserEditEventStats'; import OrganiserEditEventReview from './OrganiserEditEventReview'; import OrganiserEditEventManage from './OrganiserEditEventManage'; +import OrganiserEditEventGrants from './OrganiserEditEventGrants'; import SidebarLayout from 'components/layouts/SidebarLayout'; const OrganiserEditEvent = ({ updateEvent, updateOrganiserProfiles, event, user, match, location }) => { @@ -76,6 +77,12 @@ const OrganiserEditEvent = ({ updateEvent, updateOrganiserProfiles, event, user, label: 'Review', render: routeProps => }, + { + path: '/grants', + icon: 'star', + label: 'Travel Grants', + render: routeProps => + }, { path: '/manage', icon: 'setting', diff --git a/frontend/src/redux/organiser/actionTypes.js b/frontend/src/redux/organiser/actionTypes.js index d5a6192ab..30a189665 100644 --- a/frontend/src/redux/organiser/actionTypes.js +++ b/frontend/src/redux/organiser/actionTypes.js @@ -12,3 +12,5 @@ export const EDIT_REGISTRATION = 'organiser/EDIT_REGISTRATION'; export const SET_REGISTRATIONS_FILTERS = 'organiser/SET_REGISTRATIONS_FILTERS'; export const UPDATE_TEAMS = 'organiser/UPDATE_TEAMS'; + +export const UPDATE_TRAVEL_GRANTS = 'organiser/UPDATE_TRAVEL_GRANTS'; diff --git a/frontend/src/redux/organiser/actions.js b/frontend/src/redux/organiser/actions.js index 23fdd7050..b2f914bb9 100644 --- a/frontend/src/redux/organiser/actions.js +++ b/frontend/src/redux/organiser/actions.js @@ -4,6 +4,7 @@ import UserProfilesService from 'services/userProfiles'; import EventsService from 'services/events'; import RegistrationsService from 'services/registrations'; import TeamsService from 'services/teams'; +import TravelGrantsService from 'services/travelGrants'; /** Update event with loading/error data */ export const updateEvent = slug => async (dispatch, getState) => { @@ -135,3 +136,17 @@ export const setRegistrationsFilters = filters => dispatch => { payload: filters }); }; + +/** Update travel grants with loading/error status */ +export const updateTravelGrantsForEvent = slug => async (dispatch, getState) => { + const idToken = AuthSelectors.getIdToken(getState()); + if (!slug) return; + + dispatch({ + type: ActionTypes.UPDATE_TRAVEL_GRANTS, + promise: TravelGrantsService.getTravelGrantsForEvent(idToken, slug), + meta: { + onFailure: e => console.log('Error updating travel grants', e) + } + }); +}; diff --git a/frontend/src/redux/organiser/reducer.js b/frontend/src/redux/organiser/reducer.js index e636ee673..811317973 100644 --- a/frontend/src/redux/organiser/reducer.js +++ b/frontend/src/redux/organiser/reducer.js @@ -36,6 +36,13 @@ const initialState = { error: false, updated: 0, data: [] + }, + travelGrants: { + loading: false, + error: false, + updated: 0, + data: [], + map: {} } }; @@ -44,6 +51,7 @@ const eventHandler = buildHandler('event'); const statsHandler = buildHandler('stats'); const organisersHandler = buildHandler('organisers', 'userId'); const registrationsHandler = buildHandler('registrations', 'user'); +const travelGrantsHandler = buildHandler('travelGrants', 'user'); const teamsHandler = buildHandler('teams'); const editEvent = buildUpdatePath('event.data'); const editEventOrganisers = buildUpdatePath('event.data.organisers'); @@ -68,6 +76,9 @@ export default function reducer(state = initialState, action) { case ActionTypes.UPDATE_TEAMS: { return teamsHandler(state, action); } + case ActionTypes.UPDATE_TRAVEL_GRANTS: { + return travelGrantsHandler(state, action); + } case ActionTypes.EDIT_REGISTRATION: { const registration = action.payload; return { diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 706f47683..46eb4104f 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; import { meanBy, countBy, groupBy, mapValues } from 'lodash-es'; +import { RegistrationStatuses } from '@hackjunction/shared'; import * as FilterUtils from 'utils/filters'; import * as AuthSelectors from 'redux/auth/selectors'; import moment from 'moment'; @@ -27,6 +28,17 @@ export const registrationsError = state => state.organiser.registrations.error; export const registrationsUpdated = state => state.organiser.registrations.updated; export const registrationsFilters = state => state.organiser.registrations.filters; +export const teams = state => state.organiser.teams.data; +export const teamsLoading = state => state.organiser.teams.loading; +export const teamsError = state => state.organiser.teams.error; +export const teamsUpdated = state => state.organiser.teams.updated; + +export const travelGrants = state => state.organiser.travelGrants.data; +export const travelGrantsMap = state => state.organiser.travelGrants.map; +export const travelGrantsLoading = state => state.organiser.travelGrants.loading; +export const travelGrantsError = state => state.organiser.travelGrants.error; +export const travelGrantsUpdated = state => state.organiser.travelGrants.updated; + export const registrationsFiltered = createSelector( registrations, registrationsFilters, @@ -54,10 +66,15 @@ export const registrationsReviewed = createSelector( } ); -export const teams = state => state.organiser.teams.data; -export const teamsLoading = state => state.organiser.teams.loading; -export const teamsError = state => state.organiser.teams.error; -export const teamsUpdated = state => state.organiser.teams.updated; +export const registrationsConfirmed = createSelector( + registrations, + registrations => { + const validStatuses = [RegistrationStatuses.asObject.confirmed.id, RegistrationStatuses.asObject.checkedIn.id]; + return registrations.filter(registration => { + return validStatuses.indexOf(registration.status) !== -1; + }); + } +); export const teamsPopulated = createSelector( registrationsMap, diff --git a/frontend/src/services/travelGrants.js b/frontend/src/services/travelGrants.js new file mode 100644 index 000000000..0a3b92fdc --- /dev/null +++ b/frontend/src/services/travelGrants.js @@ -0,0 +1,21 @@ +import _axios from 'services/axios'; + +const TravelGrantsService = {}; + +function config(idToken) { + return { + headers: { + Authorization: `Bearer ${idToken}` + } + }; +} + +TravelGrantsService.getTravelGrantsForEvent = (idToken, eventSlug) => { + return _axios.get(`/travel-grants/${eventSlug}/all`, config(idToken)); +}; + +TravelGrantsService.getTravelGrantForUser = (idToken, eventSlug) => { + return _axios.get(`/travel-grants/${eventSlug}`, config(idToken)); +}; + +export default TravelGrantsService; From 9e0567c27efe0c20b07cefe7f169b55b9a1d5dab Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Thu, 26 Sep 2019 13:51:57 +0300 Subject: [PATCH 03/27] Improved filters --- backend/modules/devtools/routes.js | 28 +- backend/modules/travel-grant/controller.js | 11 + backend/modules/travel-grant/model.js | 6 +- backend/modules/travel-grant/routes.js | 17 +- frontend/.babelrc.js | 24 + frontend/config-overrides.js | 16 +- frontend/package-lock.json | 199 ++- frontend/package.json | 8 +- frontend/public/index.html | 1 - frontend/src/components/filters/FilterForm.js | 93 ++ .../components/filters/FilterValueInput.js | 67 + .../components/generic/ActionMenu/index.js | 57 + .../src/components/generic/Modal/index.js | 78 ++ .../generic/Table/TablePaginationActions.js | 65 + .../components/generic/Table/TableToolbar.js | 57 + .../src/components/generic/Table/index.js | 208 +++ .../src/components/inputs/Select/index.js | 48 + .../src/components/inputs/TextInput/index.js | 16 + .../components/layouts/SidebarLayout/index.js | 6 +- .../VisaInvitationDrawer/VisaInvitationPDF.js | 7 +- .../components/tables/AttendeeTable/index.js | 215 +-- frontend/src/index.js | 3 +- frontend/src/material-ui-theme.js | 33 +- .../EditTravelGrantModal.js | 106 ++ .../OrganiserEditEventGrants/index.js | 106 +- .../OrganiserEditEventManage/index.js | 8 - .../AttendeeFilters.js | 2 +- .../SearchAttendeesPage.js | 15 +- .../OrganiserEditEventReview/index.js | 24 +- .../OrganiserEditEventStats/index.js | 17 +- .../OrganiserEditEvent/index.js | 52 +- frontend/src/redux/organiser/actionTypes.js | 2 +- frontend/src/redux/organiser/actions.js | 24 +- frontend/src/redux/organiser/reducer.js | 25 +- frontend/src/redux/organiser/selectors.js | 19 +- frontend/src/services/travelGrants.js | 9 + frontend/src/styles/main.scss | 1 + shared/constants/field-types.js | 115 ++ shared/constants/filter-types.js | 114 ++ shared/constants/filter-values.js | 12 + shared/constants/misc.js | 4 - shared/constants/registration-fields.js | 160 +-- shared/constants/select-options.js | 44 + shared/helpers/filterFunctions.js | 99 ++ shared/helpers/filterHelpers.js | 9 + shared/index.js | 4 + shared/package-lock.json | 1150 ++++++++++++++++- shared/package.json | 13 +- shared/test/filterFunctions.test.js | 392 ++++++ 49 files changed, 3376 insertions(+), 413 deletions(-) create mode 100644 frontend/.babelrc.js create mode 100644 frontend/src/components/filters/FilterForm.js create mode 100644 frontend/src/components/filters/FilterValueInput.js create mode 100644 frontend/src/components/generic/ActionMenu/index.js create mode 100644 frontend/src/components/generic/Modal/index.js create mode 100644 frontend/src/components/generic/Table/TablePaginationActions.js create mode 100644 frontend/src/components/generic/Table/TableToolbar.js create mode 100644 frontend/src/components/generic/Table/index.js create mode 100644 frontend/src/components/inputs/Select/index.js create mode 100644 frontend/src/components/inputs/TextInput/index.js create mode 100644 frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/EditTravelGrantModal.js create mode 100644 shared/constants/field-types.js create mode 100644 shared/constants/filter-types.js create mode 100644 shared/constants/filter-values.js create mode 100644 shared/constants/select-options.js create mode 100644 shared/helpers/filterFunctions.js create mode 100644 shared/helpers/filterHelpers.js create mode 100644 shared/test/filterFunctions.test.js diff --git a/backend/modules/devtools/routes.js b/backend/modules/devtools/routes.js index 8b1dff554..a7c328c0e 100644 --- a/backend/modules/devtools/routes.js +++ b/backend/modules/devtools/routes.js @@ -1,12 +1,13 @@ const express = require('express'); const router = express.Router(); const Registration = require('../registration/model'); +const { UserProfile } = require('../user-profile/model'); router.route('/').get((req, res) => { return res.status(200).send('DEVTOOLS HERE'); }); -router.route('/anonymize-registrations').get(async (req, res) => { +router.route('/anonymize-db').get(async (req, res) => { const registrations = await Registration.find({}); const updates = registrations.map(registration => { @@ -17,8 +18,6 @@ router.route('/anonymize-registrations').get(async (req, res) => { }, update: { $set: { - 'answers.firstName': 'Anonymous', - 'answers.lastName': 'Owl', 'answers.email': 'juuso.lappalainen+' + Math.floor(Math.random() * 1000000) + '@hackjunction.com' } @@ -27,9 +26,28 @@ router.route('/anonymize-registrations').get(async (req, res) => { }; }); - const result = await Registration.bulkWrite(updates); + await Registration.bulkWrite(updates); - return res.status(200).json(result); + const userProfiles = await UserProfile.find({}); + + const userUpdates = userProfiles.map(userProfile => { + return { + updateOne: { + filter: { + _id: userProfile._id + }, + update: { + $set: { + email: 'juuso.lappalainen+' + Math.floor(Math.random() * 1000000) + '@hackjunction.com' + } + } + } + }; + }); + + await UserProfile.bulkWrite(userUpdates); + + return res.status(200).send('OK'); }); module.exports = router; diff --git a/backend/modules/travel-grant/controller.js b/backend/modules/travel-grant/controller.js index cb642c46a..16adc287e 100644 --- a/backend/modules/travel-grant/controller.js +++ b/backend/modules/travel-grant/controller.js @@ -7,6 +7,17 @@ controller.getTravelGrantsForEvent = eventId => { return TravelGrant.find({ event: eventId }); }; +controller.createTravelGrantForEvent = (eventId, userId, sum, travelsFrom) => { + const travelGrant = new TravelGrant({ + sum, + travelsFrom, + user: userId, + event: eventId + }); + + return travelGrant.save(); +}; + controller.getTravelGrantForUser = (userId, eventId) => { return TravelGrant.findOne({ event: eventId, user: userId }); }; diff --git a/backend/modules/travel-grant/model.js b/backend/modules/travel-grant/model.js index 86a833fa6..be83f3d1c 100644 --- a/backend/modules/travel-grant/model.js +++ b/backend/modules/travel-grant/model.js @@ -18,7 +18,11 @@ const TravelGrantSchema = new mongoose.Schema({ }, status: { type: String, - enum: Misc.travelGrantStatuses.ids + enum: Misc.travelGrantStatuses.ids, + default: Misc.travelGrantStatuses.items.accepted.id + }, + sum: { + type: Number }, receipt: CloudinaryImageSchema }); diff --git a/backend/modules/travel-grant/routes.js b/backend/modules/travel-grant/routes.js index 12a2ad270..f7905a7a6 100644 --- a/backend/modules/travel-grant/routes.js +++ b/backend/modules/travel-grant/routes.js @@ -15,12 +15,27 @@ const getTravelGrantsForEvent = asyncHandler(async (req, res) => { return res.status(200).json(travelGrants); }); +const createTravelGrantForEvent = asyncHandler(async (req, res) => { + const travelGrant = await TravelGrantController.createTravelGrantForEvent( + req.event._id.toString(), + req.body.userId, + req.body.sum, + req.body.travelsFrom + ).catch(err => { + console.log('ERR', err); + }); + return res.status(200).json(travelGrant); +}); + const getTravelGrantForUser = asyncHandler(async (req, res) => { const travelGrant = await TravelGrantController.getTravelGrantForUser(req.user.sub, req.event._id.toString()); return res.status(200).json(travelGrant); }); -router.route('/:slug').get(hasToken, hasRegisteredToEvent, getTravelGrantForUser); +router + .route('/:slug') + .get(hasToken, hasRegisteredToEvent, getTravelGrantForUser) + .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, createTravelGrantForEvent); router .route('/:slug/all') diff --git a/frontend/.babelrc.js b/frontend/.babelrc.js new file mode 100644 index 000000000..18fd5ac5f --- /dev/null +++ b/frontend/.babelrc.js @@ -0,0 +1,24 @@ +const plugins = [ + [ + 'babel-plugin-import', + { + libraryName: '@material-ui/core', + // Use "'libraryDirectory': ''," if your bundler does not support ES modules + libraryDirectory: 'esm', + camel2DashComponentName: false + }, + 'core' + ], + [ + 'babel-plugin-import', + { + libraryName: '@material-ui/icons', + // Use "'libraryDirectory': ''," if your bundler does not support ES modules + libraryDirectory: 'esm', + camel2DashComponentName: false + }, + 'icons' + ] +]; + +module.exports = { plugins }; diff --git a/frontend/config-overrides.js b/frontend/config-overrides.js index 0a17d06fe..398fb7789 100644 --- a/frontend/config-overrides.js +++ b/frontend/config-overrides.js @@ -1,10 +1,10 @@ -const { override, fixBabelImports } = require('customize-cra'); - +const { override, fixBabelImports, useBabelRc } = require('customize-cra'); module.exports = override( - fixBabelImports('import', { - libraryName: 'antd', - libraryDirectory: 'es', - style: 'css', - }), -); \ No newline at end of file + fixBabelImports('import', { + libraryName: 'antd', + libraryDirectory: 'es', + style: 'css' + }), + useBabelRc() +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a127c13fe..d509e3edb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1025,7 +1025,21 @@ "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" }, "@hackjunction/shared": { - "version": "file:../shared" + "version": "file:../shared", + "requires": { + "lodash": "^4.17.15", + "object-path": "^0.11.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "bundled": true + }, + "object-path": { + "version": "0.11.4", + "bundled": true + } + } }, "@hapi/address": { "version": "2.0.0", @@ -1425,6 +1439,14 @@ } } }, + "@material-ui/icons": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.4.3.tgz", + "integrity": "sha512-HVVvUyc/78kmaBd93LkfWyGkXMM+zOMKzUfulWXxaV/fFAZ3N0pD0oHjWUd94zrOoF3tZP9JC7EPlIpIcZSNow==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, "@material-ui/styles": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.4.3.tgz", @@ -2722,9 +2744,10 @@ } }, "babel-plugin-import": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.12.0.tgz", - "integrity": "sha512-3Fo7sJ2Hm71y1VJS7eMA/E7J5+roKJmzwia5BxzUQREBs6CRylwtvQq8m39W8nplG4Y7rZwOCndh5MzRTSmHpA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.12.2.tgz", + "integrity": "sha512-Vz9s+I6vAnsY8sYczU/cdtkKAHSorapa/2St6K+OzowplKizpWxul4HLi3kj1eRmHMFjhbROSMGXP+mFna2nUw==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/runtime": "^7.0.0" @@ -3585,8 +3608,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -3607,14 +3629,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3629,20 +3649,17 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -3759,8 +3776,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -3772,7 +3788,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3787,7 +3802,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3795,14 +3809,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3821,7 +3833,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3902,8 +3913,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -3915,7 +3925,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -4001,8 +4010,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -4038,7 +4046,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4058,7 +4065,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4102,14 +4108,12 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, @@ -7057,9 +7061,9 @@ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==" }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.1.tgz", + "integrity": "sha512-c0HoNHzDiHpBt4Kqe99N8tdLPKAnGCQ73gYMPWtAYM4PwGnf7xl8PBUHJqh9ijlzt2uQKaSRxbXRt+rZ7M2/kA==", "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", @@ -7192,16 +7196,16 @@ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { @@ -8633,8 +8637,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -8655,14 +8658,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8677,20 +8678,17 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -8807,8 +8805,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -8820,7 +8817,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8835,7 +8831,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8843,14 +8838,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8869,7 +8862,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8950,8 +8942,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -8963,7 +8954,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -9049,8 +9039,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -9086,7 +9075,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9106,7 +9094,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9150,14 +9137,12 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } } @@ -10688,6 +10673,16 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, "mini-css-extract-plugin": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", @@ -13738,6 +13733,11 @@ "prop-types": "^15.7.2" } }, + "react-hyper-modal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/react-hyper-modal/-/react-hyper-modal-1.1.1.tgz", + "integrity": "sha512-mN8Os2U4QU2w9r9czWZkcyyIMWTfI5jdJnxJsojgkQQT0wI9/93B3gg5MbCotif5LGPv6oFOktjlMbzDMOdGlQ==" + }, "react-inlinesvg": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/react-inlinesvg/-/react-inlinesvg-0.8.4.tgz", @@ -13850,24 +13850,20 @@ } }, "react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.0.tgz", + "integrity": "sha512-n9HXxaL/6yRlig9XPfGyagI8+bUNdqcu7FUAx0/Z+Us22Z8iHsbkyJ21Inebn9HOxI5Nxlfc8GNabkNSeXfhqw==", "requires": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" } }, "react-router-breadcrumbs-hoc": { @@ -13876,16 +13872,17 @@ "integrity": "sha512-cAjPb3agGf8OcGCHKfQQ4ori+TRf9ithB688SUdB5bRMFyYvkhcIuwd2qNpfwxI0bWbQviojibGXrpP3SD6NjA==" }, "react-router-dom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", - "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.0.tgz", + "integrity": "sha512-OkxKbMKjO7IkYqnoaZNX19MnwgjhxwZE871cPUTq0YU2wpIw7QwGxSnSoNRMOa7wO1TwvJJMFpgiEB4C/gVhTw==", "requires": { - "history": "^4.7.2", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" + "prop-types": "^15.6.2", + "react-router": "5.1.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" } }, "react-scripts": { @@ -14408,9 +14405,9 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", @@ -16415,9 +16412,9 @@ } }, "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vary": { "version": "1.1.2", diff --git a/frontend/package.json b/frontend/package.json index a061ac992..131f412a3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,11 +5,11 @@ "dependencies": { "@hackjunction/shared": "file:../shared", "@material-ui/core": "^4.4.3", + "@material-ui/icons": "^4.4.3", "@react-pdf/renderer": "^1.6.4", "antd": "^3.18.2", "auth0-js": "^9.10.0", "axios": "^0.18.0", - "babel-plugin-import": "^1.11.2", "better-npm-run": "^0.1.1", "classnames": "^2.2.6", "cloudinary-react": "^1.1.1", @@ -33,6 +33,7 @@ "react-facebook-pixel": "^0.1.3", "react-focus-within": "^2.0.1", "react-grid-system": "^4.4.6", + "react-hyper-modal": "^1.1.1", "react-inlinesvg": "^0.8.4", "react-json-view": "^1.19.1", "react-lazyload": "^2.6.2", @@ -40,7 +41,7 @@ "react-redux": "^6.0.0", "react-remove-scroll": "^2.0.4", "react-router-breadcrumbs-hoc": "^3.2.0", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.1.0", "react-scripts": "3.0.1", "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", @@ -69,5 +70,8 @@ "engines": { "node": "^12.3.1", "npm": "^6.9.0" + }, + "devDependencies": { + "babel-plugin-import": "^1.12.2" } } diff --git a/frontend/public/index.html b/frontend/public/index.html index 9e05a5011..4b567647c 100755 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -49,7 +49,6 @@ - diff --git a/frontend/src/components/filters/FilterForm.js b/frontend/src/components/filters/FilterForm.js new file mode 100644 index 000000000..5307035fa --- /dev/null +++ b/frontend/src/components/filters/FilterForm.js @@ -0,0 +1,93 @@ +import React, { useState, useMemo, useCallback } from 'react'; + +import { RegistrationFields, FilterTypes } from '@hackjunction/shared'; +import { makeStyles } from '@material-ui/core/styles'; +import { Grid, Paper, Button } from '@material-ui/core'; + +import Select from 'components/inputs/Select'; +import FilterValueInput from './FilterValueInput'; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(2) + } +})); + +const FilterForm = ({ onSubmit }) => { + const classes = useStyles(); + const [filter, setFilter] = useState(); + const [filterType, setFilterType] = useState(); + const [filterValue, setFilterValue] = useState(); + + const filterParams = useMemo(() => { + return filter ? JSON.parse(filter) : null; + }, [filter]); + + const handleSubmit = useCallback(() => { + onSubmit({ + path: filterParams ? filterParams.path : '', + type: filterType, + value: filterValue + }); + }, [filterParams, filterType, filterValue, onSubmit]); + + const filterOptions = useMemo(() => { + return RegistrationFields.filters.map(filter => ({ + value: JSON.stringify(filter), + label: filter.label + })); + }, []); + + const filterTypeOptions = useMemo(() => { + if (!filterParams) return []; + const options = FilterTypes.filterTypesForType[filterParams.type]; + if (!options) return []; + + return options.map(option => ({ + value: option, + label: FilterTypes.filterTypes[option].label + })); + }, [filterParams]); + + return ( + + + + + + + + + + + + + + ); +}; + +export default FilterForm; diff --git a/frontend/src/components/filters/FilterValueInput.js b/frontend/src/components/filters/FilterValueInput.js new file mode 100644 index 000000000..9549e262e --- /dev/null +++ b/frontend/src/components/filters/FilterValueInput.js @@ -0,0 +1,67 @@ +import React, { useMemo } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { Paper } from '@material-ui/core'; +import { FilterTypes, FilterValues } from '@hackjunction/shared'; + +import TextInput from 'components/inputs/TextInput'; +import Select from 'components/inputs/Select'; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(2) + } +})); + +const FilterValueInput = ({ filterType, valueType, value, onChange }) => { + const classes = useStyles(); + + const inputParams = { value, onChange }; + + const inner = useMemo(() => { + switch (filterType) { + case FilterTypes.filterTypes.LESS_THAN.id: + case FilterTypes.filterTypes.NOT_LESS_THAN.id: + case FilterTypes.filterTypes.MORE_THAN.id: + case FilterTypes.filterTypes.NOT_MORE_THAN.id: + return ( + + ); + case FilterTypes.filterTypes.CONTAINS.id: + case FilterTypes.filterTypes.NOT_CONTAINS.id: + case FilterTypes.filterTypes.EQUALS.id: + case FilterTypes.filterTypes.NOT_EQUALS.id: + switch (valueType) { + case FilterValues.STRING: + return ; + case FilterValues.BOOLEAN: + return ; + case FilterValues.DATE: + return ; + case FilterValues.GENDER: + return ; + case FilterValues.LANGUAGE: + return ; + return ; + return ; case FilterValues.LANGUAGE: - return ; case FilterValues.TAG: const options = event.tags.map(tag => ({ value: tag.label, label: tag.label })); - return ; case FilterValues.STATUS: - return ; default: return null; } diff --git a/frontend/src/components/generic/Empty/index.js b/frontend/src/components/generic/Empty/index.js new file mode 100644 index 000000000..28152df5c --- /dev/null +++ b/frontend/src/components/generic/Empty/index.js @@ -0,0 +1,55 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { Box, Button } from '@material-ui/core'; +import InfoTwoToneIcon from '@material-ui/icons/InfoTwoTone'; +import Typography from 'antd/lib/typography/Typography'; + +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: '#efefef' + } +})); + +const Empty = ({ isEmpty, emptyText = 'No data', button, hideIfNotEmpty = false }) => { + const classes = useStyles(); + + const renderButton = () => { + if (!button) return null; + return ( + + ); + }; + + if (!isEmpty && !hideIfNotEmpty) { + return ( + + {renderButton()} + + ); + } + + return ( + + + + {emptyText} + + {button && {renderButton()}} + + ); +}; + +export default Empty; diff --git a/frontend/src/components/generic/List/index.js b/frontend/src/components/generic/List/index.js new file mode 100644 index 000000000..04104971b --- /dev/null +++ b/frontend/src/components/generic/List/index.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import objectPath from 'object-path'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { makeStyles } from '@material-ui/core/styles'; + +import { List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Divider } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(2) + } +})); + +const _List = ({ + data = [], + rowKey, + renderPrimary = () => {}, + renderSecondary = () => {}, + emptyView = null, + onDelete, + hasDividers = true +}) => { + const classes = useStyles(); + if (data.length === 0) { + return emptyView; + } + + return ( + + {data.map((item, index) => ( + + {index !== 0 && } + + + {onDelete && ( + + onDelete(item, index)}> + + + + )} + + + ))} + + ); +}; + +export default _List; diff --git a/frontend/src/components/generic/Modal/index.js b/frontend/src/components/generic/Modal/index.js index b668f9eec..db59febad 100644 --- a/frontend/src/components/generic/Modal/index.js +++ b/frontend/src/components/generic/Modal/index.js @@ -1,16 +1,14 @@ -import React, { useState, useCallback } from 'react'; +import React from 'react'; import HyperModal from 'react-hyper-modal'; import classNames from 'classnames'; -import { Modal, Backdrop, Paper, Box, DialogTitle, Typography } from '@material-ui/core'; +import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import CloseIcon from '@material-ui/icons/Close'; -import theme from 'material-ui-theme'; const useStyles = makeStyles(theme => ({ wrapper: { display: 'flex', - zIndex: 1000000 + zIndex: 100 }, wrapperPadded: { padding: theme.spacing(2) @@ -19,7 +17,7 @@ const useStyles = makeStyles(theme => ({ background: '#ffffff', width: '100% !important', maxWidth: '600px', - zIndex: 100000, + zIndex: 1, display: 'flex', flexDirection: 'column', alignItems: 'stretch' @@ -34,15 +32,10 @@ const useStyles = makeStyles(theme => ({ }, header: { padding: theme.spacing(3), - position: 'absolute', - top: 0, - left: 0, - right: 0 + textAlign: 'left' }, inner: { - paddingTop: '100px', - paddingLeft: '1rem', - paddingRight: '1rem', + padding: '1rem', flex: 1, overflow: 'auto' } @@ -54,7 +47,6 @@ const GenericModal = ({ title, isOpen, handleClose, size, children }) => { ({ + value: { + textAlign: 'left' + }, + suffix: { + marginLeft: theme.spacing(1), + display: 'inline-block' + } +})); + +const Statistic = ({ label, value, suffix }) => { + const classes = useStyles(); + return ( + + + + {label} + + + {value} + {suffix && ( + + {suffix} + + )} + + + + ); +}; + +export default Statistic; diff --git a/frontend/src/components/generic/Stepper/index.js b/frontend/src/components/generic/Stepper/index.js new file mode 100644 index 000000000..7e068632d --- /dev/null +++ b/frontend/src/components/generic/Stepper/index.js @@ -0,0 +1,47 @@ +import React, { useCallback } from 'react'; + +import { Stepper, Step, StepLabel, StepContent } from '@material-ui/core'; + +import StepButtons from 'components/buttons/StepButtons'; + +const _Stepper = ({ + steps = [], + activeStep = 0, + onStepChange, + onFinish, + stepperProps = { orientation: 'vertical' } +}) => { + const handleNext = useCallback(() => { + onStepChange(activeStep + 1); + }, [onStepChange, activeStep]); + + const handleBack = useCallback(() => { + onStepChange(activeStep - 1); + }, [onStepChange, activeStep]); + + const handleDone = useCallback(() => { + onFinish(); + }, [onFinish]); + + return ( + + {steps.map((step, index) => ( + + {step.label} + + {step.render()} + + + + ))} + + ); +}; + +export default _Stepper; diff --git a/frontend/src/components/generic/Table/index.js b/frontend/src/components/generic/Table/index.js index 893829ec1..07b2dd8fa 100644 --- a/frontend/src/components/generic/Table/index.js +++ b/frontend/src/components/generic/Table/index.js @@ -120,7 +120,7 @@ export default ({ )} {rowNumber && #} - {rowActions && ( + {rowActions.length > 0 && ( Actions @@ -161,7 +161,7 @@ export default ({ {rowNumber && ( {1 + index + page * rowsPerPage} )} - {rowActions && ( + {rowActions.length > 0 && ( @@ -179,30 +179,36 @@ export default ({ {footer && ( - {footer} + + + {footer} + + )} - - - - - + {pagination && ( + + + + + + )} ); }; diff --git a/frontend/src/components/inputs/Select/index.js b/frontend/src/components/inputs/Select/index.js index 71e0a73ef..8231f2d6e 100644 --- a/frontend/src/components/inputs/Select/index.js +++ b/frontend/src/components/inputs/Select/index.js @@ -1,9 +1,9 @@ -import React, { useCallback, useMemo, useEffect } from 'react'; +import React, { useCallback, useMemo } from 'react'; -import { TextField, MenuItem } from '@material-ui/core'; +import { Select, MenuItem } from '@material-ui/core'; import { SelectOptions } from '@hackjunction/shared'; -const Select = ({ label, helperText, value = '', onChange = () => {}, options = [], type }) => { +const _Select = ({ label, helperText, value, onChange = () => {}, options = [], type, multiple = false }) => { const handleChange = useCallback( e => { onChange(e.target.value); @@ -36,15 +36,28 @@ const Select = ({ label, helperText, value = '', onChange = () => {}, options = } }, [options, type]); + const valueOrDefault = value || (multiple ? [] : ''); + return ( - + ); }; -export default Select; +export default _Select; diff --git a/frontend/src/components/modals/EditRegistrationModal/index.js b/frontend/src/components/modals/EditRegistrationModal/index.js new file mode 100644 index 000000000..6373820d7 --- /dev/null +++ b/frontend/src/components/modals/EditRegistrationModal/index.js @@ -0,0 +1,261 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { connect } from 'react-redux'; +import Modal from 'components/generic/Modal'; +import { Typography } from '@material-ui/core'; +import { withSnackbar } from 'notistack'; +import { RegistrationFields } from '@hackjunction/shared'; +import { Rate, notification, Divider as AntDivider, Tag, Drawer, List, Select, Button as AntButton } from 'antd'; +import { isEqual, groupBy, find } from 'lodash-es'; + +import PageWrapper from 'components/PageWrapper'; +import Button from 'components/generic/Button'; +import Divider from 'components/generic/Divider'; +import DescriptionItem from 'components/generic/DescriptionItem'; + +import UserSelectModal from 'components/modals/UserSelectModal'; +import RegistrationStatusSelect from 'components/FormComponents/RegistrationStatusSelect'; + +import * as AuthSelectors from 'redux/auth/selectors'; +import * as OrganiserSelectors from 'redux/organiser/selectors'; +import * as OrganiserActions from 'redux/organiser/actions'; + +import RegistrationsService from 'services/registrations'; +import MiscUtils from 'utils/misc'; + +const EditRegistrationModalInner = ({ idToken, event, registration, organisers, organisersMap, onEdit }) => { + const initialValues = { + rating: registration.rating, + assignedTo: registration.assignedTo, + tags: registration.tags, + status: registration.status + }; + const [formValues, setFormValues] = useState(initialValues); + const dirty = !isEqual(formValues, initialValues); + + const handleEdit = (field, value) => { + setFormValues({ + ...formValues, + [field]: value + }); + }; + + const renderAssignedTo = () => { + if (formValues.assignedTo) { + const user = organisersMap[formValues.assignedTo]; + return user ? `${user.firstName} ${user.lastName}` : '???'; + } + return 'No one'; + }; + + const renderActions = () => { + return ( + + + handleEdit('rating', value)} />} + > + + ( + - - {loading && } - - - - ); - }; - - return ( - - {renderContent()} - - ); -}; - -const mapState = state => ({}); - -const mapDispatch = dispatch => ({ - createTravelGrant: (slug, sum, travelsFrom, userId) => - dispatch(OrganiserActions.createTravelGrant(slug, sum, travelsFrom, userId)) -}); - -export default connect( - mapState, - mapDispatch -)(EditTravelGrantModal); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js new file mode 100644 index 000000000..cada9000c --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js @@ -0,0 +1,117 @@ +import React, { useState, useCallback } from 'react'; + +import { connect } from 'react-redux'; +import { Typography, Box, Grid } from '@material-ui/core'; +import { sumBy, filter, sortBy } from 'lodash-es'; +import Stepper from 'components/generic/Stepper'; +import Empty from 'components/generic/Empty'; +import List from 'components/generic/List'; +import Statistic from 'components/generic/Statistic'; +import Table from 'components/generic/Table'; + +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +import AddGroupModal from './AddGroupModal'; +import TravelGrantStepperPreview from './TravelGrantStepperPreview'; + +const TravelGrantStepper = ({ registrations }) => { + const [activeStep, setActiveStep] = useState(0); + const [modalOpen, setModalOpen] = useState(false); + const [groups, setGroups] = useState([]); + + const handleAddGroup = useCallback( + (group, amount) => { + const value = sortBy(groups.concat({ group, amount }), amount); + setGroups(value); + }, + [groups] + ); + + const handleRemoveGroup = useCallback( + (item, index) => { + setGroups(groups.filter((group, idx) => idx !== index)); + }, + [groups] + ); + + const toggleModal = useCallback(() => { + setModalOpen(!modalOpen); + }, [modalOpen]); + + return ( + + + + + + + + !!r.travelGrant).length} + suffix={`/${registrations.length}`} + /> + + + + + + + + + This is a tool for automatically assigning travel grants to participants. Choose which participants + should be eligible for how much, and the tool will automatically assign travel grants to + participants in the order they registered. + + + window.alert('HELLO')} + steps={[ + { + key: 'set-groups', + label: 'Configure travel grant groups and amounts', + render: () => ( + + + group.label} + renderSecondary={({ group, amount }) => amount} + onDelete={handleRemoveGroup} + /> + + + ) + }, + { + key: 'preview-spend', + label: 'Preview spend', + render: () => + }, + { + key: 'assign-grants', + label: 'Assign grants', + render: () => Submit changes + } + ]} + /> + + ); +}; + +const mapState = state => ({ + registrations: OrganiserSelectors.registrationsConfirmed(state) +}); + +export default connect(mapState)(TravelGrantStepper); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js new file mode 100644 index 000000000..c676e2f9a --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js @@ -0,0 +1,87 @@ +import React, { useMemo } from 'react'; + +import { sortBy, find } from 'lodash-es'; +import { FilterHelpers } from '@hackjunction/shared'; + +import Table from 'components/generic/Table'; + +const TravelGrantStepperPreview = ({ registrations, groupConfig }) => { + const sorted = sortBy(registrations, 'createdAt'); + + const registrationsByGroup = useMemo(() => { + return groupConfig.map(({ group, amount }) => { + const matching = FilterHelpers.applyFilters(registrations, group.filters).map(r => r._id); + + return { + group, + amount, + matching + }; + }); + }, [registrations, groupConfig]); + + const mapped = useMemo(() => { + return sorted.map(reg => { + const group = find(registrationsByGroup, ({ matching }) => { + return matching.indexOf(reg._id); + }); + + if (group) { + return { + ...reg, + amount: group.amount, + group: group.group + }; + } else { + return { + ...reg, + amount: 0, + group: 'No match' + }; + } + }); + }, [sorted, registrationsByGroup]); + + return ( + `${answers.firstName} ${answers.lastName}` + }, + { + key: 'travelsFrom', + label: 'Travels from', + path: 'answers.countryOfTravel' + }, + { + key: 'createdAt', + label: 'Registered', + path: 'createdAt' + }, + { + key: 'group', + label: 'Group', + path: 'group', + render: group => group.label + }, + { + key: 'amount', + label: 'Amount', + path: 'amount' + } + ]} + rowKey="_id" + loading={false} + pagination={true} + rowNumber={false} + rowSelection={false} + title={'Preview'} + /> + ); +}; + +export default TravelGrantStepperPreview; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js index cb1cebd3a..9f7acdfe3 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js @@ -1,92 +1,24 @@ import React, { useMemo, useState, useCallback, useEffect } from 'react'; import styles from './OrganiserEditEventGrants.module.scss'; -import EmailIcon from '@material-ui/icons/Email'; -import EditIcon from '@material-ui/icons/Edit'; +import { PageHeader } from 'antd'; import { connect } from 'react-redux'; import * as OrganiserSelectors from 'redux/organiser/selectors'; import * as OrganiserActions from 'redux/organiser/actions'; -import Table from 'components/generic/Table'; -import EditTravelGrantModal from './EditTravelGrantModal'; - -import FilterForm from 'components/filters/FilterForm'; - -const OrganiserEditEventGrants = ({ - event, - registrations, - travelGrantsByUser, - travelGrantsTotal, - updateTravelGrants, - loading, - error -}) => { - const [activeItem, setActiveItem] = useState(); - - const handleClose = useCallback(() => { - setActiveItem(undefined); - }, []); - - useEffect(() => { - updateTravelGrants(event.slug); - }, [updateTravelGrants, event.slug]); - - const data = useMemo(() => { - return registrations.map(reg => { - reg.travelGrant = travelGrantsByUser[reg.user]; - return reg; - }); - }, [registrations, travelGrantsByUser]); +import PageWrapper from 'components/PageWrapper'; +import BulkAssignGrantsPage from './BulkAssignGrantsPage'; +const OrganiserEditEventGrants = ({}) => { return ( -
- -

Total spend: {travelGrantsTotal}

-
setActiveItem(item) - } - ]} - columns={[ - { - key: 'firstName', - path: 'answers.firstName', - label: 'First name' - }, - { - key: 'lastName', - path: 'answers.lastName', - label: 'Last name' - }, - { - key: 'email', - path: 'answers.email', - label: 'Email' - }, - { - key: 'amount', - path: 'travelGrant.sum', - label: 'Amount', - render: sum => sum || 'N/A' - }, - { - key: 'status', - path: 'travelGrant.status', - label: 'Status', - render: status => status || 'Pending' - } - ]} - /> - + + Configure travel grants for your participants

} + footer={} + >
+
); }; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js index ead79445a..dec5a08ec 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js @@ -21,6 +21,7 @@ const OrganiserEditEvent = ({ updateOrganiserProfiles, updateRegistrations, updateTeams, + updateFilterGroups, loading, error, event, @@ -39,8 +40,17 @@ const OrganiserEditEvent = ({ updateOrganiserProfiles(event.owner, event.organisers); updateRegistrations(slug); updateTeams(slug); + updateFilterGroups(slug); } - }, [slug, event.owner, event.organisers, updateTeams, updateRegistrations, updateOrganiserProfiles]); + }, [ + slug, + event.owner, + event.organisers, + updateTeams, + updateRegistrations, + updateOrganiserProfiles, + updateFilterGroups + ]); useEffect(() => { updateData(); @@ -120,7 +130,8 @@ const mapDispatchToProps = dispatch => ({ updateOrganiserProfiles: (owner, organisers) => dispatch(OrganiserActions.updateOrganisersForEvent(owner, organisers)), updateRegistrations: slug => dispatch(OrganiserActions.updateRegistrationsForEvent(slug)), - updateTeams: slug => dispatch(OrganiserActions.updateTeamsForEvent(slug)) + updateTeams: slug => dispatch(OrganiserActions.updateTeamsForEvent(slug)), + updateFilterGroups: slug => dispatch(OrganiserActions.updateFilterGroups(slug)) }); export default connect( diff --git a/frontend/src/redux/organiser/actionTypes.js b/frontend/src/redux/organiser/actionTypes.js index 2a663c063..16c6980d8 100644 --- a/frontend/src/redux/organiser/actionTypes.js +++ b/frontend/src/redux/organiser/actionTypes.js @@ -14,3 +14,8 @@ export const UPDATE_TEAMS = 'organiser/UPDATE_TEAMS'; export const UPDATE_TRAVEL_GRANTS = 'organiser/UPDATE_TRAVEL_GRANTS'; export const CREATE_TRAVEL_GRANT = 'organiser/CREATE_TRAVEL_GRANT'; + +export const UPDATE_FILTER_GROUPS = 'organiser/UPDATE_FILTER_GROUPS'; +export const CREATE_FILTER_GROUP = 'organiser/CREATE_FILTER_GROUP'; +export const EDIT_FILTER_GROUP = 'organiser/EDIT_FILTER_GROUP'; +export const DELETE_FILTER_GROUP = 'organiser/DELETE_FILTER_GROUP'; diff --git a/frontend/src/redux/organiser/actions.js b/frontend/src/redux/organiser/actions.js index 195f1e6c7..d05523cb8 100644 --- a/frontend/src/redux/organiser/actions.js +++ b/frontend/src/redux/organiser/actions.js @@ -5,6 +5,7 @@ import EventsService from 'services/events'; import RegistrationsService from 'services/registrations'; import TeamsService from 'services/teams'; import TravelGrantsService from 'services/travelGrants'; +import FilterGroupsService from 'services/filterGroups'; /** Update event with loading/error data */ export const updateEvent = slug => async (dispatch, getState) => { @@ -156,3 +157,57 @@ export const createTravelGrant = (slug, sum, travelsFrom, userId) => async (disp return; }; + +/** Update filter groups with loading/error status */ +export const updateFilterGroups = slug => async (dispatch, getState) => { + const idToken = AuthSelectors.getIdToken(getState()); + + dispatch({ + type: ActionTypes.UPDATE_FILTER_GROUPS, + promise: FilterGroupsService.getFilterGroupsForEvent(idToken, slug), + meta: { + onFailure: e => console.log('Error updating filter groups', e) + } + }); + + return; +}; + +export const createFilterGroup = (slug, label, description, filters) => async (dispatch, getState) => { + const idToken = AuthSelectors.getIdToken(getState()); + + const filterGroup = await FilterGroupsService.createFilterGroup(idToken, label, description, filters, slug); + + dispatch({ + type: ActionTypes.CREATE_FILTER_GROUP, + payload: filterGroup + }); + + return filterGroup; +}; + +export const editFilterGroup = (slug, label, description, filters) => async (dispatch, getState) => { + const idToken = AuthSelectors.getIdToken(getState()); + + const filterGroup = await FilterGroupsService.editFilterGroup(idToken, label, description, filters, slug); + + dispatch({ + type: ActionTypes.EDIT_FILTER_GROUP, + payload: filterGroup + }); + + return filterGroup; +}; + +export const deleteFilterGroup = (slug, label) => async (dispatch, getState) => { + const idToken = AuthSelectors.getIdToken(getState()); + + const filterGroup = await FilterGroupsService.deleteFilterGroup(idToken, label, slug); + + dispatch({ + type: ActionTypes.DELETE_FILTER_GROUP, + payload: filterGroup + }); + + return filterGroup; +}; diff --git a/frontend/src/redux/organiser/reducer.js b/frontend/src/redux/organiser/reducer.js index 7f2cb1e74..a98def2f9 100644 --- a/frontend/src/redux/organiser/reducer.js +++ b/frontend/src/redux/organiser/reducer.js @@ -42,6 +42,12 @@ const initialState = { updated: 0, data: [], map: {} + }, + filterGroups: { + loading: false, + error: false, + updated: 0, + data: [] } }; @@ -51,6 +57,7 @@ const statsHandler = buildHandler('stats'); const organisersHandler = buildHandler('organisers', 'userId'); const registrationsHandler = buildHandler('registrations', 'user'); const travelGrantsHandler = buildHandler('travelGrants', 'user'); +const filterGroupsHandler = buildHandler('filterGroups'); const teamsHandler = buildHandler('teams'); const editEvent = buildUpdatePath('event.data'); const editEventOrganisers = buildUpdatePath('event.data.organisers'); @@ -78,6 +85,46 @@ export default function reducer(state = initialState, action) { case ActionTypes.UPDATE_TRAVEL_GRANTS: { return travelGrantsHandler(state, action); } + case ActionTypes.UPDATE_FILTER_GROUPS: { + return filterGroupsHandler(state, action); + } + case ActionTypes.CREATE_FILTER_GROUP: { + return { + ...state, + filterGroups: { + ...state.filterGroups, + data: state.filterGroups.data.concat(action.payload) + } + }; + } + case ActionTypes.EDIT_FILTER_GROUP: { + return { + ...state, + filterGroups: { + ...state.filterGroups, + data: state.filterGroups.data.map(filterGroup => { + if (filterGroup.label === action.payload.label) { + return action.payload; + } + return filterGroup; + }) + } + }; + } + case ActionTypes.DELETE_FILTER_GROUP: { + return { + ...state, + filterGroups: { + ...state.filterGroups, + data: state.filterGroups.data.filter(filterGroup => { + if (filterGroup.label === action.payload.label) { + return false; + } + return true; + }) + } + }; + } case ActionTypes.CREATE_TRAVEL_GRANT: { return { ...state, diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 07666bb0f..0355f6929 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -37,6 +37,11 @@ export const travelGrantsLoading = state => state.organiser.travelGrants.loading export const travelGrantsError = state => state.organiser.travelGrants.error; export const travelGrantsUpdated = state => state.organiser.travelGrants.updated; +export const filterGroups = state => state.organiser.filterGroups.data; +export const filterGroupsLoading = state => state.organiser.filterGroups.loading; +export const filterGroupsError = state => state.organiser.filterGroups.error; +export const filterGroupsUpdated = state => state.organiser.filterGroups.updated; + export const registrationsAssigned = createSelector( AuthSelectors.getCurrentUser, registrations, diff --git a/frontend/src/services/filterGroups.js b/frontend/src/services/filterGroups.js new file mode 100644 index 000000000..8bb19851f --- /dev/null +++ b/frontend/src/services/filterGroups.js @@ -0,0 +1,40 @@ +import _axios from 'services/axios'; + +const FilterGroupsService = {}; + +function config(idToken) { + return { + headers: { + Authorization: `Bearer ${idToken}` + } + }; +} + +FilterGroupsService.createFilterGroup = (idToken, label, description, filters, eventSlug) => { + const data = { + label, + description, + filters + }; + return _axios.post(`/filter-groups/${eventSlug}`, data, config(idToken)); +}; + +FilterGroupsService.editFilterGroup = (idToken, label, description, filters, eventSlug) => { + const data = { + label, + description, + filters + }; + return _axios.patch(`/filter-groups/${eventSlug}`, data, config(idToken)); +}; + +FilterGroupsService.deleteFilterGroup = (idToken, label, eventSlug) => { + const data = { label }; + return _axios.delete(`/filter-groups/${eventSlug}`, { ...config(idToken), data }); +}; + +FilterGroupsService.getFilterGroupsForEvent = (idToken, eventSlug) => { + return _axios.get(`/filter-groups/${eventSlug}`, config(idToken)); +}; + +export default FilterGroupsService; diff --git a/shared/constants/filter-types.js b/shared/constants/filter-types.js index 88a63f9a1..e44a8f935 100644 --- a/shared/constants/filter-types.js +++ b/shared/constants/filter-types.js @@ -1,7 +1,7 @@ const filterTypes = { IS_EMPTY: { id: 'IS_EMPTY', - label: 'Is empty', + label: 'Is empty' }, NOT_EMPTY: { id: 'NOT_EMPTY', @@ -15,6 +15,14 @@ const filterTypes = { id: 'NOT_EQUALS', label: "Isn't equal to" }, + ONE_OF: { + id: 'ONE_OF', + label: 'Is one of' + }, + NOT_ONE_OF: { + id: 'NOT_ONE_OF', + label: "Isn't one of " + }, CONTAINS: { id: 'CONTAINS', label: 'Contains' @@ -30,7 +38,7 @@ const filterTypes = { }, NOT_LESS_THAN: { id: 'NOT_LESS_THAN', - label: "Is at least", + label: 'Is at least', helper: 'Or length is at least' }, MORE_THAN: { @@ -40,7 +48,7 @@ const filterTypes = { }, NOT_MORE_THAN: { id: 'NOT_MORE_THAN', - label: "Is at most", + label: 'Is at most', helper: 'Or length is at most' }, BOOLEAN_TRUE: { @@ -61,6 +69,8 @@ const stringFilterTypes = [ filterTypes.NOT_EQUALS.id, filterTypes.CONTAINS.id, filterTypes.NOT_CONTAINS.id, + filterTypes.ONE_OF.id, + filterTypes.NOT_ONE_OF.id, filterTypes.LESS_THAN.id, filterTypes.NOT_LESS_THAN.id, filterTypes.MORE_THAN.id, diff --git a/shared/constants/filter-values.js b/shared/constants/filter-values.js index c8fa83c6e..61f790a06 100644 --- a/shared/constants/filter-values.js +++ b/shared/constants/filter-values.js @@ -6,6 +6,7 @@ const FilterValues = { DATE: 'DATE', GENDER: 'GENDER', NATIONALITY: 'NATIONALITY', + COUNTRY: 'COUNTRY', LANGUAGE: 'LANGUAGE', STATUS: 'STATUS', TAG: 'TAG' diff --git a/shared/constants/registration-fields.js b/shared/constants/registration-fields.js index 4c0cb2842..cb284329d 100644 --- a/shared/constants/registration-fields.js +++ b/shared/constants/registration-fields.js @@ -240,7 +240,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Country of Residence', + type: FilterTypes.STRING, + valueType: FilterValues.COUNTRY + } + ] }, cityOfResidence: { label: 'City of residence', @@ -414,7 +422,15 @@ const FieldProps = { defaultEnable: false, defaultRequired: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Motivation', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, portfolio: { label: 'Link to Portfolio', @@ -430,7 +446,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Link to Portfolio', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, github: { label: 'Link to Github', @@ -446,7 +470,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Link to GitHub', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, linkedin: { label: 'LinkedIn Profile', @@ -461,7 +493,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'LinkedIn profile', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, countryOfTravel: { label: 'Country of Travel', @@ -473,7 +513,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Country of Travel', + type: FilterTypes.STRING, + valueType: FilterValues.COUNTRY + } + ] }, cityOfTravel: { label: 'City of Travel', @@ -496,7 +544,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Link to Portfolio', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, needsTravelGrant: { label: 'Do you want to apply for a travel grant?', @@ -513,7 +569,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Applied for travel grant', + type: FilterTypes.BOOLEAN, + valueType: FilterValues.BOOLEAN + } + ] }, needsAccommodation: { label: 'Do you need free accommodation?', @@ -525,7 +589,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Needs accommodation', + type: FilterTypes.BOOLEAN, + valueType: FilterValues.BOOLEAN + } + ] }, recruitmentOptions: { label: 'Job opportunities', @@ -553,7 +625,21 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: 'applyAsTeam', + label: 'Team > Applied as team', + type: FilterTypes.BOOLEAN, + valueType: FilterValues.BOOLEAN + }, + { + path: 'applyAlone', + label: 'Team > Applied also alone', + type: FilterTypes.BOOLEAN, + valueType: FilterValues.BOOLEAN + } + ] }, secretCode: { label: 'Secret code', @@ -565,7 +651,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Secret Code', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] } }; From 122bf33295e339ddcf0d6c91bd8f93781a5ed4f5 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 17:43:40 +0300 Subject: [PATCH 06/27] add filter types oneOf, containsOneOf --- shared/constants/filter-types.js | 12 ++++- shared/helpers/filterFunctions.js | 72 +++++++++++++++++++++++++---- shared/helpers/filterHelpers.js | 20 ++++++++ shared/test/filterFunctions.test.js | 42 +++++++++++++++++ 4 files changed, 137 insertions(+), 9 deletions(-) diff --git a/shared/constants/filter-types.js b/shared/constants/filter-types.js index e44a8f935..3bcae4ece 100644 --- a/shared/constants/filter-types.js +++ b/shared/constants/filter-types.js @@ -31,6 +31,14 @@ const filterTypes = { id: 'NOT_CONTAINS', label: "Doesn't contain" }, + CONTAINS_ONE_OF: { + id: 'CONTAINS_ONE_OF', + label: 'Contains one of' + }, + NOT_CONTAINS_ONE_OF: { + id: 'NOT_CONTAINS_ONE_OF', + label: "Doesn't contain one of" + }, LESS_THAN: { id: 'LESS_THAN', label: 'Is less than', @@ -85,7 +93,9 @@ const arrayFilterTypes = [ filterTypes.LESS_THAN.id, filterTypes.NOT_LESS_THAN.id, filterTypes.MORE_THAN.id, - filterTypes.NOT_MORE_THAN.id + filterTypes.NOT_MORE_THAN.id, + filterTypes.CONTAINS_ONE_OF.id, + filterTypes.NOT_CONTAINS_ONE_OF.id ]; const numberFilterTypes = [ diff --git a/shared/helpers/filterFunctions.js b/shared/helpers/filterFunctions.js index 62d98b636..2a5c977f0 100644 --- a/shared/helpers/filterFunctions.js +++ b/shared/helpers/filterFunctions.js @@ -1,9 +1,7 @@ const objectPath = require('object-path'); const _ = require('lodash'); -const isEmpty = (object, path) => { - const value = objectPath.get(object, path); - +const _isEmpty = value => { switch (typeof value) { case 'object': return _.isEmpty(value); @@ -18,9 +16,12 @@ const isEmpty = (object, path) => { } }; -const isEqualTo = (object, path, targetValue) => { +const isEmpty = (object, path) => { const value = objectPath.get(object, path); + return _isEmpty(value); +}; +const _isEqualTo = (value, targetValue) => { switch (typeof value) { case 'object': return _.isEqual(value, targetValue); @@ -38,9 +39,12 @@ const isEqualTo = (object, path, targetValue) => { } }; -const contains = (object, path, targetValue) => { +const isEqualTo = (object, path, targetValue) => { const value = objectPath.get(object, path); + return _isEqualTo(value, targetValue); +}; +const _contains = (value, targetValue) => { if (typeof value === 'string') { if (typeof targetValue === 'string') { return ( @@ -60,8 +64,27 @@ const contains = (object, path, targetValue) => { return false; }; -const isGte = (object, path, targetValue) => { +const contains = (object, path, targetValue) => { + const value = objectPath.get(object, path); + return _contains(value, targetValue); +}; + +const _containsOneOf = (value, targetValue) => { + if (!Array.isArray(targetValue)) return false; + + for (let item of targetValue) { + if (_contains(value, item)) return true; + } + + return false; +}; + +const containsOneOf = (object, path, targetValue) => { const value = objectPath.get(object, path); + return _containsOneOf(value, targetValue); +}; + +const _isGte = (value, targetValue) => { let numValue = parseInt(value); if (Array.isArray(value) || typeof value === 'string') { numValue = value.length; @@ -73,8 +96,12 @@ const isGte = (object, path, targetValue) => { return numValue >= numTarget; }; -const isLte = (object, path, targetValue) => { +const isGte = (object, path, targetValue) => { const value = objectPath.get(object, path); + return _isGte(value, targetValue); +}; + +const _isLte = (value, targetValue) => { let numValue = parseInt(value); if (Array.isArray(value) || typeof value === 'string') { numValue = value.length; @@ -86,10 +113,39 @@ const isLte = (object, path, targetValue) => { return numValue <= numTarget; }; +const isLte = (object, path, targetValue) => { + const value = objectPath.get(object, path); + return _isLte(value, targetValue); +}; + +const _isOneOf = (value, targetValue) => { + if (!Array.isArray(targetValue)) return false; + + for (let item of targetValue) { + if (_isEqualTo(value, item)) return true; + } + + return false; +}; + +const isOneOf = (object, path, targetValue) => { + const value = objectPath.get(object, path); + return _isOneOf(value, targetValue); +}; + module.exports = { isEmpty, isEqualTo, isGte, isLte, - contains + isOneOf, + contains, + containsOneOf, + _isEmpty, + _isEqualTo, + _isGte, + _isLte, + _isOneOf, + _contains, + _containsOneOf }; diff --git a/shared/helpers/filterHelpers.js b/shared/helpers/filterHelpers.js index 69e696a55..c58e15bea 100644 --- a/shared/helpers/filterHelpers.js +++ b/shared/helpers/filterHelpers.js @@ -26,6 +26,16 @@ const buildFiltersArray = filters => { return !FilterFunctions.isEqualTo(item, filter.path, filter.value); }; } + case FilterTypes.ONE_OF.id: { + return item => { + return FilterFunctions.isOneOf(item, filter.path, filter.value); + }; + } + case FilterTypes.NOT_ONE_OF.id: { + return item => { + return !FilterFunctions.isOneOf(item, filter.path, filter.value); + }; + } case FilterTypes.CONTAINS.id: { return item => { return FilterFunctions.contains(item, filter.path, filter.value); @@ -36,6 +46,16 @@ const buildFiltersArray = filters => { return !FilterFunctions.contains(item, filter.path, filter.value); }; } + case FilterTypes.CONTAINS_ONE_OF.id: { + return item => { + return FilterFunctions.containsOneOf(item, filter.path, filter.value); + }; + } + case FilterTypes.NOT_CONTAINS_ONE_OF.id: { + return item => { + return !FilterFunctions.containsOneOf(item, filter.path, filter.value); + }; + } case FilterTypes.LESS_THAN.id: { return item => { return !FilterFunctions.isGte(item, filter.path, filter.value); diff --git a/shared/test/filterFunctions.test.js b/shared/test/filterFunctions.test.js index 5f59fe573..64b6fa3c1 100644 --- a/shared/test/filterFunctions.test.js +++ b/shared/test/filterFunctions.test.js @@ -394,4 +394,46 @@ describe('Filter functions', function() { }); }); }); + describe('isOneOf', function() { + it('should return false when target value is empty array', function() { + const value = filterFunctions._isOneOf('something', []); + assert.equal(value, false); + }); + + it('should return false when target value does not include the value', function() { + const value = filterFunctions._isOneOf('something', ['other', 'third thing']); + assert.equal(value, false); + }); + + it('should return true when target value includes the value', function() { + const value = filterFunctions._isOneOf('something', ['something', 'other']); + assert.equal(value, true); + }); + }); + describe('containsOneOf', function() { + it('should return false when value is not an array', function() { + const value = filterFunctions._containsOneOf('foobar', ['some', 'values']); + assert.equal(value, false); + }); + it('should return false when target value is not an array', function() { + const value = filterFunctions._containsOneOf(['foobar'], 'hello'); + assert.equal(value, false); + }); + it('should return false when target value is empty array', function() { + const value = filterFunctions._containsOneOf(['foobar'], []); + assert.equal(value, false); + }); + it('should return false when target value is not contained in value', function() { + const value = filterFunctions._containsOneOf(['foo'], ['bar']); + assert.equal(value, false); + }); + it('should return true when target value is contained in value', function() { + const value1 = filterFunctions._containsOneOf(['foo', 'buzz'], ['foo', 'bar']); + assert.equal(value1, true); + const value2 = filterFunctions._containsOneOf(['fizz'], ['fizz']); + assert.equal(value2, true); + const value3 = filterFunctions._containsOneOf(['fizz'], ['fizz', 'buzz']); + assert.equal(value3, true); + }); + }); }); From e11a52bbab611e849d8937ca8cca10d84d207f6c Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 18:03:33 +0300 Subject: [PATCH 07/27] Allow multiple values in select component --- frontend/src/components/filters/FilterForm.js | 1 + .../components/filters/FilterValueInput.js | 9 +++++++- .../src/components/inputs/Select/index.js | 23 +++++++++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/filters/FilterForm.js b/frontend/src/components/filters/FilterForm.js index 743dd83d3..5ede3de22 100644 --- a/frontend/src/components/filters/FilterForm.js +++ b/frontend/src/components/filters/FilterForm.js @@ -107,6 +107,7 @@ const FilterForm = ({ onSubmit, event }) => { {items.map(item => ( @@ -56,7 +65,7 @@ const _Select = ({ label, helperText, value, onChange = () => {}, options = [], {item.label} ))} - + ); }; From b452d7f823fe53dd80e605ca19687c021adc7607 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 21:25:18 +0300 Subject: [PATCH 08/27] Add some pre-commit hooks for clean code --- frontend/package-lock.json | 525 +++++++++++++++++++++---------------- frontend/package.json | 20 +- package-lock.json | 155 +++++++++++ package.json | 3 +- 4 files changed, 462 insertions(+), 241 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4fe4cf646..2cafb0bbf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -960,15 +960,6 @@ "regenerator-runtime": "^0.13.2" } }, - "@babel/runtime-corejs2": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.6.2.tgz", - "integrity": "sha512-wdyVKnTv9Be4YlwF/7pByYNfcl23qC21aAQ0aIaZOo2ZOvhFEyJdBLJClYZ9i+Pmrz7sUQgg/MwbJa2RZTkygg==", - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - } - }, "@babel/template": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", @@ -2817,23 +2808,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" - } - } - }, "babel-preset-jest": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", @@ -3093,23 +3067,6 @@ "tweetnacl": "^0.14.3" } }, - "better-npm-run": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/better-npm-run/-/better-npm-run-0.1.1.tgz", - "integrity": "sha512-SBBYsUsb6bYcUMF9QUWy39GX5kzD4CoRBP11gx/k5jYkUr4Tr+irAokIeQX5FgfCRz0Q27rt8U0J4D2TlRgQFA==", - "requires": { - "commander": "^2.9.0", - "dotenv": "^2.0.0", - "object-assign": "^4.0.1" - }, - "dependencies": { - "dotenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-2.0.0.tgz", - "integrity": "sha1-vXWcNXqqcDZeAclrewvsCKbg2Uk=" - } - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -3398,6 +3355,12 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -4743,14 +4706,6 @@ "postcss": "^7.0.5" } }, - "css-box-model": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz", - "integrity": "sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -5058,11 +5013,6 @@ "assert-plus": "^1.0.0" } }, - "dashify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz", - "integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==" - }, "data-urls": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", @@ -5090,6 +5040,12 @@ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -5244,11 +5200,193 @@ "resolved": "https://registry.npmjs.org/deline/-/deline-1.0.4.tgz", "integrity": "sha1-bAXIeDaSbhocY+R4gvPS6yxvFMk=" }, + "depcheck": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.8.3.tgz", + "integrity": "sha512-xcLTnaovCFFTts5Ge7mUUhMGHSu6eRfftvVvOjN7gXO5EFUhJfX6UQa1b08a0SIwKfzG9eKNn5mzZlXp0mZARA==", + "dev": true, + "requires": { + "@babel/parser": "^7.3.1", + "@babel/traverse": "^7.2.3", + "builtin-modules": "^3.0.0", + "deprecate": "^1.0.0", + "deps-regex": "^0.1.4", + "js-yaml": "^3.4.2", + "lodash": "^4.17.11", + "minimatch": "^3.0.2", + "node-sass-tilde-importer": "^1.0.2", + "please-upgrade-node": "^3.1.1", + "require-package-name": "^2.0.1", + "resolve": "^1.10.0", + "vue-template-compiler": "^2.6.10", + "walkdir": "^0.3.2", + "yargs": "^13.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, + "deprecate": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz", + "integrity": "sha512-ZGDXefq1xknT292LnorMY5s8UVU08/WKdzDZCUT6t9JzsiMSP4uzUhgpqugffNVcT5WC6wMBiSQ+LFjlv3v7iQ==", + "dev": true + }, + "deps-regex": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz", + "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", + "dev": true + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -5471,11 +5609,6 @@ "is-obj": "^1.0.0" } }, - "dotenv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz", - "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==" - }, "dotenv-expand": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", @@ -6206,20 +6339,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, "eventemitter3": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", @@ -6696,6 +6815,12 @@ "pkg-dir": "^3.0.0" } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -6911,11 +7036,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -7591,14 +7711,6 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, - "if-env": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/if-env/-/if-env-1.0.4.tgz", - "integrity": "sha1-iytr0wivhqOhm7JzQmdhCFEEh4s=", - "requires": { - "npm-run-all": "1.4.0" - } - }, "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", @@ -10666,11 +10778,6 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" - }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -10734,11 +10841,6 @@ } } }, - "memoize-one": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", - "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -11227,6 +11329,15 @@ "true-case-path": "^1.0.2" } }, + "node-sass-tilde-importer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz", + "integrity": "sha512-Swcmr38Y7uB78itQeBm3mThjxBy9/Ah/ykPIaURY/L6Nec9AyRoL/jJ7ECfMR+oZeCTVQNxVMu/aHU+TLRVbdg==", + "dev": true, + "requires": { + "find-parent-dir": "^0.3.0" + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -11290,18 +11401,6 @@ "react-is": "^16.8.6" } }, - "npm-run-all": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-1.4.0.tgz", - "integrity": "sha1-pGm7n+q+W/OqmRaDO69ndlguiUg=", - "requires": { - "babel-polyfill": "^6.2.0", - "minimatch": "^3.0.0", - "ps-tree": "^1.0.1", - "shell-quote": "^1.4.3", - "which": "^1.2.0" - } - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -11584,6 +11683,12 @@ "lcid": "^1.0.0" } }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -11807,14 +11912,6 @@ "pinkie-promise": "^2.0.0" } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "requires": { - "through": "~2.3" - } - }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -11929,6 +12026,15 @@ } } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -12840,6 +12946,39 @@ "uniq": "^1.0.1" } }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -12956,14 +13095,6 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "requires": { - "event-stream": "=3.3.4" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -13060,11 +13191,6 @@ "performance-now": "^2.1.0" } }, - "raf-schd": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", - "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" - }, "ramda": { "version": "0.26.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", @@ -13705,36 +13831,6 @@ "pure-color": "^1.2.0" } }, - "react-beautiful-dnd": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz", - "integrity": "sha512-7llby9U+jIfkINcyxPHVWU0HFYzqxMemUYgGHsFsbx4fZo1n/pW6sYKYzhxGxR3Ap5HxqswcQkKUZX4uEUWhlw==", - "requires": { - "@babel/runtime-corejs2": "^7.4.5", - "css-box-model": "^1.1.2", - "memoize-one": "^5.0.4", - "raf-schd": "^4.0.0", - "react-redux": "^7.0.3", - "redux": "^4.0.1", - "tiny-invariant": "^1.0.4", - "use-memo-one": "^1.1.0" - }, - "dependencies": { - "react-redux": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", - "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==", - "requires": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - } - } - } - }, "react-dev-utils": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.0.4.tgz", @@ -13963,11 +14059,6 @@ "prop-types": "^15.5.8" } }, - "react-lazyload": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-2.6.2.tgz", - "integrity": "sha512-zbFiwI3H7W0/Qvb6T/ew2NiGe2wj+soYNW7vv5Dte1eZuJDvvyUOHo8GpYfEeWoP5x4Rree2Hwop+lCISalBwg==" - }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -14019,26 +14110,6 @@ "react-is": "^16.8.2" } }, - "react-remove-scroll": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.0.4.tgz", - "integrity": "sha512-Y1MTCsePsXF2H32qJ5RtZQJRkNycys2E7SPljDza4NwLeYFZAosjazz94D3J9KE7X37yOAJM0AbUq1BFmc+gjA==", - "requires": { - "react-remove-scroll-bar": "^2.0.0", - "react-style-singleton": "^2.0.0", - "tslib": "^1.0.0", - "use-sidecar": "^1.0.1" - } - }, - "react-remove-scroll-bar": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.0.0.tgz", - "integrity": "sha512-HSdWZ+6vV6X1btLRhQlIcFulaMePCyg0Un2oXMmDdq8lK9KPUMSnWYINYWVCdgT35em9hiUaR8frBN1PYYLLpQ==", - "requires": { - "react-style-singleton": "^2.0.0", - "tslib": "^1.0.0" - } - }, "react-router": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.0.tgz", @@ -14056,11 +14127,6 @@ "tiny-warning": "^1.0.0" } }, - "react-router-breadcrumbs-hoc": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-3.2.3.tgz", - "integrity": "sha512-kcjh7CkNkErF/lUmCk+xR/2EqdXCcjYPhlgAISeXHgew8xCYWEr++iypa8E5ATk5cU5/h1h7hexPgJjAqil4BA==" - }, "react-router-dom": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.0.tgz", @@ -14172,15 +14238,6 @@ "resize-observer-polyfill": "^1.5.0" } }, - "react-style-singleton": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.0.0.tgz", - "integrity": "sha512-1Kw9G/b7EqLspjtiKsC9jtoqkx+RAti+8PmBe47ioKTcdhB3gtaKw2Lc3Hsud/VrXh+QECZKmr2yDCwR7ObNtA==", - "requires": { - "invariant": "^2.2.4", - "tslib": "^1.0.0" - } - }, "react-textarea-autosize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", @@ -14597,6 +14654,12 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "require-package-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", + "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", + "dev": true + }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -14912,6 +14975,12 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -15373,6 +15442,16 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -15456,14 +15535,6 @@ } } }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -15562,14 +15633,6 @@ "readable-stream": "^2.0.2" } }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "requires": { - "duplexer": "~0.1.1" - } - }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -16591,20 +16654,6 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "use-memo-one": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", - "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" - }, - "use-sidecar": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.1.tgz", - "integrity": "sha512-CLTDS2AZmUcXXFnxP/h/OadtvBOoHHnLYMMpKGntb5vKOQT94icrXMXX0mEdGiMhQU8vxHlndB72sRwRBHXTzw==", - "requires": { - "detect-node": "^2.0.4", - "tslib": "^1.9.3" - } - }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -16719,6 +16768,16 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" }, + "vue-template-compiler": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz", + "integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -16737,6 +16796,12 @@ "xml-name-validator": "^3.0.0" } }, + "walkdir": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.3.2.tgz", + "integrity": "sha512-0Twghia4Z5wDGDYWURlhZmI47GvERMCsXIu0QZWVVZyW9ZjpbbZvD9Zy9M6cWiQQRRbAcYajIyKNavaZZDt1Uw==", + "dev": true + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/frontend/package.json b/frontend/package.json index a0f0e3752..804df1169 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,16 +10,12 @@ "antd": "^3.18.2", "auth0-js": "^9.10.0", "axios": "^0.18.0", - "better-npm-run": "^0.1.1", "classnames": "^2.2.6", "cloudinary-react": "^1.1.1", "connected-react-router": "^6.3.0", "customize-cra": "^0.2.12", - "dashify": "^2.0.0", - "dotenv": "^8.0.0", "formik": "^1.5.7", "framer-motion": "^1.4.2", - "if-env": "^1.0.4", "joi-browser": "^13.4.0", "lodash-es": "^4.17.11", "moment": "^2.24.0", @@ -30,7 +26,6 @@ "react": "^16.8.1", "react-animate-height": "^2.0.15", "react-app-rewired": "^2.1.3", - "react-beautiful-dnd": "^11.0.4", "react-dom": "^16.8.1", "react-facebook-pixel": "^0.1.3", "react-focus-within": "^2.0.1", @@ -38,11 +33,8 @@ "react-hyper-modal": "^1.1.1", "react-inlinesvg": "^0.8.4", "react-json-view": "^1.19.1", - "react-lazyload": "^2.6.2", "react-markdown": "^4.1.0", "react-redux": "^6.0.0", - "react-remove-scroll": "^2.0.4", - "react-router-breadcrumbs-hoc": "^3.2.0", "react-router-dom": "^5.1.0", "react-scripts": "3.0.1", "redux": "^4.0.1", @@ -57,8 +49,14 @@ "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "lint": "eslint src", + "depcheck": "depcheck .", + "find-unused": "delete-react-zombies" }, + "pre-commit": [ + "lint" + ], "eslintConfig": { "extends": "react-app" }, @@ -74,6 +72,8 @@ "npm": "6.9.0" }, "devDependencies": { - "babel-plugin-import": "^1.12.2" + "babel-plugin-import": "^1.12.2", + "depcheck": "^0.8.3", + "pre-commit": "^1.2.2" } } diff --git a/package-lock.json b/package-lock.json index cca688d1f..9e53cef44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,12 @@ "concat-map": "0.0.1" } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -134,11 +140,29 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -371,6 +395,12 @@ } } }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -407,6 +437,12 @@ "has-symbols": "^1.0.0" } }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -463,6 +499,16 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -533,6 +579,12 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -589,6 +641,45 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -607,6 +698,12 @@ "event-stream": "=3.3.4" } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -669,6 +766,21 @@ "path-type": "^3.0.0" } }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "recharts": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.7.1.tgz", @@ -746,6 +858,12 @@ "path-parse": "^1.0.6" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -778,6 +896,16 @@ "jsonify": "~0.0.0" } }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -837,6 +965,15 @@ "function-bind": "^1.0.2" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -857,6 +994,18 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -874,6 +1023,12 @@ "requires": { "isexe": "^2.0.0" } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true } } } diff --git a/package.json b/package.json index 14b85ab07..db003b439 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "author": "Juuso Lappalainen", "license": "ISC", "devDependencies": { - "npm-run-all": "^4.1.5" + "npm-run-all": "^4.1.5", + "pre-commit": "^1.2.2" }, "dependencies": { "better-npm-run": "^0.1.1", From e28653b7edd8fd1114ee730bbae97e909434cd98 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 21:26:00 +0300 Subject: [PATCH 09/27] remove unused script --- frontend/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 804df1169..501b66629 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,8 +51,7 @@ "test": "react-app-rewired test", "eject": "react-scripts eject", "lint": "eslint src", - "depcheck": "depcheck .", - "find-unused": "delete-react-zombies" + "depcheck": "depcheck ." }, "pre-commit": [ "lint" From 8409ccfb9b81e3a16e2211d3419e2a415156daa5 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 21:26:22 +0300 Subject: [PATCH 10/27] edit lint path --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 501b66629..68125c308 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,7 +50,7 @@ "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject", - "lint": "eslint src", + "lint": "eslint ./src", "depcheck": "depcheck ." }, "pre-commit": [ From 97fdadc9bfa0bff4300c978d859b8dd29c61ea50 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 21:27:40 +0300 Subject: [PATCH 11/27] remove unused pre-commit --- package-lock.json | 155 ---------------------------------------------- package.json | 3 +- 2 files changed, 1 insertion(+), 157 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e53cef44..cca688d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,12 +93,6 @@ "concat-map": "0.0.1" } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -140,29 +134,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -395,12 +371,6 @@ } } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -437,12 +407,6 @@ "has-symbols": "^1.0.0" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -499,16 +463,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -579,12 +533,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -641,45 +589,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -698,12 +607,6 @@ "event-stream": "=3.3.4" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -766,21 +669,6 @@ "path-type": "^3.0.0" } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, "recharts": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.7.1.tgz", @@ -858,12 +746,6 @@ "path-parse": "^1.0.6" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -896,16 +778,6 @@ "jsonify": "~0.0.0" } }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -965,15 +837,6 @@ "function-bind": "^1.0.2" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -994,18 +857,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -1023,12 +874,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true } } } diff --git a/package.json b/package.json index db003b439..14b85ab07 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,7 @@ "author": "Juuso Lappalainen", "license": "ISC", "devDependencies": { - "npm-run-all": "^4.1.5", - "pre-commit": "^1.2.2" + "npm-run-all": "^4.1.5" }, "dependencies": { "better-npm-run": "^0.1.1", From 22cbf0c86c7e616d65ababdd0b28ceba530f15a6 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 21:28:14 +0300 Subject: [PATCH 12/27] remove .pre-commit.sample --- pre-commit.sample | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100755 pre-commit.sample diff --git a/pre-commit.sample b/pre-commit.sample deleted file mode 100755 index d6c7dbc7d..000000000 --- a/pre-commit.sample +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, copy this file to .git/hooks/pre-commit - -if [ -z "$(git status shared --porcelain)" ]; then - echo "[PRE-COMMIT] No changes in /shared" -else - echo "[PRE-COMMIT] Found changes in shared!" - echo "[PRE-COMMIT] Publishing new version of @hackjunction/shared" - cd shared && npm version patch && npm publish --silent - echo "[PRE-COMMIT] Sleeping for 10s (wait for npm to publish package)" - sleep 10s - echo "[PRE-COMMIT] Upgrading backend to latest version of @hackjunction/shared" - cd ../backend && npm upgrade @hackjunction/shared - echo "[PRE-COMMIT] Upgrading frontend to latest version of @hackjunction/shared" - cd ../frontend && npm upgrade @hackjunction/shared - cd .. - echo "[PRE-COMMIT] Adding all related changes to commit" - git add backend/package.json ./backend/package-lock.json - git add frontend/package.json ./frontend/package-lock.json - git add shared/package.json ./shared/package-lock.json - echo "[PRE-COMMIT] Done." -fi -exit 0 From 451c6f935531895e03a07d701e95a3f4434639c5 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 22:27:38 +0300 Subject: [PATCH 13/27] Finalize oneOf, containsOneOf filter UI, better travel grant tool amounts section --- backend/modules/filter-group/model.js | 5 +- frontend/src/components/filters/FilterForm.js | 4 +- .../src/components/filters/FilterListItem.js | 25 ++++- .../src/components/inputs/Select/index.js | 31 ++++++- .../src/components/inputs/TextInput/index.js | 6 +- .../TravelGrantStepper.js | 92 +++++++++---------- 6 files changed, 104 insertions(+), 59 deletions(-) diff --git a/backend/modules/filter-group/model.js b/backend/modules/filter-group/model.js index a7898c924..e8df44825 100644 --- a/backend/modules/filter-group/model.js +++ b/backend/modules/filter-group/model.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose'); +const {FilterTypes} = require('@hackjunction/shared'); const FilterGroupSchema = new mongoose.Schema({ label: { @@ -28,11 +29,11 @@ const FilterGroupSchema = new mongoose.Schema({ }, type: { type: String, - enum: [], + enum: Object.keys(FilterTypes.filterTypes), required: true }, value: { - type: String + type: mongoose.Mixed } } ] diff --git a/frontend/src/components/filters/FilterForm.js b/frontend/src/components/filters/FilterForm.js index 5ede3de22..220298215 100644 --- a/frontend/src/components/filters/FilterForm.js +++ b/frontend/src/components/filters/FilterForm.js @@ -114,7 +114,7 @@ const FilterForm = ({ onSubmit, event }) => { options={filterOptions} /> - + {filterTypeOptions.length > 0 && (
handleAmountChange(label, value)} /> + } + ]} + /> ) }, { key: 'preview-spend', label: 'Preview spend', - render: () => + render: () =>

Step 2

}, { key: 'assign-grants', label: 'Assign grants', - render: () => Submit changes + render: () =>

Step 3

} ]} /> @@ -111,7 +109,9 @@ const TravelGrantStepper = ({ registrations }) => { }; const mapState = state => ({ - registrations: OrganiserSelectors.registrationsConfirmed(state) + registrations: OrganiserSelectors.registrationsConfirmed(state), + filterGroups: OrganiserSelectors.filterGroups(state), + filterGroupsLoading: OrganiserSelectors.filterGroupsLoading(state) }); export default connect(mapState)(TravelGrantStepper); From bc35110880976b3920bec70c2e4d1f06294951fc Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 22:28:52 +0300 Subject: [PATCH 14/27] Delete travel-grants module from backend --- backend/modules/routes.js | 2 - backend/modules/travel-grant/controller.js | 25 -------- backend/modules/travel-grant/model.js | 35 ----------- backend/modules/travel-grant/routes.js | 67 ---------------------- 4 files changed, 129 deletions(-) delete mode 100644 backend/modules/travel-grant/controller.js delete mode 100644 backend/modules/travel-grant/model.js delete mode 100644 backend/modules/travel-grant/routes.js diff --git a/backend/modules/routes.js b/backend/modules/routes.js index b652d68ec..9f64157e5 100644 --- a/backend/modules/routes.js +++ b/backend/modules/routes.js @@ -7,7 +7,6 @@ const newsletterRouter = require('./newsletter/routes'); const teamRouter = require('./team/routes'); const emailRouter = require('./email-task/routes'); const devToolsRouter = require('./devtools/routes'); -const travelGrantRouter = require('./travel-grant/routes'); const filterGroupRouter = require('./filter-group/routes'); module.exports = function(app) { @@ -26,7 +25,6 @@ module.exports = function(app) { app.use('/api/filter-groups', filterGroupRouter); app.use('/api/registrations', registrationRouter); app.use('/api/teams', teamRouter); - app.use('/api/travel-grants', travelGrantRouter); app.use('/api/user-profiles', userProfileRouter); /** Admin tools (development only) */ diff --git a/backend/modules/travel-grant/controller.js b/backend/modules/travel-grant/controller.js deleted file mode 100644 index 16adc287e..000000000 --- a/backend/modules/travel-grant/controller.js +++ /dev/null @@ -1,25 +0,0 @@ -const TravelGrant = require('./model'); -const { InsufficientPrivilegesError, ForbiddenError, NotFoundError } = require('../../common/errors/errors'); - -const controller = {}; - -controller.getTravelGrantsForEvent = eventId => { - return TravelGrant.find({ event: eventId }); -}; - -controller.createTravelGrantForEvent = (eventId, userId, sum, travelsFrom) => { - const travelGrant = new TravelGrant({ - sum, - travelsFrom, - user: userId, - event: eventId - }); - - return travelGrant.save(); -}; - -controller.getTravelGrantForUser = (userId, eventId) => { - return TravelGrant.findOne({ event: eventId, user: userId }); -}; - -module.exports = controller; diff --git a/backend/modules/travel-grant/model.js b/backend/modules/travel-grant/model.js deleted file mode 100644 index be83f3d1c..000000000 --- a/backend/modules/travel-grant/model.js +++ /dev/null @@ -1,35 +0,0 @@ -const mongoose = require('mongoose'); -const { Misc } = require('@hackjunction/shared'); -const CloudinaryImageSchema = require('../../common/schemas/CloudinaryImage'); - -const TravelGrantSchema = new mongoose.Schema({ - event: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Event', - required: true - }, - user: { - type: String, - required: true - }, - travelsFrom: { - type: String, - required: true - }, - status: { - type: String, - enum: Misc.travelGrantStatuses.ids, - default: Misc.travelGrantStatuses.items.accepted.id - }, - sum: { - type: Number - }, - receipt: CloudinaryImageSchema -}); - -TravelGrantSchema.index({ event: 1, user: 1 }, { unique: true }); -TravelGrantSchema.set('timestamps', true); - -const TravelGrant = mongoose.model('TravelGrant', TravelGrantSchema); - -module.exports = TravelGrant; diff --git a/backend/modules/travel-grant/routes.js b/backend/modules/travel-grant/routes.js deleted file mode 100644 index f7905a7a6..000000000 --- a/backend/modules/travel-grant/routes.js +++ /dev/null @@ -1,67 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const asyncHandler = require('express-async-handler'); - -const { Auth } = require('@hackjunction/shared'); - -const TravelGrantController = require('./controller'); - -const { hasToken } = require('../../common/middleware/token'); -const { hasPermission } = require('../../common/middleware/permissions'); -const { hasRegisteredToEvent, isEventOrganiser } = require('../../common/middleware/events'); - -const getTravelGrantsForEvent = asyncHandler(async (req, res) => { - const travelGrants = await TravelGrantController.getTravelGrantsForEvent(req.event._id.toString()); - return res.status(200).json(travelGrants); -}); - -const createTravelGrantForEvent = asyncHandler(async (req, res) => { - const travelGrant = await TravelGrantController.createTravelGrantForEvent( - req.event._id.toString(), - req.body.userId, - req.body.sum, - req.body.travelsFrom - ).catch(err => { - console.log('ERR', err); - }); - return res.status(200).json(travelGrant); -}); - -const getTravelGrantForUser = asyncHandler(async (req, res) => { - const travelGrant = await TravelGrantController.getTravelGrantForUser(req.user.sub, req.event._id.toString()); - return res.status(200).json(travelGrant); -}); - -router - .route('/:slug') - .get(hasToken, hasRegisteredToEvent, getTravelGrantForUser) - .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, createTravelGrantForEvent); - -router - .route('/:slug/all') - .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getTravelGrantsForEvent); - -// /** Organiser routes */ -// router -// .route('/organiser/:slug') -// .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getTeamsForEvent); - -// /** User-facing routes */ -// router -// .route('/:slug') -// .get(hasToken, hasRegisteredToEvent, getTeamForEvent) -// .post(hasToken, hasRegisteredToEvent, createTeamForEvent); - -// router -// .route('/:slug/:code') -// .delete(hasToken, hasRegisteredToEvent, deleteTeamForEvent) -// .patch(hasToken, hasRegisteredToEvent, lockTeamForEvent); - -// router -// .route('/:slug/:code/members') -// .post(hasToken, hasRegisteredToEvent, joinTeamForEvent) -// .delete(hasToken, hasRegisteredToEvent, leaveTeamForEvent); - -// router.route('/:slug/:code/members/:userId').delete(hasToken, hasRegisteredToEvent, removeMemberFromTeam); - -module.exports = router; From 7455723862cc57e3008793af5b9e09123c33f547 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 22:31:11 +0300 Subject: [PATCH 15/27] Make travel grant field in registrations editable --- backend/modules/registration/controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js index bf647aec1..ab8fc3c95 100644 --- a/backend/modules/registration/controller.js +++ b/backend/modules/registration/controller.js @@ -126,7 +126,7 @@ controller.assignRegistrationForEvent = data => { }; controller.bulkEditRegistrations = (eventId, registrationIds, edits) => { - const cleanedEdits = _.pick(edits, ['status', 'tags', 'rating', 'assignedTo']); + const cleanedEdits = _.pick(edits, ['status', 'tags', 'rating', 'assignedTo', 'travelGrant']); return Registration.updateMany( { event: eventId, @@ -155,6 +155,7 @@ controller.editRegistration = (registrationId, event, data, user) => { registration.ratedBy = user.sub; registration.tags = data.tags; registration.assignedTo = data.assignedTo; + registration.travelGrant = data.travelGrant; return registration.save(); }); }; From a62ac223079b1fab4ab0dab020be09efb3c163bf Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 22:37:41 +0300 Subject: [PATCH 16/27] Remove old travel grant code --- .../OrganiserEditEventGrants/AddGroupModal.js | 6 ++-- .../OrganiserEditEventGrants/index.js | 13 ++------- frontend/src/redux/organiser/actionTypes.js | 3 -- frontend/src/redux/organiser/actions.js | 28 ------------------- frontend/src/redux/organiser/reducer.js | 24 ---------------- frontend/src/redux/organiser/selectors.js | 15 +--------- 6 files changed, 7 insertions(+), 82 deletions(-) diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js index af68bf9dd..27806d39b 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js @@ -26,7 +26,7 @@ const AddGroupModal = ({ isOpen, onClose, onDone }) => { }, [handleClose, onDone, group, amount]); return ( - + { }, { key: 'enter-amount', - label: 'Size of travel grant', + label: 'Size of grant', render: () => ( ) } diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js index 9f7acdfe3..b9cba3d9c 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js @@ -25,17 +25,10 @@ const OrganiserEditEventGrants = ({}) => { const mapState = state => ({ event: OrganiserSelectors.event(state), registrations: OrganiserSelectors.registrationsConfirmed(state), - travelGrantsByUser: OrganiserSelectors.travelGrantsMap(state), - travelGrantsTotal: OrganiserSelectors.travelGrantsTotal(state), - loading: OrganiserSelectors.registrationsLoading(state) || OrganiserSelectors.travelGrantsLoading(state), - error: OrganiserSelectors.registrationsError(state) || OrganiserSelectors.travelGrantsError(state) -}); - -const mapDispatch = dispatch => ({ - updateTravelGrants: slug => dispatch(OrganiserActions.updateTravelGrants(slug)) + loading: OrganiserSelectors.registrationsLoading(state), + error: OrganiserSelectors.registrationsError(state) }); export default connect( - mapState, - mapDispatch + mapState )(OrganiserEditEventGrants); diff --git a/frontend/src/redux/organiser/actionTypes.js b/frontend/src/redux/organiser/actionTypes.js index 16c6980d8..1ba63684d 100644 --- a/frontend/src/redux/organiser/actionTypes.js +++ b/frontend/src/redux/organiser/actionTypes.js @@ -12,9 +12,6 @@ export const EDIT_REGISTRATION = 'organiser/EDIT_REGISTRATION'; export const UPDATE_TEAMS = 'organiser/UPDATE_TEAMS'; -export const UPDATE_TRAVEL_GRANTS = 'organiser/UPDATE_TRAVEL_GRANTS'; -export const CREATE_TRAVEL_GRANT = 'organiser/CREATE_TRAVEL_GRANT'; - export const UPDATE_FILTER_GROUPS = 'organiser/UPDATE_FILTER_GROUPS'; export const CREATE_FILTER_GROUP = 'organiser/CREATE_FILTER_GROUP'; export const EDIT_FILTER_GROUP = 'organiser/EDIT_FILTER_GROUP'; diff --git a/frontend/src/redux/organiser/actions.js b/frontend/src/redux/organiser/actions.js index d05523cb8..8beb3cb27 100644 --- a/frontend/src/redux/organiser/actions.js +++ b/frontend/src/redux/organiser/actions.js @@ -130,34 +130,6 @@ export const updateTeamsForEvent = slug => async (dispatch, getState) => { }); }; -/** Update travel grants with loading/error status */ -export const updateTravelGrants = slug => async (dispatch, getState) => { - const idToken = AuthSelectors.getIdToken(getState()); - if (!slug) return; - - dispatch({ - type: ActionTypes.UPDATE_TRAVEL_GRANTS, - promise: TravelGrantsService.getTravelGrantsForEvent(idToken, slug), - meta: { - onFailure: e => console.log('Error updating travel grants', e) - } - }); -}; - -export const createTravelGrant = (slug, sum, travelsFrom, userId) => async (dispatch, getState) => { - const idToken = AuthSelectors.getIdToken(getState()); - if (!slug) return; - - const travelGrant = await TravelGrantsService.createTravelGrantForUser(idToken, slug, sum, travelsFrom, userId); - - dispatch({ - type: ActionTypes.CREATE_TRAVEL_GRANT, - payload: travelGrant - }); - - return; -}; - /** Update filter groups with loading/error status */ export const updateFilterGroups = slug => async (dispatch, getState) => { const idToken = AuthSelectors.getIdToken(getState()); diff --git a/frontend/src/redux/organiser/reducer.js b/frontend/src/redux/organiser/reducer.js index a98def2f9..7fffad80f 100644 --- a/frontend/src/redux/organiser/reducer.js +++ b/frontend/src/redux/organiser/reducer.js @@ -36,13 +36,6 @@ const initialState = { updated: 0, data: [] }, - travelGrants: { - loading: false, - error: false, - updated: 0, - data: [], - map: {} - }, filterGroups: { loading: false, error: false, @@ -56,7 +49,6 @@ const eventHandler = buildHandler('event'); const statsHandler = buildHandler('stats'); const organisersHandler = buildHandler('organisers', 'userId'); const registrationsHandler = buildHandler('registrations', 'user'); -const travelGrantsHandler = buildHandler('travelGrants', 'user'); const filterGroupsHandler = buildHandler('filterGroups'); const teamsHandler = buildHandler('teams'); const editEvent = buildUpdatePath('event.data'); @@ -82,9 +74,6 @@ export default function reducer(state = initialState, action) { case ActionTypes.UPDATE_TEAMS: { return teamsHandler(state, action); } - case ActionTypes.UPDATE_TRAVEL_GRANTS: { - return travelGrantsHandler(state, action); - } case ActionTypes.UPDATE_FILTER_GROUPS: { return filterGroupsHandler(state, action); } @@ -125,19 +114,6 @@ export default function reducer(state = initialState, action) { } }; } - case ActionTypes.CREATE_TRAVEL_GRANT: { - return { - ...state, - travelGrants: { - ...state.travelGrants, - data: state.travelGrants.data.concat(action.payload), - map: { - ...state.travelGrants.map, - [action.payload.user]: action.payload - } - } - }; - } case ActionTypes.EDIT_REGISTRATION: { const registration = action.payload; return { diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 0355f6929..4bd219858 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -31,12 +31,6 @@ export const teamsLoading = state => state.organiser.teams.loading; export const teamsError = state => state.organiser.teams.error; export const teamsUpdated = state => state.organiser.teams.updated; -export const travelGrants = state => state.organiser.travelGrants.data; -export const travelGrantsMap = state => state.organiser.travelGrants.map; -export const travelGrantsLoading = state => state.organiser.travelGrants.loading; -export const travelGrantsError = state => state.organiser.travelGrants.error; -export const travelGrantsUpdated = state => state.organiser.travelGrants.updated; - export const filterGroups = state => state.organiser.filterGroups.data; export const filterGroupsLoading = state => state.organiser.filterGroups.loading; export const filterGroupsError = state => state.organiser.filterGroups.error; @@ -168,11 +162,4 @@ export const reviewAverageByReviewer = createSelector( return meanBy(registrations, 'rating'); }); } -); - -export const travelGrantsTotal = createSelector( - travelGrants, - travelGrants => { - return sumBy(travelGrants, 'sum'); - } -); +); \ No newline at end of file From 327ea1a7cb1ed795011486bbb2a5f33de72a75ed Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Mon, 30 Sep 2019 22:45:20 +0300 Subject: [PATCH 17/27] Add travel grant field to registration edit modal --- .../modals/EditRegistrationModal/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/modals/EditRegistrationModal/index.js b/frontend/src/components/modals/EditRegistrationModal/index.js index 6373820d7..bdf5d713d 100644 --- a/frontend/src/components/modals/EditRegistrationModal/index.js +++ b/frontend/src/components/modals/EditRegistrationModal/index.js @@ -4,7 +4,7 @@ import Modal from 'components/generic/Modal'; import { Typography } from '@material-ui/core'; import { withSnackbar } from 'notistack'; import { RegistrationFields } from '@hackjunction/shared'; -import { Rate, notification, Divider as AntDivider, Tag, Drawer, List, Select, Button as AntButton } from 'antd'; +import { Rate, notification, Divider as AntDivider, Tag, Drawer, List, Select, Button as AntButton, Input } from 'antd'; import { isEqual, groupBy, find } from 'lodash-es'; import PageWrapper from 'components/PageWrapper'; @@ -27,7 +27,8 @@ const EditRegistrationModalInner = ({ idToken, event, registration, organisers, rating: registration.rating, assignedTo: registration.assignedTo, tags: registration.tags, - status: registration.status + status: registration.status, + travelGrant: registration.travelGrant }; const [formValues, setFormValues] = useState(initialValues); const dirty = !isEqual(formValues, initialValues); @@ -102,6 +103,14 @@ const EditRegistrationModalInner = ({ idToken, event, registration, organisers, } /> + + handleEdit('travelGrant', e.target.value)} /> + } + /> + + + + }, + { + label: 'Schedule', + content: + }, + { + label: 'Questions', + content: + }, + { + label: 'Miscellaneous', + content: + } + ]} + /> + + + ); + }} + + + ); +}; + +const mapStateToProps = state => ({ + event: OrganiserSelectors.event(state), + loading: OrganiserSelectors.eventLoading(state) +}); + +const mapDispatchToProps = dispatch => ({ + editEvent: (slug, data) => dispatch(OrganiserActions.editEvent(slug, data)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(OrganiserEditEventDetails); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/AddOrganiserDrawer.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/AddOrganiserDrawer.js similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/AddOrganiserDrawer.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/AddOrganiserDrawer.js diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/AddOrganiserDrawer.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/AddOrganiserDrawer.module.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/AddOrganiserDrawer.module.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/AddOrganiserDrawer.module.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/index.js similarity index 52% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/index.js index 46fa6591c..01900b71e 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/index.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { PageHeader, Button, Table, notification, message } from 'antd'; +import { Button, Table, notification, message } from 'antd'; import { concat } from 'lodash-es'; import { connect } from 'react-redux'; import { push } from 'connected-react-router'; @@ -8,7 +8,7 @@ import * as OrganiserActions from 'redux/organiser/actions'; import * as OrganiserSelectors from 'redux/organiser/selectors'; import AddOrganiserDrawer from './AddOrganiserDrawer'; -import Divider from 'components/generic/Divider'; +import PageHeader from 'components/generic/PageHeader'; import PageWrapper from 'components/PageWrapper'; const OrganiserEditEventManage = ({ @@ -74,61 +74,48 @@ const OrganiserEditEventManage = ({ error={!event && !eventLoading} render={() => ( - Manage who has access to edit this event

} - extra={[ - + + +
record.firstName + ' ' + record.lastName + }, + { + title: 'Email', + dataIndex: 'email', + key: 'email' + }, + { + title: 'Actions', + dataIndex: 'userId', + key: 'actions', + render: (text, record) => { + if (event.owner === record.userId) { + return ( + + ); + } + return ( + + ); + } + } ]} - footer={ - -
record.firstName + ' ' + record.lastName - }, - { - title: 'Email', - dataIndex: 'email', - key: 'email' - }, - { - title: 'Actions', - dataIndex: 'userId', - key: 'actions', - render: (text, record) => { - if (event.owner === record.userId) { - return ( - - ); - } - return ( - - ); - } - } - ]} - /> - - - } /> { - const [editOpen, setEditOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(false); - const registration = getAttendee(slug, registrationId); - const { answers } = registration; - - useEffect(() => { - if (registrationId) { - setLoading(true); - updateAttendee(slug, registrationId) - .catch(err => { - setError(true); - }) - .finally(() => { - setLoading(false); - }); - } - }, [registrationId, slug, updateAttendee]); - - function handleRegistrationEdit(values) { - editAttendee(slug, registrationId, values); - } - - function handleAccept() { - acceptAttendee(slug, registrationId); - } - - function handleReject() { - rejectAttendee(slug, registrationId); - } - - function renderError() { - return ( -
-

Something went wrong

-

Please close the drawer and try again

-
- ); - } - - function renderContent() { - const fields = Object.keys(answers); - const grouped = groupBy(fields, field => RegistrationFields.getCategory(field)); - const categoryNames = Object.keys(grouped); - const currentStatus = RegistrationStatuses.asObject[registration.status]; - - return ( - - {currentStatus.allowEdit && ( -
- Actions: - - - - - - - - -
- )} - - setEditOpen(true)}> - Edit - - } - > - {categoryNames.map((categoryName, index) => { - return ( - - - {grouped[categoryName].map(field => { - let label = RegistrationFields.fieldToLabelMap[field]; - if (!label) { - const customField = find( - event.registrationQuestions, - f => f.name === field - ); - if (customField) { - label = customField.label; - } - } - return ( - - {JSON.stringify(registration.answers[field])} - - ); - })} - - - ); - })} - - setEditOpen(false)} - onSubmit={handleRegistrationEdit} - registration={registration} - /> -
- ); - } - - function renderTitle() { - const currentStatus = RegistrationStatuses.asObject[registration.status]; - return ( -
- {registration.answers.firstName + ' ' + registration.answers.lastName} - - status: {currentStatus.label} -
- ); - } - return ( - - - {error && renderError()} - {!error && answers && renderContent()} - - - ); -}; - -const mapStateToProps = state => ({ - idToken: AuthSelectors.getIdToken(state), - getAttendee: OrganiserSelectors.getAttendeeByEvent(state) -}); - -const mapDispatchToProps = dispatch => ({ - editAttendee: (slug, registrationId, data) => dispatch(OrganiserActions.editAttendee(slug, registrationId, data)), - acceptAttendee: (slug, registrationId) => dispatch(OrganiserActions.acceptAttendee(slug, registrationId)), - rejectAttendee: (slug, registrationId) => dispatch(OrganiserActions.rejectAttendee(slug, registrationId)), - updateAttendee: (slug, registrationId) => dispatch(OrganiserActions.updateAttendee(slug, registrationId)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AttendeeDrawer); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.module.scss deleted file mode 100644 index c2dc51e53..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -.drawerTitle { - display: flex; - flex-direction: row; -} - -.buttonsWrapper { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - background: #ececec; - padding: 1rem; -} - -.acceptButton { - color: green; -} - -.rejectButton { - color: red; -} - -.buttonsLabel { - font-weight: bold; -} diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.js deleted file mode 100644 index dc347fcbe..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.js +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import styles from './AttendeeDrawerEdit.module.scss'; - -import { filter } from 'lodash-es'; -import { connect } from 'react-redux'; -import { RegistrationStatuses } from '@hackjunction/shared'; -import { Formik } from 'formik'; -import { Drawer, Descriptions, Button, Rate, Tag, Dropdown, Menu, Icon } from 'antd'; -import Divider from 'components/generic/Divider'; -import * as AuthSelectors from 'redux/auth/selectors'; - -const AttendeeDrawerEdit = ({ isOpen, onClose, onSubmit, registration, session }) => { - function buildInitial() { - return { - status: registration.status, - rating: registration.rating, - ratedBy: registration.ratedBy - }; - } - - function handleSubmit(values) { - onSubmit(values); - onClose(); - } - - function handleRatingChange(rating, setFieldValue) { - setFieldValue('rating', rating); - setFieldValue('ratedBy', session.sub); - } - - function renderStatusMenu(currentStatus, setFieldValue) { - const statuses = RegistrationStatuses.asArray.filter(status => status.id !== currentStatus.id); - const assignable = filter(statuses, s => s.allowAssign); - return ( - - {assignable.map(status => ( - setFieldValue('status', status.id)}> - {status.label} - - ))} - - ); - } - - const initialValues = buildInitial(); - - return ( - - - {formikProps => { - const { values } = formikProps; - const currentStatus = RegistrationStatuses.asObject[values.status]; - const initialStatus = RegistrationStatuses.asObject[initialValues.status]; - return ( - - - -
-
- {currentStatus.label} - } - type="link" - > - Change - -
- - {currentStatus.description} -
-
- - handleRatingChange(value, formikProps.setFieldValue)} - /> - - - {values.ratedBy} - -
- - -
- ); - }} -
-
- ); -}; - -const mapStateToProps = state => ({ - session: AuthSelectors.getCurrentUser(state) -}); - -export default connect(mapStateToProps)(AttendeeDrawerEdit); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.module.scss deleted file mode 100644 index 9b1df2879..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawerEdit.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.statusWrapper { - display: flex; - align-items: flex-start; - flex-direction: column; -} - -.menuSubHeader { - text-align: center; - color: red; - padding: 10px; - width: 200px; -} diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeesOverview.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeesOverview.js deleted file mode 100644 index e891b0a9a..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeesOverview.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useEffect } from 'react'; -import styles from './AttendeesOverview.module.scss'; - -import { Row, Col, Card, Statistic, Icon } from 'antd'; -import RegistrationsService from 'services/registrations'; - -const STATS = [ - { - label: 'Applications', - getValue: () => 272 - }, - { - label: 'Amount rated', - getValue: () => 16, - suffix: '%' - } -]; - -const AttendeesOverview = () => { - const renderStatCards = () => { - return STATS.map(stat => { - return ( -
- - - - - ); - }); - }; - - return {renderStatCards()}; -}; - -export default AttendeesOverview; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer.js deleted file mode 100644 index f7ffdeaf8..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer.js +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react'; -import styles from './FiltersDrawer.module.scss'; -import { connect } from 'react-redux'; -import { concat, filter, map } from 'lodash-es'; -import { RegistrationStatuses, RegistrationFields } from '@hackjunction/shared'; -import { Drawer, Tag, Descriptions, Input, Button, Select, Row, Col } from 'antd'; -import { Formik } from 'formik'; - -import Divider from 'components/generic/Divider'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; - -const FiltersDrawer = ({ onClose, isOpen, getFilters, setFilters, event }) => { - const filters = getFilters(event.slug); - - function handleSubmit(values) { - setFilters(event.slug, values); - onClose(); - } - - function handleReset() { - setFilters(event.slug, {}); - onClose(); - } - - function renderStatusSelect(formikProps) { - const selected = formikProps.values.status || []; - return RegistrationStatuses.asArray.map(status => { - const checked = selected.indexOf(status.id) !== -1; - return ( - { - if (checked) { - formikProps.setFieldValue('status', concat(selected, status.id)); - } else { - formikProps.setFieldValue('status', filter(selected, s => s !== status.id)); - } - }} - > - {status.label} - - ); - }); - } - - function renderSearchFilter(formikProps) { - const currentValue = formikProps.values.email || ''; - return ( - formikProps.setFieldValue('email', e.target.value)} - /> - ); - } - - function renderFieldExists(formikProps) { - const currentValue = formikProps.values.fields || undefined; - const userDetailFields = Object.keys(event.userDetailsConfig).map(q => ({ - name: q, - label: RegistrationFields.getLabel(q) - })); - const registrationFields = map(event.registrationQuestions, q => ({ - name: q.name, - label: q.label - })); - return ( - - ); - } - - return ( - - - {formikProps => { - return ( - - - -

Filter participants by application status

- {renderStatusSelect(formikProps)} -
- -

Search by email address

- {renderSearchFilter(formikProps)} -
- -

Select all fields that must exist on the application

- {renderFieldExists(formikProps)} -
-
- - -
- - - - - - - - ); - }} - - - ); -}; - -const mapStateToProps = state => ({ - getFilters: OrganiserSelectors.getAttendeesFiltersForEvent(state) -}); - -const mapDispatchToProps = dispatch => ({ - setFilters: (slug, filters) => dispatch(OrganiserActions.setAttendeesFiltersForEvent(slug, filters)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(FiltersDrawer); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/index.js deleted file mode 100644 index 9e134acac..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/index.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react'; - -import { PageHeader, Table, Dropdown, Menu, Icon, Tag, Button, Menu } from 'antd'; -import { connect } from 'react-redux'; -import { isEmpty } from 'lodash-es'; -import { RegistrationStatuses } from '@hackjunction/shared'; - -import MiscUtils from 'utils/misc'; -import * as AuthSelectors from 'redux/auth/selectors'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; -import Divider from 'components/generic/Divider'; -import AttendeeDrawer from './AttendeeDrawer'; -import FiltersDrawer from 'pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/FiltersDrawer'; - -const OrganiserEditEventAttendees = ({ - slug, - idToken, - getEventBySlug, - updateAttendees, - getAttendeeByEvent, - getAttendeeIds, - getAttendeeFilters -}) => { - const [activeItem, setActiveItem] = useState(); - const [filtersOpen, setFiltersOpen] = useState(false); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(false); - const event = getEventBySlug(slug); - const attendeeIds = getAttendeeIds(slug); - const filters = getAttendeeFilters(slug); - const hasFilters = !isEmpty(filters); - - const updateAttendeesCallback = useCallback(() => { - updateAttendees(slug) - .catch(() => { - setError(true); - }) - .finally(() => { - setLoading(false); - }); - }, [slug, updateAttendees]); - - useEffect(() => { - setLoading(true); - updateAttendeesCallback(); - }, [updateAttendeesCallback]); - - async function handleFiltersClosed() { - setFiltersOpen(false); - setLoading(true); - await MiscUtils.sleep(1000); - updateAttendees(slug) - .catch(() => { - setError(true); - }) - .finally(() => { - setLoading(false); - }); - } - - function renderTop() { - return

View who has registered to your event

; - } - - function renderContent() { - return ( -
-
{ - const { color, label } = RegistrationStatuses.asObject[status]; - return {label}; - } - }, - { - title: 'Actions', - dataIndex: 'email', - render: (email, record) => { - return ( - ( - - setActiveItem(record._id)}>Show - Full Details - Show Profile - Delete - - )} - > -
- Actions -
-
- ); - } - } - ]} - dataSource={attendeeIds.map(id => getAttendeeByEvent(slug, id))} - pagination={false} - footer={() => {selectedRowKeys.length} selected} - /> - - ); - } - - return ( - - - Search - Assigned to me - 3rd section - - - Filters active - - ), - - ]} - footer={ - - - {!error && renderContent()} - setActiveItem(undefined)} - /> - - - - } - /> - - ); -}; - -const mapStateToProps = state => ({ - idToken: AuthSelectors.getIdToken(state), - getEventBySlug: OrganiserSelectors.getEventBySlug(state), - getAttendeeByEvent: OrganiserSelectors.getAttendeeByEvent(state), - getAttendeeIds: OrganiserSelectors.getAttendeeIdsForEvent(state), - getAttendeeFilters: OrganiserSelectors.getAttendeesFiltersForEvent(state) -}); - -const mapDispatchToProps = dispatch => ({ - updateAttendees: slug => dispatch(OrganiserActions.updateAttendeesForEvent(slug)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(OrganiserEditEventAttendees); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDebug/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDebug/index.js deleted file mode 100644 index 6e1926bb6..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDebug/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import ReactJson from 'react-json-view'; - -import { connect } from 'react-redux'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; - -const OrganiserEditEventDebug = ({ slug, getEventBySlug }) => { - const event = getEventBySlug(slug); - - return ; -}; - -const mapStateToProps = state => ({ - getEventBySlug: OrganiserSelectors.getEventBySlug(state) -}); - -export default connect(mapStateToProps)(OrganiserEditEventDebug); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDetails/OrganiserEditEventDetails.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDetails/OrganiserEditEventDetails.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDetails/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDetails/index.js deleted file mode 100644 index 84b1f7f3f..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventDetails/index.js +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import styles from './OrganiserEditEventDetails.module.scss'; - -import { Tabs, PageHeader, Button, notification } from 'antd'; -import { Formik } from 'formik'; -import { connect } from 'react-redux'; -import { forOwn } from 'lodash-es'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; -import Divider from 'components/generic/Divider'; -import OrganiserEditEventInfo from './OrganiserEditEventInfo'; -import OrganiserEditEventTimes from './OrganiserEditEventTimes'; -import OrganiserEditEventRegistration from './OrganiserEditEventRegistration'; -import OrganiserEditEventMisc from './OrganiserEditEventMisc'; -import BlockExitIfDirty from 'components/FormComponents/BlockExitIfDirty'; - -const { TabPane } = Tabs; - -const OrganiserEditEventDetails = ({ event, editEvent }) => { - const { slug } = event; - function onSubmit(values, actions) { - const changed = {}; - forOwn(values, (value, field) => { - if (event[field] !== value) { - changed[field] = value; - } - }); - editEvent(slug, changed) - .then(savedEvent => { - notification.success({ - message: 'Your changes were saved successfully' - }); - actions.setSubmitting(false); - }) - .catch(err => { - const { message, errors } = err.response.data; - - if (errors) { - const errorKeys = Object.keys(errors); - - notification.error({ - message: 'Unable to save changes', - description: ( -
    - {errorKeys.map(key => ( -
  • - {key} {errors[key].message} -
  • - ))} -
- ) - }); - } else { - notification.error({ - message: 'Unable to save changes', - description: message - }); - } - }) - .finally(() => { - actions.setSubmitting(false); - }); - } - - return ( - - {formikProps => { - const errorCount = Object.keys(formikProps.errors).length; - const hasErrors = errorCount !== 0; - const canSave = formikProps.dirty && !hasErrors; - const isPublic = formikProps.values.published; - - return ( - - Configure your event information, registration settings and schedule

} - extra={[ - , - - ]} - /> - - - - - - - - - - - - - - - - - - - - - - - -
- ); - }} -
- ); -}; - -const mapStateToProps = state => ({ - event: OrganiserSelectors.event(state) -}); - -const mapDispatchToProps = dispatch => ({ - editEvent: (slug, data) => dispatch(OrganiserActions.editEvent(slug, data)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(OrganiserEditEventDetails); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js deleted file mode 100644 index 27806d39b..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/AddGroupModal.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState, useCallback } from 'react'; - -import Stepper from 'components/generic/Stepper'; - -import Modal from 'components/generic/Modal'; -import FilterGroupMenu from 'components/filters/FilterGroupMenu'; -import TextInput from 'components/inputs/TextInput'; - -const AddGroupModal = ({ isOpen, onClose, onDone }) => { - const [activeStep, setActiveStep] = useState(0); - const [group, setGroup] = useState(); - const [amount, setAmount] = useState(0); - - const reset = useCallback(() => { - setActiveStep(0); - }, []); - - const handleClose = useCallback(() => { - reset(); - onClose(); - }, [onClose, reset]); - - const handleDone = useCallback(() => { - onDone(group, amount); - handleClose(); - }, [handleClose, onDone, group, amount]); - - return ( - - - }, - { - key: 'enter-amount', - label: 'Size of grant', - render: () => ( - - ) - } - ]} - /> - - ); -}; - -export default AddGroupModal; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/BulkAssignGrantsPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/BulkAssignGrantsPage.js deleted file mode 100644 index 5a1f12e1d..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/BulkAssignGrantsPage.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; - -import { Grid } from '@material-ui/core'; - -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import TravelGrantStepper from './TravelGrantStepper'; - -const BulkAssignGrantsPage = ({ registrations, filterGroups }) => { - return ( - - - - - - ); -}; - -const mapState = state => ({ - registrations: OrganiserSelectors.registrations(state), - filterGroups: OrganiserSelectors.filterGroups(state) -}); - -export default connect(mapState)(BulkAssignGrantsPage); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/OrganiserEditEventGrants.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/OrganiserEditEventGrants.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js deleted file mode 100644 index c676e2f9a..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepperPreview.js +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useMemo } from 'react'; - -import { sortBy, find } from 'lodash-es'; -import { FilterHelpers } from '@hackjunction/shared'; - -import Table from 'components/generic/Table'; - -const TravelGrantStepperPreview = ({ registrations, groupConfig }) => { - const sorted = sortBy(registrations, 'createdAt'); - - const registrationsByGroup = useMemo(() => { - return groupConfig.map(({ group, amount }) => { - const matching = FilterHelpers.applyFilters(registrations, group.filters).map(r => r._id); - - return { - group, - amount, - matching - }; - }); - }, [registrations, groupConfig]); - - const mapped = useMemo(() => { - return sorted.map(reg => { - const group = find(registrationsByGroup, ({ matching }) => { - return matching.indexOf(reg._id); - }); - - if (group) { - return { - ...reg, - amount: group.amount, - group: group.group - }; - } else { - return { - ...reg, - amount: 0, - group: 'No match' - }; - } - }); - }, [sorted, registrationsByGroup]); - - return ( -
`${answers.firstName} ${answers.lastName}` - }, - { - key: 'travelsFrom', - label: 'Travels from', - path: 'answers.countryOfTravel' - }, - { - key: 'createdAt', - label: 'Registered', - path: 'createdAt' - }, - { - key: 'group', - label: 'Group', - path: 'group', - render: group => group.label - }, - { - key: 'amount', - label: 'Amount', - path: 'amount' - } - ]} - rowKey="_id" - loading={false} - pagination={true} - rowNumber={false} - rowSelection={false} - title={'Preview'} - /> - ); -}; - -export default TravelGrantStepperPreview; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js deleted file mode 100644 index b9cba3d9c..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useMemo, useState, useCallback, useEffect } from 'react'; -import styles from './OrganiserEditEventGrants.module.scss'; - -import { PageHeader } from 'antd'; -import { connect } from 'react-redux'; - -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; - -import PageWrapper from 'components/PageWrapper'; -import BulkAssignGrantsPage from './BulkAssignGrantsPage'; - -const OrganiserEditEventGrants = ({}) => { - return ( - - Configure travel grants for your participants

} - footer={} - >
-
- ); -}; - -const mapState = state => ({ - event: OrganiserSelectors.event(state), - registrations: OrganiserSelectors.registrationsConfirmed(state), - loading: OrganiserSelectors.registrationsLoading(state), - error: OrganiserSelectors.registrationsError(state) -}); - -export default connect( - mapState -)(OrganiserEditEventGrants); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js deleted file mode 100644 index 926abbaf8..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js +++ /dev/null @@ -1,343 +0,0 @@ -import React, { useState, useCallback, useEffect, useMemo } from 'react'; -import './AttendeeFilters.scss'; - -import { connect } from 'react-redux'; -import { Button as AntButton, Select, Row, Col, Rate, Tag, Collapse, Input, Steps } from 'antd'; -import { RegistrationStatuses, RegistrationFields } from '@hackjunction/shared'; -import { find } from 'lodash-es'; - -import Divider from 'components/generic/Divider'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; -import FilterOptions from 'constants/filters'; - -const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) => { - const [filterType, setFilterType] = useState(); - const [filterValue, setFilterValue] = useState(); - const [filterField, setFilterField] = useState(); - const disabled = !filterType; - - useEffect(() => { - setFilterType(undefined); - setFilterValue(undefined); - setFilterField(undefined); - }, [filters]); - - const handleAdd = useCallback(() => { - const filter = { - type: filterType, - value: filterValue, - field: filterField - }; - setFilters(filters.concat(filter)); - }, [filterType, filterValue, filterField, filters, setFilters]); - - const handleRemove = useCallback( - index => { - const newFilters = filters.filter((f, idx) => { - return idx !== index; - }); - setFilters(newFilters); - }, - [filters, setFilters] - ); - - const handleReset = useCallback(() => { - setFilters([]); - }, [setFilters]); - - const handleTypeChange = useCallback(type => { - setFilterType(type); - setFilterValue(undefined); - }, []); - - const handleFieldChange = useCallback(field => { - setFilterField(field); - }, []); - - const questionSelect = useMemo(() => { - return ( - - ); - }, [event, handleFieldChange, filterField]); - - const renderOptions = () => { - switch (filterType) { - case 'status-equals': - case 'status-nequals': { - return ( - - ); - } - case 'rating-lte': - case 'rating-gte': - return ; - case 'tags-contain': - case 'tags-not-contain': - return ( - - ); - case 'field-equals': - case 'field-nequals': - case 'field-contains': - case 'field-not-contains': - return ( - - {questionSelect} - - setFilterValue(e.target.value)} - placeholder="Enter value" - size="large" - /> - - ); - case 'field-not-empty': - case 'field-empty': - return questionSelect; - default: - return null; - } - }; - - const renderForm = () => { - return ( - - - - - - - - {renderOptions()} - - - - - Add filter - - - - - - - ); - }; - - const renderItemValue = (filter, label) => { - switch (filter.type) { - case 'status-equals': - case 'status-nequals': { - const statuses = RegistrationStatuses.asArray - .filter(status => { - return filter.value && filter.value.indexOf(status.id) !== -1; - }) - .map(status => { - return {status.label}; - }); - return ( - - {label} {statuses} - - ); - } - case 'rating-lte': - case 'rating-gte': { - return ( - - {label} - - ); - } - case 'tags-contain': - case 'tags-not-contain': - const tags = event.tags - .filter(tag => { - return filter.value && filter.value.indexOf(tag.label) !== -1; - }) - .map(tag => { - return {tag.label}; - }); - return ( - - {label} {tags} - - ); - case 'field-equals': - return ( - - {filter.field} EQUALS {filter.value} - - ); - case 'field-nequals': - return ( - - {filter.field} DOES NOT EQUAL {filter.value} - - ); - case 'field-empty': - return ( - - {filter.field} IS EMPTY - - ); - case 'field-not-empty': - return ( - - {filter.field} IS NOT EMPTY - - ); - case 'field-contains': { - return ( - - {filter.field} CONTAINS {filter.value} - - ); - } - case 'field-not-contains': { - return ( - - {filter.field} DOES NOT CONTAIN {filter.value} - - ); - } - default: - return {label}; - } - }; - - const renderFilterSteps = () => { - return filters.map((filter, idx) => { - const label = find(FilterOptions, option => option.id === filter.type).label; - - return ( - {renderItemValue(filter, label)}} - description={ - handleRemove(idx)}> - Remove filter - - } - /> - ); - }); - }; - - return ( - - - Clear filters - - } - > - {renderForm()} - - index !== 0 && AND - } - direction="vertical" - className="AttendeeFilters--steps" - > - {renderFilterSteps()} - - - - ); -}; - -const mapState = state => ({ - event: OrganiserSelectors.event(state), - registrations: OrganiserSelectors.registrations(state), - filters: [] -}); - -const mapDispatch = dispatch => ({ - setFilters: filters => dispatch(OrganiserActions.setRegistrationsFilters(filters)) -}); - -export default connect( - mapState, - mapDispatch -)(AttendeeFilters); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.scss deleted file mode 100644 index 5cbe8ad3d..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.scss +++ /dev/null @@ -1,30 +0,0 @@ -.AttendeeFilters--steps { - &-top { - display: flex; - flex-direction: row; - justify-content: space-between; - } - - .ant-steps-item { - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - margin-bottom: 1rem; - } - - .ant-steps-item .ant-steps-item-title { - width: 100%; - } - - .ant-steps-item .ant-steps-item-content { - width: auto; - padding-top: 4px; - margin-left: 50px; - } - - .ant-steps-item .ant-steps-item-tail { - display: none !important; - } - - &-icon { - display: block; - } -} diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Admin/AdminPage.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Admin/AdminPage.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Admin/index.js similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Admin/index.js diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/AssignAttendeesPage.module.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/AssignAttendeesPage.module.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/SearchAttendeesPage.module.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.module.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/SearchAttendeesPage.module.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/index.js similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/index.js diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/TeamsPage.module.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/TeamsPage.module.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js similarity index 82% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js index 8b461bf53..35f154a9c 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventGrants/TravelGrantStepper.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js @@ -16,19 +16,24 @@ const TravelGrantStepper = ({ registrations, filterGroups, filterGroupsLoading } useEffect(() => { if (filterGroups) { - setGroups(filterGroups.reduce((res, group) => { - res[group.label] = 0; - return res; - }, {})); + setGroups( + filterGroups.reduce((res, group) => { + res[group.label] = 0; + return res; + }, {}) + ); } }, [filterGroups]); - const handleAmountChange = useCallback((group, amount) => { - setGroups({ - ...groups, - [group]: amount, - }); - }, [groups]); + const handleAmountChange = useCallback( + (group, amount) => { + setGroups({ + ...groups, + [group]: amount + }); + }, + [groups] + ); return ( @@ -86,7 +91,14 @@ const TravelGrantStepper = ({ registrations, filterGroups, filterGroupsLoading } key: 'amount', label: 'Amount', path: 'label', - render: (label) => handleAmountChange(label, value)} /> + render: label => ( + handleAmountChange(label, value)} + /> + ) } ]} /> diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js similarity index 66% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js index 073f757a4..d5c200659 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js @@ -10,33 +10,17 @@ import PageHeader from 'components/generic/PageHeader'; import * as OrganiserSelectors from 'redux/organiser/selectors'; -import SearchAttendeesPage from './SearchAttendeesPage'; -import AssignAttendeesPage from './AssignAttendeesPage'; -import TeamsPage from './TeamsPage'; -import AdminPage from './AdminPage'; +import SearchAttendeesPage from './Search'; +import AssignAttendeesPage from './Assigned'; +import TeamsPage from './Teams'; +import AdminPage from './Admin'; +import TravelGrantsPage from './Travel'; const OrganiserEditEventReview = ({ event, organisers, registrationsLoading, updateData }) => { - const [selectedKey, setSelectedKey] = useState('search'); - - const renderSelectedKey = () => { - switch (selectedKey) { - case 'search': - return ; - case 'teams': - return ; - case 'assigned': - return ; - case 'admin': - return ; - default: - return null; - } - }; - return ( - - + Travel grants + content: }, { label: 'Admin & Tools', content: - }, + } ]} /> diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/OrganiserEditEventStats.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/Stats.module.scss similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/OrganiserEditEventStats.module.scss rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/Stats.module.scss diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/index.js similarity index 91% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/index.js index 245de7142..904243b0b 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/index.js @@ -1,18 +1,19 @@ import React, { useEffect } from 'react'; -import styles from './OrganiserEditEventStats.module.scss'; +import styles from './Stats.module.scss'; import { connect } from 'react-redux'; -import { PageHeader, Row, Col, Card } from 'antd'; +import { Row, Col, Card } from 'antd'; import * as OrganiserSelectors from 'redux/organiser/selectors'; import * as OrganiserActions from 'redux/organiser/actions'; import Divider from 'components/generic/Divider'; +import PageHeader from 'components/generic/PageHeader'; import PageWrapper from 'components/PageWrapper'; + import ApplicationsOverTime from 'components/plots/ApplicationsOverTime'; import RatingsSplit from 'components/plots/RatingsSplit'; import ReviewersList from 'components/plots/ReviewersList'; - import ApplicationsCount from 'components/plots/ApplicationsCount'; import TeamsCount from 'components/plots/TeamsCount'; import ReviewedPercent from 'components/plots/ReviewedPercent'; @@ -20,8 +21,9 @@ import ReviewedAverage from 'components/plots/ReviewedAverage'; import ApplicationsLast24h from 'components/plots/ApplicationsLast24h'; const OrganiserEditEventStats = ({ slug, loading }) => { - const renderContent = () => { - return ( + return ( + + @@ -72,12 +74,6 @@ const OrganiserEditEventStats = ({ slug, loading }) => { - ); - }; - - return ( - - Key stats for the event

} footer={renderContent()} />
); }; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js index dec5a08ec..6cc93f811 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js @@ -9,11 +9,11 @@ import * as OrganiserActions from 'redux/organiser/actions'; import PageWrapper from 'components/PageWrapper'; import Image from 'components/generic/Image'; import EventNavBar from 'components/navbars/EventNavBar'; -import OrganiserEditEventDetails from './OrganiserEditEventDetails'; -import OrganiserEditEventStats from './OrganiserEditEventStats'; -import OrganiserEditEventReview from './OrganiserEditEventReview'; -import OrganiserEditEventManage from './OrganiserEditEventManage'; -import OrganiserEditEventGrants from './OrganiserEditEventGrants'; + +import DetailsPage from './Details'; +import StatsPage from './Stats'; +import ParticipantsPage from './Participants'; +import ManagePage from './Manage'; import SidebarLayout from 'components/layouts/SidebarLayout'; const OrganiserEditEvent = ({ @@ -83,33 +83,25 @@ const OrganiserEditEvent = ({ path: '', icon: 'home', label: 'Edit', - render: routeProps => + render: routeProps => }, { path: '/stats', icon: 'line-chart', label: 'Stats', - render: routeProps => - }, - { - path: '/review', - icon: 'star', - label: 'Review', - render: routeProps => ( - - ) + render: routeProps => }, { - path: '/grants', + path: '/participants', icon: 'star', - label: 'Travel Grants', - render: routeProps => + label: 'Participants', + render: routeProps => }, { path: '/manage', icon: 'setting', label: 'Manage', - render: routeProps => + render: routeProps => } ]} /> diff --git a/frontend/src/styles/main.scss b/frontend/src/styles/main.scss index b8f41111f..70ba590f4 100644 --- a/frontend/src/styles/main.scss +++ b/frontend/src/styles/main.scss @@ -40,40 +40,9 @@ body.body { flex-direction: column; } -/* Define sensible defaults for common elements */ +/* Define some overrides */ .body { - h1, - h2, - h3, - h4, - h5, - h6 { - @extend %font-title; - } - - p, - span, - li, - td, - th { - @extend %font-body; - } - - label, - small { - @extend %font-secondary; - } - - input, - textarea { - @extend %font-input; - } - - a { - @extend %font-link; - } - .ant-timeline-item-head-custom { background: $lightgrey; } From b737cdbf9537ed3ab2ac631d30625f0f1160e485 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 15:23:50 +0300 Subject: [PATCH 21/27] travel grants stuff --- backend/common/services/sendgrid.js | 24 +++ backend/modules/email-task/controller.js | 27 +++ backend/modules/email-task/types.js | 4 +- backend/modules/registration/controller.js | 11 ++ backend/modules/registration/model.js | 9 +- backend/modules/registration/routes.js | 10 + .../Participants/Assigned/index.js | 2 +- .../Participants/Teams/index.js | 8 +- .../Participants/Travel/CalculateSpend.js | 181 ++++++++++++++++++ .../Participants/Travel/index.js | 149 ++++++-------- frontend/src/redux/organiser/selectors.js | 19 +- frontend/src/services/registrations.js | 7 + 12 files changed, 356 insertions(+), 95 deletions(-) create mode 100644 frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/CalculateSpend.js diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js index 50f9d5db9..633cf0593 100644 --- a/backend/common/services/sendgrid.js +++ b/backend/common/services/sendgrid.js @@ -64,6 +64,30 @@ const SendgridService = { return SendgridService.send(msg); }, + sendTravelGrantAcceptedEmail: (event, user, registration) => { + const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { + header_image: event.coverImage.url, + subject: `Your travel grant for ${event.name} has been confirmed`, + subtitle: `You have been granted a travel grant of up to ${registration.travelGrant}€`, + body: `This means that we will refund your travel costs to Junction 2019, up to the amount above, in exchange for a receipt of your travels. You'll be able to submit your receipt via the platform at a later date.`, + cta_text: 'Event dashboard', + cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}` + }); + + return SendgridService.send(msg); + }, + sendTravelGrantRejectedEmail: (event, user, registration) => { + const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { + header_image: event.coverImage.url, + subject: `Your travel grant for ${event.name} has been rejected`, + subtitle: `Unfortunately we were unable to give you a travel grant`, + body: ``, + cta_text: 'Event dashboard', + cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}` + }); + + return SendgridService.send(msg); + }, sendGenericEmail: (to, params) => { const msg = SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { subject: params.subject, diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js index 8080c5fe0..9519ae2f8 100644 --- a/backend/modules/email-task/controller.js +++ b/backend/modules/email-task/controller.js @@ -3,6 +3,7 @@ const SendgridService = require('../../common/services/sendgrid'); const EmailTypes = require('./types'); const EventController = require('../event/controller'); const UserController = require('../user-profile/controller'); +const RegistrationController = require('../registration/controller'); const shortid = require('shortid'); const Promise = require('bluebird'); const controller = {}; @@ -55,6 +56,22 @@ controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => return task; }; +controller.createTravelGrantAcceptedTask = async (userId, eventId, deliverNow = false) => { + const task = await controller.createTask(userId, eventId, EmailTypes.travelGrantAccepted); + if (task && deliverNow) { + return controller.deliverEmailTask(task); + } + return task; +}; + +controller.createTravelGrantRejectedTask = async (userId, eventId, deliverNow = false) => { + const task = await controller.createTask(userId, eventId, EmailTypes.travelGrantRejected); + if (task && deliverNow) { + return controller.deliverEmailTask(task); + } + return task; +}; + controller.createGenericTask = async (userId, eventId, uniqueId, msgParams, deliverNow = false) => { if (!uniqueId) { uniqueId = shortid.generate(); @@ -84,6 +101,16 @@ controller.deliverEmailTask = async task => { await SendgridService.sendRegisteredEmail(event, user); break; } + case EmailTypes.travelGrantAccepted: { + const registration = await RegistrationController.getRegistration(task.user, task.event); + await SendgridService.sendTravelGrantAcceptedEmail(event, user, registration); + break; + } + case EmailTypes.travelGrantRejected: { + const registration = await RegistrationController.getRegistration(task.user, task.event); + await SendgridService.sendTravelGrantRejectedEmail(event, user, registration); + break; + } default: { await SendgridService.sendGenericEmail(user.email, task.params); break; diff --git a/backend/modules/email-task/types.js b/backend/modules/email-task/types.js index 4f8983f62..51eb0a3ae 100644 --- a/backend/modules/email-task/types.js +++ b/backend/modules/email-task/types.js @@ -1,7 +1,9 @@ const EmailTypes = { registrationAccepted: 'registration-accepted', registrationRejected: 'registration-rejected', - registrationReceived: 'registration-received' + registrationReceived: 'registration-received', + travelGrantRejected: 'travelgrant-rejected', + travelGrantAccepted: 'travelgrant-accepted' }; module.exports = EmailTypes; diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js index ab8fc3c95..a63912a70 100644 --- a/backend/modules/registration/controller.js +++ b/backend/modules/registration/controller.js @@ -138,6 +138,17 @@ controller.bulkEditRegistrations = (eventId, registrationIds, edits) => { ); }; +controller.bulkAssignTravelGrants = (eventId, grants) => { + const updates = grants.map(({ _id, amount }) => { + return Registration.findById(_id).then(reg => { + reg.travelGrant = amount; + return reg.save(); + }); + }); + + return Promise.all(updates); +}; + controller.getFullRegistration = (eventId, registrationId) => { return Registration.findById(registrationId).then(registration => { if (!registration || registration.event.toString() !== eventId) { diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js index 647e3f185..34a32114d 100644 --- a/backend/modules/registration/model.js +++ b/backend/modules/registration/model.js @@ -72,6 +72,7 @@ RegistrationSchema.plugin(updateAllowedPlugin, { RegistrationSchema.pre('save', function(next) { this._wasNew = this.isNew; + this._previousGrant = this.travelGrant; next(); }); @@ -96,7 +97,13 @@ RegistrationSchema.post('save', function(doc, next) { EmailTaskController.createRejectedTask(doc.user, doc.event, true); } - next(); + if (!this._previousGrant && this.travelGrant === 0) { + EmailTaskController.createTravelGrantRejectedTask(doc.user, doc.event, true); + } + + if (!this._previousGrant && this.travelGrant > 0) { + EmailTaskController.createTravelGrantAcceptedTask(doc.user, doc.event, true); + } }); RegistrationSchema.index({ event: 1, user: 1 }, { unique: true }); diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js index 2fe447920..81a1794e6 100644 --- a/backend/modules/registration/routes.js +++ b/backend/modules/registration/routes.js @@ -87,6 +87,12 @@ const bulkEditRegistrations = asyncHandler(async (req, res) => { return res.status(200).json([]); }); +const bulkAssignTravelGrants = asyncHandler(async (req, res) => { + await RegistrationController.bulkAssignTravelGrants(req.event._id.toString(), req.body.grants); + + return res.status(200).json([]); +}); + const bulkAcceptRegistrations = asyncHandler(async (req, res) => { const eventId = req.event._id.toString(); const accepted = await RegistrationController.acceptSoftAccepted(eventId); @@ -130,6 +136,10 @@ router .route('/:slug/bulk') .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkEditRegistrations); +router + .route('/:slug/bulk/grants') + .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAssignTravelGrants); + router .route('/:slug/bulk/accept') .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAcceptRegistrations); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js index a4b30e9b7..2e010f8f6 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js @@ -65,7 +65,7 @@ const SearchAttendeesPage = ({ idToken, event, registrations = [], registrations Assign random registrations - r._id)} /> + {/* r._id)} /> */} diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js index e5e9e8e62..2ad347a3c 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js @@ -26,10 +26,10 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat if (!team.members || !team.members.length) return null; return (
- r._id)} buttonProps={{ text: 'Edit all team members' }} - /> + /> */}
); }} @@ -106,10 +106,10 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
{teamsFiltered.length} teams - + /> */}
{ + const [calculations, setCalculations] = useState(); + const [maxSpend, setMaxSpend] = useState(0); + const { slug } = event; + + const makeCalculations = useCallback(() => { + const groupsSorted = sortBy(filterGroups, group => amountsByGroup[group.label] * -1); + const registrationsMapped = groupsSorted.reduce( + ({ registrations, res }, group) => { + const filtered = FilterHelpers.applyFilters(registrations, group.filters); + const amount = parseInt(amountsByGroup[group.label]); + + if (amount === 0) { + return { registrations, res }; + } + + return { + res: res.concat( + filtered.map(reg => ({ + id: reg._id, + group: group.label, + amount, + createdAt: reg.createdAt + })) + ), + registrations: difference(registrations, filtered) + }; + }, + { + registrations: eligibleRegistrations, + res: [] + } + ).res; + const registrationsSorted = sortBy(registrationsMapped, 'createdAt'); + + const registrationsGranted = []; + let currentSpend = 0; + for (let item of registrationsSorted) { + if (currentSpend + item.amount <= maxSpend) { + currentSpend += item.amount; + registrationsGranted.push(item); + } + } + + const byGroup = filterGroups.map(group => { + const amount = amountsByGroup[group.label]; + const granted = registrationsGranted.filter(r => r.group === group.label); + const total = registrationsSorted.filter(r => r.group === group.label); + + return { + group: group.label, + amount, + registrationsGranted: granted, + registrationsTotal: total, + totalSpend: sumBy(granted, 'amount') + }; + }); + + const byGroupSorted = sortBy(byGroup, 'totalSpend'); + + setCalculations({ + granted: registrationsGranted, + total: registrationsSorted, + spend: currentSpend, + byGroup: byGroupSorted + }); + }, [filterGroups, eligibleRegistrations, maxSpend, amountsByGroup]); + + const handleSubmit = useCallback(() => { + const data = calculations.granted.map(item => ({ + _id: item.id, + amount: item.amount + })); + + window.alert('Start submission'); + + RegistrationsService.bulkAssignTravelGrantsForEvent(idToken, slug, data) + .then(() => { + window.alert('DONE!'); + }) + .catch(err => { + window.alert('ERR'); + console.log(err); + }); + }, [idToken, slug, calculations]); + + const canSubmit = calculations && calculations.granted.length > 0; + + return ( + + + + + + + + + + + {calculations && ( + + + + This would result in a total spend of {calculations.spend} and give travel grants to{' '} + {calculations.granted.length} participants + + +
+ `${granted.length}/${registrationsTotal.length}` + }, + { + key: 'spend', + label: 'Spend (EUR)', + path: 'totalSpend' + } + ]} + /> + + )} + + + + + + + + ); +}; + +const mapState = state => ({ + idToken: AuthSelectors.getIdToken(state), + event: OrganiserSelectors.event(state), + filterGroups: OrganiserSelectors.filterGroups(state), + eligibleRegistrations: OrganiserSelectors.registrations(state) +}); + +export default connect(mapState)(CalculateSpend); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js index 35f154a9c..7b43421b7 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js @@ -1,17 +1,15 @@ import React, { useState, useEffect, useCallback } from 'react'; import { connect } from 'react-redux'; -import { Typography, Box, Grid } from '@material-ui/core'; -import { sumBy, filter } from 'lodash-es'; -import Stepper from 'components/generic/Stepper'; -import Statistic from 'components/generic/Statistic'; +import { Typography, Grid } from '@material-ui/core'; import Table from 'components/generic/Table'; import TextInput from 'components/inputs/TextInput'; import * as OrganiserSelectors from 'redux/organiser/selectors'; -const TravelGrantStepper = ({ registrations, filterGroups, filterGroupsLoading }) => { - const [activeStep, setActiveStep] = useState(0); +import CalculateSpend from './CalculateSpend'; + +const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) => { const [groups, setGroups] = useState({}); useEffect(() => { @@ -36,87 +34,64 @@ const TravelGrantStepper = ({ registrations, filterGroups, filterGroupsLoading } ); return ( - - - - - - - - !!r.travelGrant).length} - suffix={`/${registrations.length}`} - /> - - - - - - - - - This is a tool for automatically assigning travel grants to participants. Choose which participants - should be eligible for how much, and the tool will automatically assign travel grants to - participants in the order they registered. + + + + Here you can automatically grant travel grants based on your filter groups. Set the amount you want + to grant for each group (or 0 to skip that group), and the tool will go through confirmed + participants in order of registration time, and assign them their respective travel grant amounts as + long as the budget is not exceeded. +
+
+ If a participant belongs to more than one of your filter groups, they will be granted the travel + grant with the highest amount. If you want a group to be guaranteed to receive a travel grant, + regardless of how late they've registered, you can set all other groups' amounts to 0 and your + budget high enough to fit everyone in the group. +
+
+ You can also assign travel grant amounts individually, by editing a given participant on the + Participants -tab. +
+
+ +
( + handleAmountChange(label, value)} + /> + ) + } + ]} + /> + + + + Set budget and preview - - window.alert('HELLO')} - steps={[ - { - key: 'set-groups', - label: 'Configure the size of travel grant per group', - render: () => ( -
( - handleAmountChange(label, value)} - /> - ) - } - ]} - /> - ) - }, - { - key: 'preview-spend', - label: 'Preview spend', - render: () =>

Step 2

- }, - { - key: 'assign-grants', - label: 'Assign grants', - render: () =>

Step 3

- } - ]} - /> - + + + + + ); }; @@ -126,4 +101,4 @@ const mapState = state => ({ filterGroupsLoading: OrganiserSelectors.filterGroupsLoading(state) }); -export default connect(mapState)(TravelGrantStepper); +export default connect(mapState)(TravelGrantPage); diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 4bd219858..1b39b642b 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -65,6 +65,23 @@ export const registrationsConfirmed = createSelector( } ); +export const registrationsEligibleForTravelGrant = createSelector( + registrationsConfirmed, + registrations => + registrations.filter(r => { + return !r.travelGrant && r.travelGrant !== 0 && r.answers.needsTravelGrant; + }) +); + +export const travelGrantSpend = createSelector( + registrationsConfirmed, + registrations => { + return sumBy(registrations, r => { + return r.travelGrant || 0; + }); + } +); + export const teamsPopulated = createSelector( registrationsMap, teams, @@ -162,4 +179,4 @@ export const reviewAverageByReviewer = createSelector( return meanBy(registrations, 'rating'); }); } -); \ No newline at end of file +); diff --git a/frontend/src/services/registrations.js b/frontend/src/services/registrations.js index 16297882c..2b5b839c9 100644 --- a/frontend/src/services/registrations.js +++ b/frontend/src/services/registrations.js @@ -68,6 +68,13 @@ RegistrationsService.bulkEditRegistrationsForEvent = (idToken, slug, registratio return _axios.patch(`${BASE_ROUTE}/${slug}/bulk`, { registrationIds, edits }, config(idToken)); }; +/** Assign travel grant amounts in bulk + * PATCH /:slug/bulk/grants + */ +RegistrationsService.bulkAssignTravelGrantsForEvent = (idToken, slug, grants) => { + return _axios.patch(`${BASE_ROUTE}/${slug}/bulk/grants`, { grants }, config(idToken)); +}; + /** Accept all soft-accepted registrations * PATCH /:slug/bulk/accept */ From 1c69e2a4dc1d0111c4440a3f87fe50c144acb0e6 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 15:26:26 +0300 Subject: [PATCH 22/27] disable travel grant emails --- backend/modules/registration/model.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js index 34a32114d..4da97b52d 100644 --- a/backend/modules/registration/model.js +++ b/backend/modules/registration/model.js @@ -97,13 +97,13 @@ RegistrationSchema.post('save', function(doc, next) { EmailTaskController.createRejectedTask(doc.user, doc.event, true); } - if (!this._previousGrant && this.travelGrant === 0) { - EmailTaskController.createTravelGrantRejectedTask(doc.user, doc.event, true); - } + // if (!this._previousGrant && this.travelGrant === 0) { + // EmailTaskController.createTravelGrantRejectedTask(doc.user, doc.event, true); + // } - if (!this._previousGrant && this.travelGrant > 0) { - EmailTaskController.createTravelGrantAcceptedTask(doc.user, doc.event, true); - } + // if (!this._previousGrant && this.travelGrant > 0) { + // EmailTaskController.createTravelGrantAcceptedTask(doc.user, doc.event, true); + // } }); RegistrationSchema.index({ event: 1, user: 1 }, { unique: true }); From 1eae1f25583f4a316f3b8a57c4a4bbd5ff834de6 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 15:28:40 +0300 Subject: [PATCH 23/27] re-install babel-plugin-import --- frontend/package-lock.json | 1 - frontend/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4ab27b09e..1efb63953 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2758,7 +2758,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.12.2.tgz", "integrity": "sha512-Vz9s+I6vAnsY8sYczU/cdtkKAHSorapa/2St6K+OzowplKizpWxul4HLi3kj1eRmHMFjhbROSMGXP+mFna2nUw==", - "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/runtime": "^7.0.0" diff --git a/frontend/package.json b/frontend/package.json index f6649d901..fd5884b83 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "antd": "^3.18.2", "auth0-js": "^9.10.0", "axios": "^0.18.0", + "babel-plugin-import": "^1.12.2", "classnames": "^2.2.6", "cloudinary-react": "^1.1.1", "connected-react-router": "^6.3.0", @@ -70,7 +71,6 @@ "npm": "6.9.0" }, "devDependencies": { - "babel-plugin-import": "^1.12.2", "depcheck": "^0.8.3", "sync-dotenv": "^2.2.1" } From cc5adc2a6da991279f915054f69aba61bf56fa57 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 21:05:00 +0300 Subject: [PATCH 24/27] Finalize travel grant feature --- backend/common/services/sendgrid.js | 49 ++++++++++-- backend/modules/email-task/controller.js | 17 ++-- backend/modules/registration/controller.js | 20 +++++ backend/modules/registration/model.js | 22 ++++-- backend/modules/registration/routes.js | 9 ++- .../src/components/generic/Statistic/index.js | 21 ++++- .../TravelGrantStatusBlock.js | 73 +++++++++++++----- .../Participants/Travel/CalculateSpend.js | 17 ++-- .../Participants/Travel/index.js | 77 +++++++++++++++++-- frontend/src/redux/organiser/actions.js | 1 - frontend/src/redux/organiser/selectors.js | 20 ++++- frontend/src/services/registrations.js | 4 + frontend/src/services/travelGrants.js | 30 -------- shared/constants/registration-fields.js | 10 ++- 14 files changed, 275 insertions(+), 95 deletions(-) delete mode 100644 frontend/src/services/travelGrants.js diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js index 633cf0593..22822ae1d 100644 --- a/backend/common/services/sendgrid.js +++ b/backend/common/services/sendgrid.js @@ -64,24 +64,59 @@ const SendgridService = { return SendgridService.send(msg); }, - sendTravelGrantAcceptedEmail: (event, user, registration) => { + sendTravelGrantAcceptedEmail: (event, user, params) => { const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { header_image: event.coverImage.url, subject: `Your travel grant for ${event.name} has been confirmed`, - subtitle: `You have been granted a travel grant of up to ${registration.travelGrant}€`, - body: `This means that we will refund your travel costs to Junction 2019, up to the amount above, in exchange for a receipt of your travels. You'll be able to submit your receipt via the platform at a later date.`, + subtitle: `You have been granted a travel grant of up to ${params.amount}€`, + body: `This means that we will refund your travel costs to ${event.name}, up to the amount above. Please note that the following conditions apply: +
    +
  • + The travel grant is valid for travel from ${params.countryOfTravel} to Junction 2019. If you are travelling from somewhere else, Junction reserves the + right to change your travel grant class and/or amount. +
  • +
  • + Travel grants are only available to participants who have checked in at the venue. +
  • +
  • + You will need to supply receipt(s) of your travels, which clearly show the total cost of your trip, per traveller. +
  • +
+ + You will be able to submit your receipts and other travel grant details via the registration platform + once you have checked in to the event. See you soon! + `, cta_text: 'Event dashboard', cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}` }); return SendgridService.send(msg); }, - sendTravelGrantRejectedEmail: (event, user, registration) => { + sendTravelGrantRejectedEmail: (event, user) => { const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { header_image: event.coverImage.url, - subject: `Your travel grant for ${event.name} has been rejected`, - subtitle: `Unfortunately we were unable to give you a travel grant`, - body: ``, + subject: `Your travel grant status for ${event.name}`, + subtitle: `Unfortunately we we're unable to give you a travel grant this time...`, + body: ` + We would have loved to give everyone a travel grant, but unfortunately we have a limited budget and you + didn't quite make the cut this time. +
+
+ Don't worry, it's nothing personal – we want to give out travel + grants as evenly as possible to our participants, and thus we divided the travel grant applicants to + geographical areas by the country of travel – these are called travel grant classes. We then gave out + the travel grants for confirmed participants in order of registration time within that travel grant class. + This time there were more people applying for travel grants in your travel grant class than we had budget + for and the travel grants were given to those who applied to ${event.name} before you. +
+
+ Hopefully you can still make it to ${event.name} despite this - it's going to be awesome! +
+
+ If you won't be able to travel to the event due to not receiving a travel grant, or won't be able to make + it for some other reason, please be so kind and cancel your registration via the Event Dashboard so we can + accept someone from the waitlist. + `, cta_text: 'Event dashboard', cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}` }); diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js index 9519ae2f8..582d78b1f 100644 --- a/backend/modules/email-task/controller.js +++ b/backend/modules/email-task/controller.js @@ -56,16 +56,19 @@ controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => return task; }; -controller.createTravelGrantAcceptedTask = async (userId, eventId, deliverNow = false) => { - const task = await controller.createTask(userId, eventId, EmailTypes.travelGrantAccepted); +controller.createTravelGrantAcceptedTask = async (registration, deliverNow = false) => { + const task = await controller.createTask(registration.user, registration.event, EmailTypes.travelGrantAccepted, { + amount: registration.travelGrant, + countryOfTravel: registration.answers.countryOfTravel + }); if (task && deliverNow) { return controller.deliverEmailTask(task); } return task; }; -controller.createTravelGrantRejectedTask = async (userId, eventId, deliverNow = false) => { - const task = await controller.createTask(userId, eventId, EmailTypes.travelGrantRejected); +controller.createTravelGrantRejectedTask = async (registration, deliverNow = false) => { + const task = await controller.createTask(registration.user, registration.event, EmailTypes.travelGrantRejected); if (task && deliverNow) { return controller.deliverEmailTask(task); } @@ -102,13 +105,11 @@ controller.deliverEmailTask = async task => { break; } case EmailTypes.travelGrantAccepted: { - const registration = await RegistrationController.getRegistration(task.user, task.event); - await SendgridService.sendTravelGrantAcceptedEmail(event, user, registration); + await SendgridService.sendTravelGrantAcceptedEmail(event, user, task.params); break; } case EmailTypes.travelGrantRejected: { - const registration = await RegistrationController.getRegistration(task.user, task.event); - await SendgridService.sendTravelGrantRejectedEmail(event, user, registration); + await SendgridService.sendTravelGrantRejectedEmail(event, user, task.params); break; } default: { diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js index a63912a70..68c4ea9c5 100644 --- a/backend/modules/registration/controller.js +++ b/backend/modules/registration/controller.js @@ -149,6 +149,26 @@ controller.bulkAssignTravelGrants = (eventId, grants) => { return Promise.all(updates); }; +controller.rejectPendingTravelGrants = eventId => { + return Registration.find({ + event: eventId, + status: { + $in: ['confirmed', 'checkedIn'] + }, + travelGrant: { + $exists: false + }, + 'answers.needsTravelGrant': true + }).then(registrations => { + const promises = registrations.map(registration => { + registration.travelGrant = 0; + return registration.save(); + }); + + return Promise.all(promises); + }); +}; + controller.getFullRegistration = (eventId, registrationId) => { return Registration.findById(registrationId).then(registration => { if (!registration || registration.event.toString() !== eventId) { diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js index 4da97b52d..559cbe727 100644 --- a/backend/modules/registration/model.js +++ b/backend/modules/registration/model.js @@ -42,7 +42,11 @@ const RegistrationSchema = new mongoose.Schema({ default: {} }, travelGrant: { - type: Number + type: Number, + set: function(amount) { + this._previousGrant = this.travelGrant; + return amount; + } } }); @@ -72,12 +76,12 @@ RegistrationSchema.plugin(updateAllowedPlugin, { RegistrationSchema.pre('save', function(next) { this._wasNew = this.isNew; - this._previousGrant = this.travelGrant; next(); }); /** Trigger email sending on status changes etc. */ RegistrationSchema.post('save', function(doc, next) { + console.log('POST SAVE', doc._id); const SOFT_ACCEPTED = RegistrationStatuses.asObject.softAccepted.id; const ACCEPTED = RegistrationStatuses.asObject.accepted.id; const SOFT_REJECTED = RegistrationStatuses.asObject.softRejected.id; @@ -97,13 +101,15 @@ RegistrationSchema.post('save', function(doc, next) { EmailTaskController.createRejectedTask(doc.user, doc.event, true); } - // if (!this._previousGrant && this.travelGrant === 0) { - // EmailTaskController.createTravelGrantRejectedTask(doc.user, doc.event, true); - // } + if (!this._previousGrant && this.travelGrant === 0) { + EmailTaskController.createTravelGrantRejectedTask(doc, true); + } + + if (!this._previousGrant && this.travelGrant > 0) { + EmailTaskController.createTravelGrantAcceptedTask(doc, true); + } - // if (!this._previousGrant && this.travelGrant > 0) { - // EmailTaskController.createTravelGrantAcceptedTask(doc.user, doc.event, true); - // } + next(); }); RegistrationSchema.index({ event: 1, user: 1 }, { unique: true }); diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js index 81a1794e6..bbec861e0 100644 --- a/backend/modules/registration/routes.js +++ b/backend/modules/registration/routes.js @@ -93,6 +93,12 @@ const bulkAssignTravelGrants = asyncHandler(async (req, res) => { return res.status(200).json([]); }); +const bulkRejectTravelGrants = asyncHandler(async (req, res) => { + await RegistrationController.rejectPendingTravelGrants(req.event._id.toString()); + + return res.status(200).json([]); +}); + const bulkAcceptRegistrations = asyncHandler(async (req, res) => { const eventId = req.event._id.toString(); const accepted = await RegistrationController.acceptSoftAccepted(eventId); @@ -138,7 +144,8 @@ router router .route('/:slug/bulk/grants') - .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAssignTravelGrants); + .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAssignTravelGrants) + .delete(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkRejectTravelGrants); router .route('/:slug/bulk/accept') diff --git a/frontend/src/components/generic/Statistic/index.js b/frontend/src/components/generic/Statistic/index.js index 6580d756e..7e928ec5a 100644 --- a/frontend/src/components/generic/Statistic/index.js +++ b/frontend/src/components/generic/Statistic/index.js @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Card, CardContent, Typography } from '@material-ui/core'; +import { Card, CardContent, Typography, Button, Box, CircularProgress } from '@material-ui/core'; const useStyles = makeStyles(theme => ({ value: { @@ -13,7 +13,14 @@ const useStyles = makeStyles(theme => ({ } })); -const Statistic = ({ label, value, suffix }) => { +const Statistic = ({ label, value, suffix, action, actionText }) => { + const [actionLoading, setActionLoading] = useState(); + + const handleAction = useCallback(async () => { + setActionLoading(true); + await action(); + setActionLoading(false); + }, [action]); const classes = useStyles(); return ( @@ -29,6 +36,14 @@ const Statistic = ({ label, value, suffix }) => { )} + {action && actionText && ( + + {actionLoading && } + + + )} ); diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js index 2f3e67a62..781800fad 100644 --- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js +++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js @@ -31,28 +31,59 @@ const TravelGrantStatusBlock = ({ event, registration }) => { } if (registration.status === STATUSES.confirmed.id) { - return ( -
- - - Please consult the{' '} - - FAQ section - {' '} - of our website for details on the travel grant amounts available for the country you're - travelling from. -

- } - /> - - ); + if (registration.travelGrant === 0) { + return ( +
+ + + + ); + } + + if (!registration.travelGrant) { + return ( + + + + Please consult the{' '} + + FAQ section + {' '} + of our website for details on the travel grant amounts available for the country you're + travelling from. +

+ } + /> + + ); + } + + if (registration.travelGrant > 0) { + return ( +
+ + + + ); + } } + return null; }; diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/CalculateSpend.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/CalculateSpend.js index 18372a9ce..fe7a7ca32 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/CalculateSpend.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/CalculateSpend.js @@ -4,6 +4,7 @@ import { FilterHelpers } from '@hackjunction/shared'; import { connect } from 'react-redux'; import { sortBy, sumBy, difference } from 'lodash-es'; import { Grid, Typography, Button, Box } from '@material-ui/core'; +import { withSnackbar } from 'notistack'; import TextField from 'components/inputs/TextInput'; import Table from 'components/generic/Table'; @@ -11,7 +12,7 @@ import * as OrganiserSelectors from 'redux/organiser/selectors'; import * as AuthSelectors from 'redux/auth/selectors'; import RegistrationsService from 'services/registrations'; -const CalculateSpend = ({ idToken, event, amountsByGroup, filterGroups, eligibleRegistrations }) => { +const CalculateSpend = ({ idToken, event, amountsByGroup, filterGroups, eligibleRegistrations, enqueueSnackbar }) => { const [calculations, setCalculations] = useState(); const [maxSpend, setMaxSpend] = useState(0); const { slug } = event; @@ -85,17 +86,15 @@ const CalculateSpend = ({ idToken, event, amountsByGroup, filterGroups, eligible amount: item.amount })); - window.alert('Start submission'); - RegistrationsService.bulkAssignTravelGrantsForEvent(idToken, slug, data) .then(() => { - window.alert('DONE!'); + enqueueSnackbar('Success!', { variant: 'success' }); }) .catch(err => { - window.alert('ERR'); + enqueueSnackbar('Something went wrong...', { variant: 'error' }); console.log(err); }); - }, [idToken, slug, calculations]); + }, [idToken, slug, calculations, enqueueSnackbar]); const canSubmit = calculations && calculations.granted.length > 0; @@ -162,7 +161,7 @@ const CalculateSpend = ({ idToken, event, amountsByGroup, filterGroups, eligible - @@ -175,7 +174,7 @@ const mapState = state => ({ idToken: AuthSelectors.getIdToken(state), event: OrganiserSelectors.event(state), filterGroups: OrganiserSelectors.filterGroups(state), - eligibleRegistrations: OrganiserSelectors.registrations(state) + eligibleRegistrations: OrganiserSelectors.registrationsEligibleForTravelGrant(state) }); -export default connect(mapState)(CalculateSpend); +export default withSnackbar(connect(mapState)(CalculateSpend)); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js index 7b43421b7..e78dbc207 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js @@ -1,15 +1,34 @@ import React, { useState, useEffect, useCallback } from 'react'; +import { sumBy } from 'lodash-es'; import { connect } from 'react-redux'; import { Typography, Grid } from '@material-ui/core'; +import { FilterHelpers } from '@hackjunction/shared'; +import { withSnackbar } from 'notistack'; + import Table from 'components/generic/Table'; import TextInput from 'components/inputs/TextInput'; +import Statistic from 'components/generic/Statistic'; + +import RegistrationsService from 'services/registrations'; +import * as AuthSelectors from 'redux/auth/selectors'; import * as OrganiserSelectors from 'redux/organiser/selectors'; import CalculateSpend from './CalculateSpend'; -const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) => { +const TravelGrantPage = ({ + enqueueSnackbar, + event, + idToken, + registrations, + registrationsWithTravelGrant, + filterGroups, + filterGroupsLoading, + travelGrantSpend, + travelGrantCount, + travelGrantRejectedCount +}) => { const [groups, setGroups] = useState({}); useEffect(() => { @@ -33,8 +52,45 @@ const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) = [groups] ); + const filterGroupsMapped = filterGroups.map(group => { + const items = FilterHelpers.applyFilters(registrationsWithTravelGrant, group.filters); + return { + ...group, + spend: sumBy(items, r => r.travelGrant || 0) + }; + }); + + const handleBulkReject = useCallback(() => { + return RegistrationsService.bulkRejectTravelGrantsForEvent(idToken, event.slug) + .then(() => { + enqueueSnackbar('Success!', { variant: 'success' }); + return; + }) + .catch(err => { + enqueueSnackbar('Something went wrong', { variant: 'error' }); + return; + }); + }, [idToken, event.slug, enqueueSnackbar]); + return ( + + + + + + + + + + + + Here you can automatically grant travel grants based on your filter groups. Set the amount you want @@ -59,7 +115,7 @@ const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) = rowSelection={false} rowNumber={false} pagination={false} - dataSource={filterGroups} + dataSource={filterGroupsMapped} loading={filterGroupsLoading} columns={[ { @@ -67,6 +123,11 @@ const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) = label: 'Group', path: 'label' }, + { + key: 'spend', + label: 'Current spend', + path: 'spend' + }, { key: 'amount', label: 'Amount', @@ -96,9 +157,15 @@ const TravelGrantPage = ({ registrations, filterGroups, filterGroupsLoading }) = }; const mapState = state => ({ - registrations: OrganiserSelectors.registrationsConfirmed(state), + idToken: AuthSelectors.getIdToken(state), + event: OrganiserSelectors.event(state), + registrations: OrganiserSelectors.registrationsEligibleForTravelGrant(state), + registrationsWithTravelGrant: OrganiserSelectors.registrationsWithTravelGrant(state), filterGroups: OrganiserSelectors.filterGroups(state), - filterGroupsLoading: OrganiserSelectors.filterGroupsLoading(state) + filterGroupsLoading: OrganiserSelectors.filterGroupsLoading(state), + travelGrantSpend: OrganiserSelectors.travelGrantSpend(state), + travelGrantCount: OrganiserSelectors.travelGrantCount(state), + travelGrantRejectedCount: OrganiserSelectors.travelGrantRejectedCount(state) }); -export default connect(mapState)(TravelGrantPage); +export default withSnackbar(connect(mapState)(TravelGrantPage)); diff --git a/frontend/src/redux/organiser/actions.js b/frontend/src/redux/organiser/actions.js index 8beb3cb27..025287b7c 100644 --- a/frontend/src/redux/organiser/actions.js +++ b/frontend/src/redux/organiser/actions.js @@ -4,7 +4,6 @@ import UserProfilesService from 'services/userProfiles'; import EventsService from 'services/events'; import RegistrationsService from 'services/registrations'; import TeamsService from 'services/teams'; -import TravelGrantsService from 'services/travelGrants'; import FilterGroupsService from 'services/filterGroups'; /** Update event with loading/error data */ diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 1b39b642b..973431389 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -73,8 +73,16 @@ export const registrationsEligibleForTravelGrant = createSelector( }) ); -export const travelGrantSpend = createSelector( +export const registrationsWithTravelGrant = createSelector( registrationsConfirmed, + registrations => + registrations.filter(r => { + return r.travelGrant && r.travelGrant !== 0; + }) +); + +export const travelGrantSpend = createSelector( + registrationsWithTravelGrant, registrations => { return sumBy(registrations, r => { return r.travelGrant || 0; @@ -82,6 +90,16 @@ export const travelGrantSpend = createSelector( } ); +export const travelGrantCount = createSelector( + registrationsWithTravelGrant, + registrations => registrations.length +); + +export const travelGrantRejectedCount = createSelector( + registrationsConfirmed, + registrations => registrations.filter(r => r.travelGrant === 0).length +); + export const teamsPopulated = createSelector( registrationsMap, teams, diff --git a/frontend/src/services/registrations.js b/frontend/src/services/registrations.js index 2b5b839c9..c63e32f48 100644 --- a/frontend/src/services/registrations.js +++ b/frontend/src/services/registrations.js @@ -75,6 +75,10 @@ RegistrationsService.bulkAssignTravelGrantsForEvent = (idToken, slug, grants) => return _axios.patch(`${BASE_ROUTE}/${slug}/bulk/grants`, { grants }, config(idToken)); }; +RegistrationsService.bulkRejectTravelGrantsForEvent = (idToken, slug) => { + return _axios.delete(`${BASE_ROUTE}/${slug}/bulk/grants`, config(idToken)); +}; + /** Accept all soft-accepted registrations * PATCH /:slug/bulk/accept */ diff --git a/frontend/src/services/travelGrants.js b/frontend/src/services/travelGrants.js deleted file mode 100644 index 3a8b1320d..000000000 --- a/frontend/src/services/travelGrants.js +++ /dev/null @@ -1,30 +0,0 @@ -import _axios from 'services/axios'; - -const TravelGrantsService = {}; - -function config(idToken) { - return { - headers: { - Authorization: `Bearer ${idToken}` - } - }; -} - -TravelGrantsService.getTravelGrantsForEvent = (idToken, eventSlug) => { - return _axios.get(`/travel-grants/${eventSlug}/all`, config(idToken)); -}; - -TravelGrantsService.getTravelGrantForUser = (idToken, eventSlug) => { - return _axios.get(`/travel-grants/${eventSlug}`, config(idToken)); -}; - -TravelGrantsService.createTravelGrantForUser = (idToken, eventSlug, sum, travelsFrom, userId) => { - const data = { - sum, - travelsFrom, - userId - }; - return _axios.post(`/travel-grants/${eventSlug}`, data, config(idToken)); -}; - -export default TravelGrantsService; diff --git a/shared/constants/registration-fields.js b/shared/constants/registration-fields.js index cb284329d..9b1610e32 100644 --- a/shared/constants/registration-fields.js +++ b/shared/constants/registration-fields.js @@ -532,7 +532,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'City of Travel', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, needsVisa: { label: 'Do you need a visa?', From 3cd1bd629668741e860329b1aefa43e0f219a976 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 21:48:39 +0300 Subject: [PATCH 25/27] Fix buggy custom field validation --- backend/common/services/sendgrid.js | 2 +- backend/modules/registration/model.js | 1 - .../RegistrationFieldCustom/index.js | 15 +++++++++++++-- shared/helpers/customValidator.js | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js index 22822ae1d..2b01ed48c 100644 --- a/backend/common/services/sendgrid.js +++ b/backend/common/services/sendgrid.js @@ -78,7 +78,7 @@ const SendgridService = {
  • Travel grants are only available to participants who have checked in at the venue.
  • -
  • +
  • You will need to supply receipt(s) of your travels, which clearly show the total cost of your trip, per traveller.
  • diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js index 559cbe727..adf22c3e2 100644 --- a/backend/modules/registration/model.js +++ b/backend/modules/registration/model.js @@ -81,7 +81,6 @@ RegistrationSchema.pre('save', function(next) { /** Trigger email sending on status changes etc. */ RegistrationSchema.post('save', function(doc, next) { - console.log('POST SAVE', doc._id); const SOFT_ACCEPTED = RegistrationStatuses.asObject.softAccepted.id; const ACCEPTED = RegistrationStatuses.asObject.accepted.id; const SOFT_REJECTED = RegistrationStatuses.asObject.softRejected.id; diff --git a/frontend/src/components/FormComponents/RegistrationFieldCustom/index.js b/frontend/src/components/FormComponents/RegistrationFieldCustom/index.js index df0c3557d..cfbcd9e2e 100644 --- a/frontend/src/components/FormComponents/RegistrationFieldCustom/index.js +++ b/frontend/src/components/FormComponents/RegistrationFieldCustom/index.js @@ -1,9 +1,13 @@ import React from 'react'; +import joi from 'joi-browser'; import { Input, Select } from 'antd'; - +import { RegistrationFields, CustomValidator } from '@hackjunction/shared'; import FormikField from '../FormikField'; +const { fieldTypes } = RegistrationFields; +const Validator = new CustomValidator(joi, false); + const RegistrationFieldCustom = React.memo(({ section, question }) => { const name = `${section.name}.${question.name}`; const renderInputForField = ({ field, form }) => { @@ -62,6 +66,13 @@ const RegistrationFieldCustom = React.memo(({ section, question }) => { return ''; }; + const validatorOptions = { + fieldType: question.fieldType, + fieldLabel: question.label, + required: question.fieldRequired, + fieldOptions: question.settings + }; + return ( { hintMarkdown={true} isFast={true} required={question.fieldRequired} - validate={() => {}} + validate={Validator.validate(validatorOptions)} alwaysFocused={false} render={renderInputForField} renderValue={renderValueForField} diff --git a/shared/helpers/customValidator.js b/shared/helpers/customValidator.js index 0b022b58e..09fd1ef70 100644 --- a/shared/helpers/customValidator.js +++ b/shared/helpers/customValidator.js @@ -15,7 +15,7 @@ class CustomValidator extends BaseValidator { return this.joi .string() .min(fieldOptions.minLength || 0) - .max(fieldOptions.maxLength || 100) + .max(fieldOptions.maxLength || 10000) .allow(...allowArgs) .label(fieldLabel); } @@ -25,7 +25,7 @@ class CustomValidator extends BaseValidator { return this.joi .string() .min(fieldOptions.minLength || 0) - .max(fieldOptions.maxLength || 1000) + .max(fieldOptions.maxLength || 10000) .allow(...allowArgs) .label(fieldLabel); } From f3bc68e4007cb3eb986abe901af969548f4621ee Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Tue, 1 Oct 2019 21:52:47 +0300 Subject: [PATCH 26/27] Final wording of travel grant email --- backend/common/services/sendgrid.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js index 2b01ed48c..b20ab27c1 100644 --- a/backend/common/services/sendgrid.js +++ b/backend/common/services/sendgrid.js @@ -64,27 +64,32 @@ const SendgridService = { return SendgridService.send(msg); }, + sendTravelGrantAcceptedEmail: (event, user, params) => { const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { header_image: event.coverImage.url, subject: `Your travel grant for ${event.name} has been confirmed`, subtitle: `You have been granted a travel grant of up to ${params.amount}€`, - body: `This means that we will refund your travel costs to ${event.name}, up to the amount above. Please note that the following conditions apply: + body: `This means that we will assist you with your travel costs to Junction 2019, up to the amount above. Please note that the following conditions apply:
    • - The travel grant is valid for travel from ${params.countryOfTravel} to Junction 2019. If you are travelling from somewhere else, Junction reserves the - right to change your travel grant class and/or amount. + The travel grant is valid for travel from ${params.countryOfTravel} to ${event.name}. If you are travelling from somewhere else, + Junction reserves the right to change your travel grant class and/or amount.
    • Travel grants are only available to participants who have checked in at the venue.
    • - You will need to supply receipt(s) of your travels, which clearly show the total cost of your trip, per traveller. + You will need to supply receipt(s) of your travel to ${event.name}, which clearly shows the total cost of your trip, per traveller. You may provide additional details in the travel receipt file. +
    • +
    • + You have to submit the travel receipt file and additional required information before 24th November 23:59 Finnish Time.
    - You will be able to submit your receipts and other travel grant details via the registration platform - once you have checked in to the event. See you soon! + You will be able to submit your receipts and other information required for receiving the travel grant via the registration platform once you have checked in to the event. See you soon! + + Psst, please note that the transaction will be made in Euros, so please make sure you have a bank account able to receive Euro payments available. `, cta_text: 'Event dashboard', cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}` From 5991c570fb31905bb608c8b241433bfb8ef5f859 Mon Sep 17 00:00:00 2001 From: Juuso Lappalainen Date: Wed, 2 Oct 2019 10:47:46 +0300 Subject: [PATCH 27/27] Fix travel grant filtering issue --- backend/modules/registration/controller.js | 31 +++++++++++++--------- shared/constants/registration-fields.js | 1 + shared/index.js | 1 + 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js index 68c4ea9c5..65ae0606b 100644 --- a/backend/modules/registration/controller.js +++ b/backend/modules/registration/controller.js @@ -1,6 +1,6 @@ const _ = require('lodash'); const Promise = require('bluebird'); -const { RegistrationStatuses } = require('@hackjunction/shared'); +const { RegistrationStatuses, RegistrationFields, FieldTypes } = require('@hackjunction/shared'); const Registration = require('./model'); const { NotFoundError, ForbiddenError } = require('../../common/errors/errors'); const UserProfileController = require('../user-profile/controller'); @@ -74,19 +74,26 @@ controller.getRegistrationsForEvent = eventId => { }).then(registrations => { /** Do some minor optimisation here to cut down on size */ return registrations.map(reg => { - reg.answers = _.mapValues(reg.answers, answer => { - if (typeof answer === 'string' && answer.length > 50) { - return answer.slice(0, 10) + '...'; - } - if (typeof answer === 'object' && !Array.isArray(answer) && Object.keys(answer).length > 0) { - return _.mapValues(answer, subAnswer => { - if (typeof subAnswer === 'string' && subAnswer.length > 50) { - return subAnswer.slice(0, 10); + reg.answers = _.mapValues(reg.answers, (answer, field) => { + const fieldType = RegistrationFields.getFieldType(field); + switch (fieldType) { + case FieldTypes.LONG_TEXT.id: + if (answer && answer.length > 10) { + return answer.slice(0, 10) + '...'; + } + return answer; + default: { + if (typeof answer === 'object' && !Array.isArray(answer) && Object.keys(answer).length > 0) { + return _.mapValues(answer, subAnswer => { + if (typeof subAnswer === 'string' && subAnswer.length > 50) { + return subAnswer.slice(0, 10); + } + return subAnswer; + }); } - return subAnswer; - }); + return answer; + } } - return answer; }); return reg; }); diff --git a/shared/constants/registration-fields.js b/shared/constants/registration-fields.js index 9b1610e32..051f5a239 100644 --- a/shared/constants/registration-fields.js +++ b/shared/constants/registration-fields.js @@ -1118,6 +1118,7 @@ const Helpers = { }, getFields: () => Fields, getField: field => Fields[field], + getFieldType: field => (Fields[field] ? Fields[field].fieldType.id : null), filters: buildFiltersArray(), fieldToLabelMap: buildFieldToLabelMap(), fieldTypes: FieldTypes, diff --git a/shared/index.js b/shared/index.js index cd29779b6..8bc146a20 100644 --- a/shared/index.js +++ b/shared/index.js @@ -9,6 +9,7 @@ module.exports = { Industries: require('./constants/industries'), Languages: require('./constants/languages'), Misc: require('./constants/misc'), + FieldTypes: require('./constants/field-types'), FilterTypes: require('./constants/filter-types'), FilterValues: require('./constants/filter-values'), FilterHelpers: require('./helpers/filterHelpers'),