diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeesOverview.module.scss b/backend/.env.example similarity index 100% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeesOverview.module.scss rename to backend/.env.example diff --git a/backend/common/errors/errorHandler.js b/backend/common/errors/errorHandler.js index 7c0983983..c9248d01e 100644 --- a/backend/common/errors/errorHandler.js +++ b/backend/common/errors/errorHandler.js @@ -40,7 +40,7 @@ const errorHandler = (error, request, response, next) => { } } } - console.log('FOOBAR', error.message); + console.log('UNHANDLED ERROR', error.message); return response.status(500).json({ message: 'Unexpected error', diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js index 50f9d5db9..b20ab27c1 100644 --- a/backend/common/services/sendgrid.js +++ b/backend/common/services/sendgrid.js @@ -64,6 +64,70 @@ 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 assist you with your travel costs to Junction 2019, up to the amount above. Please note that the following conditions apply: + + + 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}` + }); + + return SendgridService.send(msg); + }, + sendTravelGrantRejectedEmail: (event, user) => { + const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, { + header_image: event.coverImage.url, + 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}` + }); + + 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..582d78b1f 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,25 @@ controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => return task; }; +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 (registration, deliverNow = false) => { + const task = await controller.createTask(registration.user, registration.event, 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 +104,14 @@ controller.deliverEmailTask = async task => { await SendgridService.sendRegisteredEmail(event, user); break; } + case EmailTypes.travelGrantAccepted: { + await SendgridService.sendTravelGrantAcceptedEmail(event, user, task.params); + break; + } + case EmailTypes.travelGrantRejected: { + await SendgridService.sendTravelGrantRejectedEmail(event, user, task.params); + 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/filter-group/controller.js b/backend/modules/filter-group/controller.js new file mode 100644 index 000000000..8a0208da1 --- /dev/null +++ b/backend/modules/filter-group/controller.js @@ -0,0 +1,35 @@ +const FilterGroup = require('./model'); + +const controller = {}; +const { NotFoundError } = require('../../common/errors/errors'); + +controller.createFilterGroup = (label, description, createdBy, eventId, filters) => { + const filterGroup = new FilterGroup({ + label, + description, + createdBy, + filters, + event: eventId + }); + + return filterGroup.save(); +}; + +controller.editFilterGroup = (label, description, sub, eventId, filters) => { + return FilterGroup.findOne({ label, event: eventId }).then(filterGroup => { + if (!filterGroup) throw new NotFoundError(`Filter group with label ${label} does not exist`); + filterGroup.description = description; + filterGroup.filters = filters; + return filterGroup.save(); + }); +}; + +controller.deleteFilterGroup = (label, eventId) => { + return FilterGroup.findOneAndRemove({ label, event: eventId }); +}; + +controller.getFilterGroupsForEvent = eventId => { + return FilterGroup.find({ event: eventId }); +}; + +module.exports = controller; diff --git a/backend/modules/filter-group/model.js b/backend/modules/filter-group/model.js new file mode 100644 index 000000000..e8df44825 --- /dev/null +++ b/backend/modules/filter-group/model.js @@ -0,0 +1,47 @@ +const mongoose = require('mongoose'); +const {FilterTypes} = require('@hackjunction/shared'); + +const FilterGroupSchema = new mongoose.Schema({ + label: { + type: String, + required: true + }, + description: { + type: String + }, + event: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + createdBy: { + type: String, + required: true + }, + filters: [ + { + label: { + type: String + }, + path: { + type: String, + required: true + }, + type: { + type: String, + enum: Object.keys(FilterTypes.filterTypes), + required: true + }, + value: { + type: mongoose.Mixed + } + } + ] +}); + +FilterGroupSchema.set('timestamps', true); +FilterGroupSchema.index({ event: 1, label: 1 }, { unique: true }); + +const FilterGroup = mongoose.model('FilterGroup', FilterGroupSchema); + +module.exports = FilterGroup; diff --git a/backend/modules/filter-group/routes.js b/backend/modules/filter-group/routes.js new file mode 100644 index 000000000..c553e682e --- /dev/null +++ b/backend/modules/filter-group/routes.js @@ -0,0 +1,60 @@ +const express = require('express'); +const router = express.Router(); +const asyncHandler = require('express-async-handler'); + +const { Auth } = require('@hackjunction/shared'); + +const FilterGroupController = require('./controller'); + +const { hasToken } = require('../../common/middleware/token'); +const { hasPermission } = require('../../common/middleware/permissions'); +const { isEventOrganiser } = require('../../common/middleware/events'); + +const createFilterGroup = asyncHandler(async (req, res) => { + console.log('CREATING FILTER GROUP'); + + const { label, description, filters } = req.body; + const { sub } = req.user; + const { _id } = req.event; + console.log('CREATING FILTER GROUP'); + + const filterGroup = await FilterGroupController.createFilterGroup(label, description, sub, _id.toString(), filters); + console.log('CREATING FILTER GROUP'); + + return res.status(200).json(filterGroup); +}); + +const editFilterGroup = asyncHandler(async (req, res) => { + const { label, description, filters } = req.body; + const { sub } = req.user; + const { _id } = req.event; + + const filterGroup = await FilterGroupController.editFilterGroup(label, description, sub, _id.toString(), filters); + return res.status(200).json(filterGroup); +}); + +const deleteFilterGroup = asyncHandler(async (req, res) => { + const { label } = req.body; + const { _id } = req.event; + + const filterGroup = await FilterGroupController.deleteFilterGroup(label, _id.toString()); + + return res.status(200).json(filterGroup); +}); + +const getFilterGroupsForEvent = asyncHandler(async (req, res) => { + const { _id } = req.event; + + const filterGroups = await FilterGroupController.getFilterGroupsForEvent(_id.toString()); + + return res.status(200).json(filterGroups); +}); + +router + .route('/:slug') + .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getFilterGroupsForEvent) + .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, createFilterGroup) + .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, editFilterGroup) + .delete(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, deleteFilterGroup); + +module.exports = router; diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js index 2e9fbbc99..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' && 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; }); @@ -126,7 +133,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, @@ -138,6 +145,37 @@ 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.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) { @@ -155,6 +193,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(); }); }; diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js index 960cc0155..adf22c3e2 100644 --- a/backend/modules/registration/model.js +++ b/backend/modules/registration/model.js @@ -40,6 +40,13 @@ const RegistrationSchema = new mongoose.Schema({ answers: { type: mongoose.Mixed, default: {} + }, + travelGrant: { + type: Number, + set: function(amount) { + this._previousGrant = this.travelGrant; + return amount; + } } }); @@ -93,6 +100,14 @@ RegistrationSchema.post('save', function(doc, next) { EmailTaskController.createRejectedTask(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); + } + next(); }); diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js index 2fe447920..bbec861e0 100644 --- a/backend/modules/registration/routes.js +++ b/backend/modules/registration/routes.js @@ -87,6 +87,18 @@ 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 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); @@ -130,6 +142,11 @@ 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) + .delete(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkRejectTravelGrants); + router .route('/:slug/bulk/accept') .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAcceptRegistrations); diff --git a/backend/modules/routes.js b/backend/modules/routes.js index cdd954964..9f64157e5 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 filterGroupRouter = require('./filter-group/routes'); module.exports = function(app) { app.get('/api', (req, res) => { @@ -15,15 +16,16 @@ module.exports = function(app) { }); }); app.use('/api/auth', authRouter); - app.use('/api/upload', uploadRouter); - app.use('/api/newsletter', newsletterRouter); app.use('/api/email', emailRouter); + app.use('/api/newsletter', newsletterRouter); + app.use('/api/upload', uploadRouter); /** Model related routes */ app.use('/api/events', eventRouter); + app.use('/api/filter-groups', filterGroupRouter); + app.use('/api/registrations', registrationRouter); app.use('/api/teams', teamRouter); app.use('/api/user-profiles', userProfileRouter); - app.use('/api/registrations', registrationRouter); /** Admin tools (development only) */ if (global.gConfig.DEVTOOLS_ENABLED) { diff --git a/backend/package-lock.json b/backend/package-lock.json index df76a536d..2e468e0c8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -25,7 +25,27 @@ } }, "@hackjunction/shared": { - "version": "file:../shared" + "version": "file:../shared", + "requires": { + "lodash": "^4.17.15", + "object-path": "^0.11.4" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true }, "@sendgrid/client": { "version": "6.4.0", @@ -84,6 +104,12 @@ "@types/node": "*" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, "@types/express": { "version": "4.17.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", @@ -120,11 +146,28 @@ "@types/express": "*" } }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/node": { "version": "12.6.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", @@ -308,17 +351,44 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -699,6 +769,12 @@ "unset-value": "^1.0.0" } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -720,6 +796,17 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -1018,6 +1105,15 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1056,6 +1152,30 @@ } } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -1184,6 +1304,15 @@ } } }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1254,6 +1383,15 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -1663,6 +1801,20 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + } + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -1733,6 +1885,15 @@ "unpipe": "~1.0.0" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -2575,6 +2736,12 @@ } } }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -2590,6 +2757,30 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -2687,6 +2878,12 @@ "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -2802,6 +2999,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2897,6 +3100,12 @@ } } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -3057,6 +3266,12 @@ "path-is-inside": "^1.0.1" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3178,6 +3393,12 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -3304,6 +3525,28 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz", "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==" }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "lock": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", @@ -3359,6 +3602,16 @@ "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -3413,6 +3666,12 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -3433,11 +3692,34 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3502,6 +3784,16 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -3793,6 +4085,18 @@ "abbrev": "1" } }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3945,6 +4249,30 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "pac-proxy-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.0.tgz", @@ -4011,6 +4339,22 @@ "callsites": "^3.0.0" } }, + "parse-dotenv": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-dotenv/-/parse-dotenv-2.0.1.tgz", + "integrity": "sha512-fZ+Llm/5UafyVDQ9cRAUiOHqpeZaK2vH2cwTQkvkAi4UR4Ekj5XsAIlT0g92UKLpG3qp31SZQCqQnJGduKE7KA==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4045,6 +4389,12 @@ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4063,11 +4413,26 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -4186,6 +4551,12 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4222,6 +4593,27 @@ } } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -4254,6 +4646,16 @@ "readable-stream": "^2.0.2" } }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -4348,6 +4750,15 @@ "semver": "^5.1.0" } }, + "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" + } + }, "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", @@ -4580,6 +4991,12 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -4790,6 +5207,38 @@ "memory-pager": "^1.0.2" } }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "speakingurl": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", @@ -4890,12 +5339,24 @@ "ansi-regex": "^3.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -4965,6 +5426,17 @@ "upper-case": "^1.1.1" } }, + "sync-dotenv": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/sync-dotenv/-/sync-dotenv-2.2.1.tgz", + "integrity": "sha512-G5Gns+hc0lsOwoUhT0Sls1CFcq0VYw9CSTbj4tyNtF2Rx6CLclPga2iCSDmwENkVZM03pnPE/kpGTxMIqdB1bg==", + "dev": true, + "requires": { + "globby": "^9.2.0", + "meow": "5.0.0", + "parse-dotenv": "2.0.1" + } + }, "table": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/table/-/table-5.4.4.tgz", @@ -5141,6 +5613,12 @@ } } }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -5353,6 +5831,16 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "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", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5442,6 +5930,15 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } } } } diff --git a/backend/package.json b/backend/package.json index 4c60b2e78..6dd4bbdef 100644 --- a/backend/package.json +++ b/backend/package.json @@ -40,7 +40,8 @@ "devDependencies": { "eslint": "^5.16.0", "kill-port": "^1.4.0", - "nodemon": "^1.18.10" + "nodemon": "^1.18.10", + "sync-dotenv": "^2.2.1" }, "engines": { "node": "12.3.1", 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/.env.example b/frontend/.env.example new file mode 100644 index 000000000..eb9910a16 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,8 @@ +PORT= +REACT_APP_CLOUDINARY_CLOUD_NAME= +REACT_APP_AUTH0_DOMAIN= +REACT_APP_AUTH0_CLIENT_ID= +REACT_APP_BASE_URL= +REACT_APP_API_BASE_URL= +REACT_APP_FACEBOOK_PIXEL_ID= +REACT_APP_IS_DEBUG= \ No newline at end of file diff --git a/frontend/config-overrides.js b/frontend/config-overrides.js index 00d73d4b6..398fb7789 100644 --- a/frontend/config-overrides.js +++ b/frontend/config-overrides.js @@ -1,9 +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' - }) + }), + useBabelRc() ); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1adbb4f90..1efb63953 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", @@ -1041,6 +1032,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==" + }, "@emotion/is-prop-valid": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.3.tgz", @@ -1057,7 +1053,11 @@ "optional": true }, "@hackjunction/shared": { - "version": "file:../shared" + "version": "file:../shared", + "requires": { + "lodash": "^4.17.15", + "object-path": "^0.11.4" + } }, "@hapi/address": { "version": "2.1.2", @@ -1422,6 +1422,122 @@ "@types/yargs": "^13.0.0" } }, + "@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/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", + "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", @@ -1721,6 +1837,23 @@ "@babel/types": "^7.3.0" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -1743,6 +1876,18 @@ "@types/istanbul-lib-report": "*" } }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.7.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz", + "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==", + "dev": true + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -1770,6 +1915,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", @@ -2683,23 +2836,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", @@ -2959,23 +3095,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", @@ -3264,6 +3383,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", @@ -3477,22 +3602,26 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -3501,12 +3630,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "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", @@ -3515,32 +3646,38 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -3548,22 +3685,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -3571,12 +3712,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -3591,7 +3734,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -3604,12 +3748,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -3617,7 +3763,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -3625,7 +3772,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -3634,17 +3782,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "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" @@ -3652,12 +3803,14 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { "brace-expansion": "^1.1.7" @@ -3665,12 +3818,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { "safe-buffer": "^5.1.2", @@ -3679,7 +3834,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -3687,7 +3843,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { "minimist": "0.0.8" @@ -3695,12 +3852,14 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -3710,7 +3869,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -3727,7 +3887,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -3736,12 +3897,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -3750,7 +3913,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -3761,17 +3925,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { "wrappy": "1" @@ -3779,17 +3946,20 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -3798,17 +3968,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -3819,14 +3992,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -3840,7 +4015,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -3848,37 +4024,44 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "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", @@ -3888,7 +4071,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -3896,7 +4080,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "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" @@ -3904,12 +4089,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -3923,12 +4110,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -3936,12 +4125,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } } @@ -4075,6 +4266,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", @@ -4325,6 +4521,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", @@ -4533,14 +4734,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", @@ -4672,6 +4865,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", @@ -4866,6 +5068,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", @@ -4879,6 +5087,16 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -5020,11 +5238,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", @@ -5175,6 +5575,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", @@ -5238,11 +5647,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", @@ -5973,20 +6377,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", @@ -6463,6 +6853,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", @@ -6678,11 +7074,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", @@ -7299,6 +7690,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", @@ -7353,14 +7749,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", @@ -7746,6 +8134,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", @@ -8504,22 +8897,26 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -8528,12 +8925,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "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", @@ -8542,32 +8941,38 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -8575,22 +8980,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -8598,12 +9007,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -8618,7 +9029,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -8631,12 +9043,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -8644,7 +9058,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -8652,7 +9067,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -8661,17 +9077,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "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" @@ -8679,12 +9098,14 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { "brace-expansion": "^1.1.7" @@ -8692,12 +9113,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { "safe-buffer": "^5.1.2", @@ -8706,7 +9129,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -8714,7 +9138,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { "minimist": "0.0.8" @@ -8722,12 +9147,14 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -8737,7 +9164,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -8754,7 +9182,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -8763,12 +9192,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -8777,7 +9208,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -8788,17 +9220,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { "wrappy": "1" @@ -8806,17 +9241,20 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -8825,17 +9263,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -8846,14 +9287,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -8867,7 +9310,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -8875,37 +9319,44 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "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", @@ -8915,7 +9366,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -8923,7 +9375,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "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" @@ -8931,12 +9384,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -8950,12 +9405,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -8963,12 +9420,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } } @@ -9928,13 +10387,90 @@ "verror": "1.10.0" } }, - "jsx-ast-utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", - "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "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": { - "array-includes": "^3.0.3", - "object.assign": "^4.1.0" + "@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", + "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "requires": { + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" } }, "killable": { @@ -10280,11 +10816,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", @@ -10348,11 +10879,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", @@ -10473,6 +10999,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", @@ -10524,6 +11060,16 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -10831,6 +11377,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", @@ -10873,21 +11428,25 @@ "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", "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" }, - "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=", + "notistack": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-0.9.2.tgz", + "integrity": "sha512-Z2zi3ankqvwmrQvUx7SHM2X3P8llbPA/O1nM1hqn2fGuffn40Fi8isw701pdxczxA6S7huSyfOE/ZPgeTpWmrQ==", "requires": { - "babel-polyfill": "^6.2.0", - "minimatch": "^3.0.0", - "ps-tree": "^1.0.1", - "shell-quote": "^1.4.3", - "which": "^1.2.0" + "classnames": "^2.2.6", + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.7.2", + "react-is": "^16.8.6" } }, "npm-run-path": { @@ -11296,6 +11855,12 @@ "safe-buffer": "^5.1.1" } }, + "parse-dotenv": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-dotenv/-/parse-dotenv-2.0.1.tgz", + "integrity": "sha512-fZ+Llm/5UafyVDQ9cRAUiOHqpeZaK2vH2cwTQkvkAi4UR4Ekj5XsAIlT0g92UKLpG3qp31SZQCqQnJGduKE7KA==", + "dev": true + }, "parse-entities": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", @@ -11395,14 +11960,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", @@ -11517,6 +12074,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", @@ -11543,6 +12109,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.24", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz", @@ -12539,14 +13110,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", @@ -12635,6 +13198,12 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -12643,11 +13212,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", @@ -13288,36 +13852,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", @@ -13505,6 +14039,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", @@ -13541,11 +14080,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", @@ -13597,63 +14131,35 @@ "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": "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": { - "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": "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": { @@ -13753,15 +14259,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", @@ -13770,6 +14267,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", @@ -14167,6 +14675,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", @@ -14482,6 +14996,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", @@ -15026,14 +15546,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", @@ -15132,14 +15644,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", @@ -15426,6 +15930,250 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "sync-dotenv": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/sync-dotenv/-/sync-dotenv-2.2.1.tgz", + "integrity": "sha512-G5Gns+hc0lsOwoUhT0Sls1CFcq0VYw9CSTbj4tyNtF2Rx6CLclPga2iCSDmwENkVZM03pnPE/kpGTxMIqdB1bg==", + "dev": true, + "requires": { + "globby": "^9.2.0", + "meow": "5.0.0", + "parse-dotenv": "2.0.1" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -16161,20 +16909,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", @@ -16289,6 +17023,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", @@ -16307,6 +17051,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 b4c243407..fd5884b83 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,42 +4,39 @@ "private": true, "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", + "babel-plugin-import": "^1.12.2", "classnames": "^2.2.6", "cloudinary-react": "^1.1.1", "connected-react-router": "^6.3.0", "customize-cra": "^0.2.12", - "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", "moment-timezone": "^0.5.25", "node-sass": "^4.11.0", + "notistack": "^0.9.2", "object-path": "^0.11.4", "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", "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", "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": "^4.3.1", + "react-router-dom": "^5.1.0", "react-scripts": "3.0.1", "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", @@ -53,7 +50,11 @@ "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 .", + "sync-env": "sync-dotenv", + "precommit": "npm run lint && npm run sync-env" }, "eslintConfig": { "extends": "react-app" @@ -68,5 +69,9 @@ "engines": { "node": "12.3.1", "npm": "6.9.0" + }, + "devDependencies": { + "depcheck": "^0.8.3", + "sync-dotenv": "^2.2.1" } } 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/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/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/components/buttons/StepButtons.js b/frontend/src/components/buttons/StepButtons.js new file mode 100644 index 000000000..63cfd20eb --- /dev/null +++ b/frontend/src/components/buttons/StepButtons.js @@ -0,0 +1,31 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { Button, Box } from '@material-ui/core'; + +const useButtonStyles = makeStyles(theme => ({ + button: { + marginLeft: theme.spacing(2) + } +})); + +const StepButtons = ({ numSteps, activeStep, onBack, onNext, onFinish }) => { + const classes = useButtonStyles(); + return ( + + + + + ); +}; + +export default StepButtons; diff --git a/frontend/src/components/filters/FilterForm.js b/frontend/src/components/filters/FilterForm.js new file mode 100644 index 000000000..220298215 --- /dev/null +++ b/frontend/src/components/filters/FilterForm.js @@ -0,0 +1,153 @@ +import React, { useState, useMemo, useCallback, useEffect } from 'react'; + +import { connect } from 'react-redux'; +import { RegistrationFields, FilterTypes } from '@hackjunction/shared'; +import { makeStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { + Grid, + Button, + ExpansionPanel, + ExpansionPanelSummary, + ExpansionPanelDetails, + ExpansionPanelActions, + Typography +} from '@material-ui/core'; + +import Select from 'components/inputs/Select'; +import FilterValueInput from './FilterValueInput'; +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(2) + }, + headingItem: { + marginRight: theme.spacing(1) + }, + body: { + padding: theme.spacing(3) + } +})); + +const FilterForm = ({ onSubmit, event }) => { + const classes = useStyles(); + const [expanded, setExpanded] = useState(false); + const [filter, setFilter] = useState(); + const [filterType, setFilterType] = useState(); + const [filterValue, setFilterValue] = useState(); + + useEffect(() => { + setFilterType(undefined); + }, [filter]); + + useEffect(() => { + setFilterValue(undefined); + }, [filterType]); + + const toggleExpanded = useCallback(() => { + setExpanded(!expanded); + }, [expanded]); + + const handleClear = useCallback(() => { + setExpanded(false); + setFilter(undefined); + setFilterType(undefined); + setFilterValue(undefined); + }, []); + + const filterParams = useMemo(() => { + return filter ? JSON.parse(filter) : null; + }, [filter]); + + const submitValue = useMemo(() => { + if (!filterParams) return null; + if (!filterType) return null; + + return { + label: filterParams.label, + path: filterParams.path, + type: filterType, + value: filterValue + }; + }, [filterParams, filterType, filterValue]); + + const handleSubmit = useCallback(() => { + onSubmit(submitValue); + handleClear(); + }, [submitValue, onSubmit, handleClear]); + + 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 ( + + } aria-controls="panel1c-content" id="panel1c-header"> +
+ Add a filter +
+
+ + + + + )} + + + + + + + + + + +
+ ); +}; + +const mapState = state => ({ + event: OrganiserSelectors.event(state) +}); + +export default connect(mapState)(FilterForm); diff --git a/frontend/src/components/filters/FilterGroupMenu.js b/frontend/src/components/filters/FilterGroupMenu.js new file mode 100644 index 000000000..91cb4d41d --- /dev/null +++ b/frontend/src/components/filters/FilterGroupMenu.js @@ -0,0 +1,152 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { connect } from 'react-redux'; +import { sortBy } from 'lodash-es'; +import { List, ListItem, ListItemText, Menu, MenuItem, Paper, Box, Divider } from '@material-ui/core'; + +import FilterForm from './FilterForm'; +import FilterList from './FilterList'; +import FilterSaveForm from './FilterSaveForm'; + +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +const useStyles = makeStyles(theme => ({ + root: {} +})); + +const FilterGroupMenu = ({ + onChange = () => {}, + onSelectedChange = () => {}, + event, + filterGroups, + showEdit = true +}) => { + const classes = useStyles(); + const [anchorEl, setAnchorEl] = React.useState(null); + + const [selected, setSelected] = useState(); + const [filters, setFilters] = useState([]); + + useEffect(() => { + if (selected) { + setFilters(selected.filters); + } else { + setFilters([]); + } + }, [selected]); + + useEffect(() => { + onSelectedChange(selected); + }, [selected, onSelectedChange]); + + useEffect(() => { + onChange(filters); + }, [filters, onChange]); + + const handleFilterAdd = useCallback( + filter => { + setFilters(filters.concat(filter)); + }, + [filters] + ); + + const handleClickListItem = event => { + setAnchorEl(event.currentTarget); + }; + + const handleMenuItemClick = option => { + if (option.isDefault) { + setSelected(); + } else { + setSelected(option); + } + setAnchorEl(null); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const options = useMemo(() => { + let items = [ + { + label: 'All participants', + description: 'No filters', + filters: [], + isDefault: true + } + ]; + + if (showEdit) { + items.push({ + label: 'New filters', + description: 'Apply a set of custom filters', + filters: [], + isAdd: true + }); + } + + items = items.concat(sortBy(filterGroups, 'label')); + + return items; + }, [filterGroups, showEdit]); + + const activeItem = selected || options[0]; + const reservedLabels = options.map(option => option.label); + + return ( + + + + + + + + {options.map((option, index) => ( + + {index !== 0 && } + handleMenuItemClick(option)} + > + + + + ))} + + {showEdit && !activeItem.isDefault && ( + + + + + + )} + + ); +}; + +const mapState = state => ({ + filterGroups: OrganiserSelectors.filterGroups(state), + event: OrganiserSelectors.event(state) +}); + +export default connect(mapState)(FilterGroupMenu); diff --git a/frontend/src/components/filters/FilterList.js b/frontend/src/components/filters/FilterList.js new file mode 100644 index 000000000..6abfcbe44 --- /dev/null +++ b/frontend/src/components/filters/FilterList.js @@ -0,0 +1,79 @@ +import React, { useState, useCallback, useEffect } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { + List, + Divider, + ExpansionPanel, + ExpansionPanelSummary, + ExpansionPanelDetails, + ExpansionPanelActions, + Typography, + Button, + Badge +} from '@material-ui/core'; +import FilterListItem from 'components/filters/FilterListItem'; + +const useStyles = makeStyles(theme => ({ + headingItem: { + marginRight: theme.spacing(1) + }, + badge: { + right: -1 * theme.spacing(2), + top: theme.spacing(1) + }, + list: { + width: '100%' + } +})); + +const FilterList = ({ activeItemKey, filters = [], onChange = () => {} }) => { + const classes = useStyles(); + const [expanded, setExpanded] = useState(false); + const toggleExpanded = useCallback(() => setExpanded(!expanded), [expanded]); + const hasFilters = filters.length !== 0; + + useEffect(() => { + setExpanded(false); + }, [activeItemKey]); + + const handleRemove = useCallback( + index => { + const newFilters = filters.filter((filter, idx) => { + return idx !== index; + }); + onChange(newFilters); + }, + [onChange, filters] + ); + + return ( + + } aria-controls="panel1c-content" id="panel1c-header"> + + Active filters + + + + + {filters.map((filter, index) => ( + + {index !== 0 && } + handleRemove(index)} /> + + ))} + + + + ); +}; + +export default FilterList; diff --git a/frontend/src/components/filters/FilterListItem.js b/frontend/src/components/filters/FilterListItem.js new file mode 100644 index 000000000..825fe7a0a --- /dev/null +++ b/frontend/src/components/filters/FilterListItem.js @@ -0,0 +1,66 @@ +import React from 'react'; + +import DeleteIcon from '@material-ui/icons/Delete'; +import { makeStyles } from '@material-ui/core/styles'; +import { Typography, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Chip } from '@material-ui/core'; +import { FilterTypes } from '@hackjunction/shared'; + +const useStyles = makeStyles(theme => ({ + inline: { + display: 'inline' + }, + chips: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap' + }, + chip: { + margin: 2 + } +})); + +const FilterListItem = ({ filter = {}, onRemove }) => { + const classes = useStyles(); + const getType = () => { + const params = FilterTypes.filterTypes[filter.type]; + return params ? params.label : filter.type; + }; + + const renderValue = (value) => { + if (Array.isArray(value)) { + return( +
+ {value.map(item => ( + + ))} +
+ ) + } + return value; + } + + return ( + + + + {getType()} + {' '} + {renderValue(filter.value)} + + } + /> + {typeof onRemove === 'function' && ( + + + + + + )} + + ); +}; + +export default FilterListItem; diff --git a/frontend/src/components/filters/FilterSaveForm.js b/frontend/src/components/filters/FilterSaveForm.js new file mode 100644 index 000000000..3b2ffc76b --- /dev/null +++ b/frontend/src/components/filters/FilterSaveForm.js @@ -0,0 +1,209 @@ +import React, { useState, useEffect, useCallback } from 'react'; + +import { withSnackbar } from 'notistack'; +import { connect } from 'react-redux'; +import { + ExpansionPanel, + ExpansionPanelSummary, + ExpansionPanelDetails, + ExpansionPanelActions, + Typography, + Button, + Grid, + CircularProgress +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import TextInput from 'components/inputs/TextInput'; +import { useFormField } from 'hooks/formHooks'; +import * as OrganiserActions from 'redux/organiser/actions'; +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +const FilterSaveForm = ({ + createFilterGroup, + editFilterGroup, + deleteFilterGroup, + filters, + activeItem, + reservedLabels, + event, + enqueueSnackbar, + onSave, + onDelete +}) => { + const isEdit = !activeItem.isDefault && !activeItem.isAdd; + const [loading, setLoading] = useState(false); + const [expanded, setExpanded] = useState(false); + const label = useFormField(isEdit ? activeItem.label : '', value => { + if (value.length === 0) { + return 'Name is required'; + } + + if (value.length > 50) { + return 'Name must be under 50 characters'; + } + + if (!isEdit) { + if (reservedLabels.indexOf(value) !== -1) { + return 'Name is already taken'; + } + } + + return; + }); + + const description = useFormField(isEdit ? activeItem.description : '', value => { + if (value.length > 100) { + return 'Description must be under 100 characters'; + } + + return; + }); + + const toggleExpanded = useCallback( + (event, isExpanded) => { + setExpanded(isExpanded); + label.setValue(isEdit ? activeItem.label : ''); + description.setValue(isEdit ? activeItem.description : ''); + }, + [isEdit, label, description, activeItem] + ); + + useEffect(() => { + setExpanded(false); + }, [activeItem]); + + const handleSubmit = () => { + const errs = [label.validate(), description.validate()].filter(err => err !== undefined); + if (errs.length > 0) { + return; + } + + if (isEdit) { + handleEdit(label.value, description.value); + } else { + handleCreate(label.value, description.value); + } + }; + + const handleEdit = useCallback( + (label, description) => { + setLoading(true); + editFilterGroup(event.slug, label, description, filters) + .then(item => { + enqueueSnackbar('Edits saved!', { variant: 'success' }); + toggleExpanded(null, false); + onSave(item); + }) + .catch(err => { + enqueueSnackbar('Something went wrong', { variant: 'error' }); + }) + .finally(() => { + setLoading(false); + }); + }, + [onSave, toggleExpanded, enqueueSnackbar, event.slug, filters, editFilterGroup] + ); + + const handleCreate = useCallback( + (label, description) => { + setLoading(true); + createFilterGroup(event.slug, label, description, filters) + .then(item => { + enqueueSnackbar('Filter group created', { variant: 'success' }); + toggleExpanded(null, false); + onSave(item); + }) + .catch(err => { + enqueueSnackbar('Something went wrong', { variant: 'error' }); + }) + .finally(() => { + setLoading(false); + }); + }, + [onSave, toggleExpanded, enqueueSnackbar, event.slug, filters, createFilterGroup] + ); + + const handleDelete = useCallback(() => { + setLoading(true); + deleteFilterGroup(event.slug, label.value) + .then(() => { + enqueueSnackbar('Filter group deleted', { variant: 'success' }); + toggleExpanded(null, false); + onDelete(); + }) + .catch(err => { + enqueueSnackbar('Something went wrong', { variant: 'error' }); + }) + .finally(() => { + setLoading(false); + }); + }, [label, onDelete, toggleExpanded, enqueueSnackbar, deleteFilterGroup, event.slug]); + + return ( + + } aria-controls="save-filters" id="save-filters"> + {isEdit ? 'Edit these filters' : 'Save these filters'} + + + + {!isEdit && ( + + + You can save this filter group for later use. This allows you to easily view stats for + the group, and do things like bulk edit their applications or send an email to everyone + in the group. + + + )} + + + + + + + + + + {loading && } + {isEdit && ( + + )} + + + + ); +}; + +const mapState = state => ({ + event: OrganiserSelectors.event(state) +}); + +const mapDispatch = dispatch => ({ + createFilterGroup: (slug, label, description, filters) => + dispatch(OrganiserActions.createFilterGroup(slug, label, description, filters)), + editFilterGroup: (slug, label, description, filters) => + dispatch(OrganiserActions.editFilterGroup(slug, label, description, filters)), + deleteFilterGroup: (slug, label) => dispatch(OrganiserActions.deleteFilterGroup(slug, label)) +}); + +export default withSnackbar( + connect( + mapState, + mapDispatch + )(FilterSaveForm) +); diff --git a/frontend/src/components/filters/FilterValueInput.js b/frontend/src/components/filters/FilterValueInput.js new file mode 100644 index 000000000..9cad0dbb3 --- /dev/null +++ b/frontend/src/components/filters/FilterValueInput.js @@ -0,0 +1,73 @@ +import React from 'react'; +import { FilterTypes, FilterValues } from '@hackjunction/shared'; + +import TextInput from 'components/inputs/TextInput'; +import Select from 'components/inputs/Select'; + +const MULTI_TYPES = [ + FilterTypes.filterTypes.ONE_OF.id, + FilterTypes.filterTypes.NOT_ONE_OF.id, + FilterTypes.filterTypes.CONTAINS_ONE_OF.id, + FilterTypes.filterTypes.NOT_CONTAINS_ONE_OF.id +]; + +const FilterValueInput = ({ filterType, valueType, value, onChange, event }) => { + const inputParams = { value, onChange }; + 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: + case FilterTypes.filterTypes.ONE_OF.id: + case FilterTypes.filterTypes.NOT_ONE_OF.id: + case FilterTypes.filterTypes.CONTAINS_ONE_OF.id: + case FilterTypes.filterTypes.NOT_CONTAINS_ONE_OF.id: + const isMulti = MULTI_TYPES.indexOf(filterType) !== -1; + switch (valueType) { + case FilterValues.STRING: + return ; + case FilterValues.BOOLEAN: + return ; + case FilterValues.DATE: + return ; + case FilterValues.GENDER: + return ; + case FilterValues.COUNTRY: + return ; + case FilterValues.TAG: + const options = event.tags.map(tag => ({ + value: tag.label, + label: tag.label + })); + return ; + default: + return null; + } + case FilterTypes.filterTypes.IS_EMPTY: + case FilterTypes.filterTypes.NOT_EMPTY: + case FilterTypes.filterTypes.BOOLEAN_FALSE.id: + case FilterTypes.filterTypes.BOOLEAN_TRUE.id: + return null; + default: + return null; + } +}; + +export default FilterValueInput; diff --git a/frontend/src/components/generic/ActionMenu/index.js b/frontend/src/components/generic/ActionMenu/index.js new file mode 100644 index 000000000..4806b2fbe --- /dev/null +++ b/frontend/src/components/generic/ActionMenu/index.js @@ -0,0 +1,57 @@ +import React, { useState, useCallback } from 'react'; + +import { Menu, MenuItem, IconButton, Tooltip } from '@material-ui/core'; +import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; + +const ActionMenu = ({ title = 'Actions', actions = [], actionProps }) => { + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = useCallback(event => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(event => { + setAnchorEl(null); + }, []); + + const handleItemClick = action => { + action.action(...actionProps); + handleClose(); + }; + + if (actions.length === 1) { + const action = actions[0]; + return ( + + handleItemClick(action)}> + + + + ); + } + + return ( + + + + + + + + {actions.map(action => ( + handleItemClick(action)}> + {action.label} + + ))} + + + ); +}; + +export default ActionMenu; 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 new file mode 100644 index 000000000..db59febad --- /dev/null +++ b/frontend/src/components/generic/Modal/index.js @@ -0,0 +1,70 @@ +import React from 'react'; + +import HyperModal from 'react-hyper-modal'; +import classNames from 'classnames'; +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles(theme => ({ + wrapper: { + display: 'flex', + zIndex: 100 + }, + wrapperPadded: { + padding: theme.spacing(2) + }, + content: { + background: '#ffffff', + width: '100% !important', + maxWidth: '600px', + zIndex: 1, + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch' + }, + contentMed: { + maxWidth: '900px' + }, + contentMax: { + maxWidth: 'none', + height: '100% !important', + borderRadius: '0 !important' + }, + header: { + padding: theme.spacing(3), + textAlign: 'left' + }, + inner: { + padding: '1rem', + flex: 1, + overflow: 'auto' + } +})); + +const GenericModal = ({ title, isOpen, handleClose, size, children }) => { + const classes = useStyles(); + return ( + + + {title} + + {children} + + ); +}; + +export default GenericModal; diff --git a/frontend/src/components/generic/PageHeader/index.js b/frontend/src/components/generic/PageHeader/index.js new file mode 100644 index 000000000..c1ccbb046 --- /dev/null +++ b/frontend/src/components/generic/PageHeader/index.js @@ -0,0 +1,28 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { Box, Typography } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + subheading: { + marginTop: theme.spacing(1), + marginLeft: theme.spacing(0.5), + fontFamily: 'Lato' + } +})); + +const PageHeader = ({ heading, subheading }) => { + const classes = useStyles(); + return ( + + {heading} + {subheading && ( + + {subheading} + + )} + + ); +}; + +export default PageHeader; diff --git a/frontend/src/components/generic/Statistic/index.js b/frontend/src/components/generic/Statistic/index.js new file mode 100644 index 000000000..7e928ec5a --- /dev/null +++ b/frontend/src/components/generic/Statistic/index.js @@ -0,0 +1,52 @@ +import React, { useState, useCallback } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { Card, CardContent, Typography, Button, Box, CircularProgress } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + value: { + textAlign: 'left' + }, + suffix: { + marginLeft: theme.spacing(1), + display: 'inline-block' + } +})); + +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 ( + + + + {label} + + + {value} + {suffix && ( + + {suffix} + + )} + + {action && actionText && ( + + {actionLoading && } + + + )} + + + ); +}; + +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/TablePaginationActions.js b/frontend/src/components/generic/Table/TablePaginationActions.js new file mode 100644 index 000000000..e0972862c --- /dev/null +++ b/frontend/src/components/generic/Table/TablePaginationActions.js @@ -0,0 +1,65 @@ +import React, { useCallback } from 'react'; + +import { IconButton, Box } from '@material-ui/core'; + +import FirstPageIcon from '@material-ui/icons/FirstPage'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; +import LastPageIcon from '@material-ui/icons/LastPage'; + +const TablePaginationActions = ({ count, page, rowsPerPage, onChangePage }) => { + const handleFirstPageButtonClick = useCallback( + event => { + onChangePage(event, 0); + }, + [onChangePage] + ); + + const handleBackButtonClick = useCallback( + event => { + onChangePage(event, page - 1); + }, + [onChangePage, page] + ); + + const handleNextButtonClick = useCallback( + event => { + onChangePage(event, page + 1); + }, + [onChangePage, page] + ); + + const handleLastPageButtonClick = useCallback( + event => { + onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }, + [onChangePage, count, rowsPerPage] + ); + + return ( + + + + + + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + + + + ); +}; + +export default TablePaginationActions; diff --git a/frontend/src/components/generic/Table/TableToolbar.js b/frontend/src/components/generic/Table/TableToolbar.js new file mode 100644 index 000000000..a55bb2820 --- /dev/null +++ b/frontend/src/components/generic/Table/TableToolbar.js @@ -0,0 +1,57 @@ +import React from 'react'; + +import { lighten, makeStyles } from '@material-ui/core/styles'; +import { Toolbar, Tooltip, Typography, Box, IconButton } from '@material-ui/core'; + +import classNames from 'classnames'; + +const useToolbarStyles = makeStyles(theme => ({ + root: { + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(1), + transition: 'all 0.2s ease' + }, + highlight: { + color: theme.palette.primary.dark, + backgroundColor: lighten(theme.palette.primary.main, 0.85) + }, + action: { + transform: 'scale(0.5)', + transition: 'all 0.2s ease', + opacity: 0, + pointerEvents: 'none' + }, + actionActive: { + transform: 'scale(1)', + opacity: 1, + pointerEvents: 'initial' + } +})); + +const TableToolbar = ({ title, selectedRows, actions = [] }) => { + const classes = useToolbarStyles(); + const hasSelected = selectedRows.length > 0; + + return ( + + + {hasSelected ? `${selectedRows.length} selected` : title} + + + {actions.map(action => ( + + action.action(selectedRows)} aria-label={action.label}> + {action.icon} + + + ))} + + + ); +}; + +export default TableToolbar; diff --git a/frontend/src/components/generic/Table/index.js b/frontend/src/components/generic/Table/index.js new file mode 100644 index 000000000..07b2dd8fa --- /dev/null +++ b/frontend/src/components/generic/Table/index.js @@ -0,0 +1,214 @@ +import React, { useState, useCallback, useMemo } from 'react'; + +import { lighten, makeStyles } from '@material-ui/core/styles'; +import { + Table, + TableBody, + TableCell, + TableRow, + TableHead, + TableFooter, + TablePagination, + Box, + Paper, + CircularProgress, + Checkbox +} from '@material-ui/core'; + +import objectPath from 'object-path'; + +import TableToolbar from './TableToolbar'; +import TablePaginationActions from './TablePaginationActions'; +import ActionMenu from 'components/generic/ActionMenu'; + +const useTableStyles = makeStyles(theme => ({ + root: { + overflowX: 'auto' + } +})); + +const usePaginationStyles = makeStyles(theme => ({ + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + flexWrap: 'wrap', + height: 'auto' + } +})); + +export default ({ + columns = [], + dataSource = [], + rowKey, + loading = true, + pagination = true, + rowNumber = true, + rowSelection = true, + footer, + title = '', + selectedActions = [], + rowActions = [] +}) => { + const classes = useTableStyles(); + const paginationClasses = usePaginationStyles(); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [page, setPage] = useState(0); + const [selectedRows, setSelectedRows] = useState([]); + + const handleChangePage = useCallback((event, newPage) => { + setPage(newPage); + }, []); + + const handleChangeRowsPerPage = useCallback(event => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }, []); + + const handleSelectAll = useCallback(() => { + if (selectedRows.length === dataSource.length) { + setSelectedRows([]); + } else { + setSelectedRows(dataSource.map(item => item[rowKey])); + } + }, [dataSource, rowKey, selectedRows]); + + const handleSelectRow = item => { + const key = item[rowKey]; + let rows = selectedRows.slice(); + const index = rows.indexOf(key); + if (rows.indexOf(key) !== -1) { + rows.splice(index, 1); + } else { + rows = rows.concat(key); + } + setSelectedRows(rows); + }; + + const isRowSelected = item => { + return selectedRows.indexOf(item[rowKey]) !== -1; + }; + + const columnCount = useMemo(() => { + let columnCount = columns.length; + if (rowSelection) columnCount++; + if (rowNumber) columnCount++; + if (rowActions) columnCount++; + return columnCount; + }, [columns, rowSelection, rowActions, rowNumber]); + + const data = useMemo(() => { + return dataSource.slice(page * rowsPerPage, (page + 1) * rowsPerPage); + }, [dataSource, page, rowsPerPage]); + + const isAllSelected = useMemo(() => { + if (!dataSource.length || loading) return false; + return selectedRows.length === dataSource.length; + }, [selectedRows, dataSource, loading]); + + return ( + + + + + + + {rowSelection && ( + + + + )} + {rowNumber && #} + {rowActions.length > 0 && ( + + Actions + + )} + {columns.map(column => ( + {column.label} + ))} + + + + {loading && ( + + + + + + )} + {!loading && data.length === 0 && ( + + + No data + + + )} + {!loading && + data.length > 0 && + data.map((item, index) => ( + + {rowSelection && ( + + handleSelectRow(item)} + /> + + )} + {rowNumber && ( + {1 + index + page * rowsPerPage} + )} + {rowActions.length > 0 && ( + + + + )} + {columns.map(column => { + const value = objectPath.get(item, column.path); + return ( + + {column.render ? column.render(value, item) : value} + + ); + })} + + ))} + + {footer && ( + + + + {footer} + + + + )} +
+
+ {pagination && ( + + + + + + )} +
+ ); +}; diff --git a/frontend/src/components/inputs/Select/index.js b/frontend/src/components/inputs/Select/index.js new file mode 100644 index 000000000..1a4236b10 --- /dev/null +++ b/frontend/src/components/inputs/Select/index.js @@ -0,0 +1,95 @@ +import React, { useCallback, useMemo } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import { TextField, MenuItem, Chip } from '@material-ui/core'; +import { SelectOptions } from '@hackjunction/shared'; + +const useStyles = makeStyles(theme => ({ + chips: { + display: 'flex', + flexWrap: 'wrap' + }, + chip: { + margin: 2, + } +})); + +const _Select = ({ + label, + placeholder, + helperText, + value, + onChange = () => {}, + options = [], + type, + multiple = false +}) => { + const classes = useStyles() + const handleChange = useCallback( + e => { + onChange(e.target.value); + }, + [onChange] + ); + + const items = useMemo(() => { + switch (type) { + case 'country': + return SelectOptions.COUNTRIES; + case 'nationality': + return SelectOptions.NATIONALITIES; + case 'gender': + return SelectOptions.GENDERS; + case 'industry': + return SelectOptions.INDUSTRY; + case 'language': + return SelectOptions.LANGUAGES; + case 'role': + return SelectOptions.ROLES; + case 'skill': + return SelectOptions.SKILLS; + case 'theme': + return SelectOptions.THEMES; + case 'status': + return SelectOptions.STATUSES; + default: + return options; + } + }, [options, type]); + + const valueOrDefault = value || (multiple ? [] : ''); + + const selectProps = { multiple }; + if (multiple) { + selectProps.renderValue = (value = []) => { + return( +
+ {value.map(item => ( + + ))} +
+ ); + } + } + + return ( + + {items.map(item => ( + + {item.label} + + ))} + + ); +}; + +export default _Select; diff --git a/frontend/src/components/inputs/TextInput/index.js b/frontend/src/components/inputs/TextInput/index.js new file mode 100644 index 000000000..cd7dffcff --- /dev/null +++ b/frontend/src/components/inputs/TextInput/index.js @@ -0,0 +1,20 @@ +import React, { useCallback } from 'react'; + +import { TextField } from '@material-ui/core'; + +const TextInput = ({ label, helperText, value = '', onChange = () => {}, error, disabled, rawOnChange = false, type = 'text', multiline = false }) => { + const handleChange = useCallback( + e => { + if (rawOnChange) { + onChange(e); + } else { + onChange(e.target.value); + } + }, + [onChange, rawOnChange] + ); + + return ; +}; + +export default TextInput; diff --git a/frontend/src/components/layouts/MaterialTabsLayout/index.js b/frontend/src/components/layouts/MaterialTabsLayout/index.js new file mode 100644 index 000000000..e860b5242 --- /dev/null +++ b/frontend/src/components/layouts/MaterialTabsLayout/index.js @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { makeStyles, useTheme } from '@material-ui/core/styles'; +import { AppBar, Tabs, Tab, Typography, Box, useMediaQuery} from '@material-ui/core'; + +function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, +}; + +function a11yProps(index) { + return { + id: `scrollable-auto-tab-${index}`, + 'aria-controls': `scrollable-auto-tabpanel-${index}`, + }; +} + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + width: '100%', + backgroundColor: theme.palette.background.paper, + }, + wrapper: { + textAlign: 'left', + alignItems: 'flex-start' + } +})); + +const MaterialTabsLayout = ({ tabs }) => { + const classes = useStyles(); + const [value, setValue] = React.useState(0); + + const handleChange = (event, newValue) => { + setValue(newValue); + }; + + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + return ( +
+ + {tabs.map((tab, index) => ( + + ))} + + + {tabs.map((tab, index) => ( + + {tab.content} + + ))} + +
+ ); +} + +export default MaterialTabsLayout; \ No newline at end of file diff --git a/frontend/src/components/layouts/SidebarLayout/index.js b/frontend/src/components/layouts/SidebarLayout/index.js index 451b03920..53e3671bc 100644 --- a/frontend/src/components/layouts/SidebarLayout/index.js +++ b/frontend/src/components/layouts/SidebarLayout/index.js @@ -118,7 +118,11 @@ const SidebarLayout = React.memo(({ renderTop, renderSidebarTop, baseRoute, loca if (hidden) { return null; } else { - return ; + return ( + + {render()} + + ); } })} diff --git a/frontend/src/components/modals/EditRegistrationModal/index.js b/frontend/src/components/modals/EditRegistrationModal/index.js new file mode 100644 index 000000000..bdf5d713d --- /dev/null +++ b/frontend/src/components/modals/EditRegistrationModal/index.js @@ -0,0 +1,270 @@ +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, Input } 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, + travelGrant: registration.travelGrant + }; + 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)} />} + > + + ( + + + + }, + { + 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 50% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Manage/index.js index 81934eaf8..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 = ({ @@ -68,71 +68,54 @@ const OrganiserEditEventManage = ({ }); } - const testEmail = () => { - const recipient = 'juuso.lappalainen@hackjunction.com'; - }; - return ( ( - 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 ( - - ); - } - } - ]} - /> - - - } /> - - )} /> diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.js deleted file mode 100644 index 9c283d5e2..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventAttendees/AttendeeDrawer.js +++ /dev/null @@ -1,196 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import styles from './AttendeeDrawer.module.scss'; - -import { connect } from 'react-redux'; -import { RegistrationFields, RegistrationStatuses } from '@hackjunction/shared'; -import { Drawer, Skeleton, Descriptions, Tabs, Button, Tag, Popconfirm } from 'antd'; -import { find, groupBy } from 'lodash-es'; - -import * as AuthSelectors from 'redux/auth/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import Divider from 'components/generic/Divider'; -import AttendeeDrawerEdit from './AttendeeDrawerEdit'; - -const AttendeeDrawer = ({ - event, - idToken, - slug, - registrationId, - isOpen, - onClose, - editAttendee, - acceptAttendee, - rejectAttendee, - updateAttendee, - getAttendee -}) => { - 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/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/OrganiserEditEventReview/AttendeeFilters.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js deleted file mode 100644 index 12c3aee23..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: OrganiserSelectors.registrationsFilters(state) -}); - -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/SearchAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js deleted file mode 100644 index dfce04240..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import styles from './SearchAttendeesPage.module.scss'; -import { connect } from 'react-redux'; -import { Button as AntButton } from 'antd'; - -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as FilterUtils from 'utils/filters'; - -import Divider from 'components/generic/Divider'; -import AttendeeTable from 'components/tables/AttendeeTable'; -import BulkEditRegistrationDrawer from 'components/modals/BulkEditRegistrationDrawer'; -import BulkEmailDrawer from 'components/modals/BulkEmailDrawer'; -import AttendeeFilters from './AttendeeFilters'; - -const SearchAttendeesPage = ({ registrations, registrationsLoading, filters }) => { - const filtered = FilterUtils.applyFilters(registrations, filters); - - const renderBulkActions = () => { - if (!registrations.length) return null; - const ids = registrations.map(r => r._id); - const userIds = registrations.map(r => r.user); - return ( -
-
- {registrations.length} registrations -
- - -
- ); - }; - - return ( - - - - {renderBulkActions()} - - - ); -}; - -const mapState = state => ({ - registrations: OrganiserSelectors.registrationsFiltered(state), - registrationsLoading: OrganiserSelectors.registrationsLoading(state), - filters: OrganiserSelectors.registrationsFilters(state) -}); - -export default connect(mapState)(SearchAttendeesPage); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js deleted file mode 100644 index af122db33..000000000 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; - -import { PageHeader, Menu, Button as AntButton } from 'antd'; -import { connect } from 'react-redux'; - -import PageWrapper from 'components/PageWrapper'; -import Divider from 'components/generic/Divider'; - -import * as OrganiserSelectors from 'redux/organiser/selectors'; -import * as OrganiserActions from 'redux/organiser/actions'; - -import SearchAttendeesPage from './SearchAttendeesPage'; -import AssignAttendeesPage from './AssignAttendeesPage'; -import TeamsPage from './TeamsPage'; -import AdminPage from './AdminPage'; - -const OrganiserEditEventReview = ({ event, organisers, updateRegistrations, updateTeams, registrationsLoading }) => { - const [selectedKey, setSelectedKey] = useState('search'); - - const updateData = useCallback(() => { - updateRegistrations(event.slug); - updateTeams(event.slug); - }, [event.slug, updateTeams, updateRegistrations]); - - useEffect(() => { - updateData(); - }, [event.slug, updateData]); - - const renderSelectedKey = () => { - switch (selectedKey) { - case 'search': - return ; - case 'teams': - return ; - case 'assigned': - return ; - case 'admin': - return ; - default: - return null; - } - }; - - return ( - - Applications to your event

} - extra={ - - Refresh data - - } - footer={ - - setSelectedKey(key)} - > - Participants - Teams - Assigned to you - Admin & Tools - - - {renderSelectedKey()} - - } - /> -
- ); -}; - -const mapState = state => ({ - organisers: OrganiserSelectors.organisers(state), - event: OrganiserSelectors.event(state), - registrationsLoading: OrganiserSelectors.registrationsLoading(state) -}); - -const mapDispatch = dispatch => ({ - updateRegistrations: slug => dispatch(OrganiserActions.updateRegistrationsForEvent(slug)), - updateTeams: slug => dispatch(OrganiserActions.updateTeamsForEvent(slug)) -}); - -export default connect( - mapState, - mapDispatch -)(OrganiserEditEventReview); 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 97% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Assigned/index.js index a4b30e9b7..2e010f8f6 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.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/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/Participants/Search/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/index.js new file mode 100644 index 000000000..ebc6d3005 --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Search/index.js @@ -0,0 +1,32 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import styles from './SearchAttendeesPage.module.scss'; +import { connect } from 'react-redux'; +import { FilterHelpers } from '@hackjunction/shared'; + +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +import Divider from 'components/generic/Divider'; +import AttendeeTable from 'components/tables/AttendeeTable'; +import FilterGroupMenu from 'components/filters/FilterGroupMenu'; + +const SearchAttendeesPage = ({ registrations, registrationsLoading }) => { + const [filters, setFilters] = useState([]); + const filtered = FilterHelpers.applyFilters(registrations, filters); + + return ( + + + + {/* {renderBulkActions()} */} + + + ); +}; + +const mapState = state => ({ + registrations: OrganiserSelectors.registrations(state), + registrationsLoading: OrganiserSelectors.registrationsLoading(state), + filters: [] +}); + +export default connect(mapState)(SearchAttendeesPage); 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 97% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Teams/index.js index e5e9e8e62..2ad347a3c 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.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 + })); + + RegistrationsService.bulkAssignTravelGrantsForEvent(idToken, slug, data) + .then(() => { + enqueueSnackbar('Success!', { variant: 'success' }); + }) + .catch(err => { + enqueueSnackbar('Something went wrong...', { variant: 'error' }); + console.log(err); + }); + }, [idToken, slug, calculations, enqueueSnackbar]); + + 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.registrationsEligibleForTravelGrant(state) +}); + +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 new file mode 100644 index 000000000..e78dbc207 --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/Travel/index.js @@ -0,0 +1,171 @@ +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 = ({ + enqueueSnackbar, + event, + idToken, + registrations, + registrationsWithTravelGrant, + filterGroups, + filterGroupsLoading, + travelGrantSpend, + travelGrantCount, + travelGrantRejectedCount +}) => { + const [groups, setGroups] = useState({}); + + useEffect(() => { + if (filterGroups) { + setGroups( + filterGroups.reduce((res, group) => { + res[group.label] = 0; + return res; + }, {}) + ); + } + }, [filterGroups]); + + const handleAmountChange = useCallback( + (group, amount) => { + setGroups({ + ...groups, + [group]: amount + }); + }, + [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 + 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 + + + + + + + ); +}; + +const mapState = 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), + travelGrantSpend: OrganiserSelectors.travelGrantSpend(state), + travelGrantCount: OrganiserSelectors.travelGrantCount(state), + travelGrantRejectedCount: OrganiserSelectors.travelGrantRejectedCount(state) +}); + +export default withSnackbar(connect(mapState)(TravelGrantPage)); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js new file mode 100644 index 000000000..d5c200659 --- /dev/null +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Participants/index.js @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; + +import { connect } from 'react-redux'; +import { Typography } from '@material-ui/core'; + +import PageWrapper from 'components/PageWrapper'; +import Divider from 'components/generic/Divider'; +import MaterialTabsLayout from 'components/layouts/MaterialTabsLayout'; +import PageHeader from 'components/generic/PageHeader'; + +import * as OrganiserSelectors from 'redux/organiser/selectors'; + +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 }) => { + return ( + + + + }, + { + label: 'Teams', + content: + }, + { + label: 'Assigned to you', + content: + }, + { + label: 'Travel', + content: + }, + { + label: 'Admin & Tools', + content: + } + ]} + /> + + ); +}; + +const mapState = state => ({ + organisers: OrganiserSelectors.organisers(state), + event: OrganiserSelectors.event(state), + registrationsLoading: OrganiserSelectors.registrationsLoading(state) +}); + +export default connect(mapState)(OrganiserEditEventReview); 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 77% rename from frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js rename to frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/index.js index 750dd16d6..904243b0b 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/Stats/index.js @@ -1,32 +1,29 @@ 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'; import ReviewedAverage from 'components/plots/ReviewedAverage'; import ApplicationsLast24h from 'components/plots/ApplicationsLast24h'; -const OrganiserEditEventStats = ({ slug, loading, updateRegistrations, updateTeams }) => { - useEffect(() => { - updateRegistrations(slug); - updateTeams(slug); - }, [slug, updateRegistrations, updateTeams]); - - const renderContent = () => { - return ( +const OrganiserEditEventStats = ({ slug, loading }) => { + return ( + + @@ -77,12 +74,6 @@ const OrganiserEditEventStats = ({ slug, loading, updateRegistrations, updateTea - ); - }; - - return ( - - Key stats for the event

} footer={renderContent()} />
); }; @@ -94,12 +85,4 @@ const mapState = state => ({ OrganiserSelectors.organisersLoading(state) }); -const mapDispatch = dispatch => ({ - updateRegistrations: slug => dispatch(OrganiserActions.updateRegistrationsForEvent(slug)), - updateTeams: slug => dispatch(OrganiserActions.updateTeamsForEvent(slug)) -}); - -export default connect( - mapState, - mapDispatch -)(OrganiserEditEventStats); +export default connect(mapState)(OrganiserEditEventStats); diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js index 9fcf64bd3..6cc93f811 100644 --- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js +++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import styles from './OrganiserEditEvent.module.scss'; import { connect } from 'react-redux'; @@ -9,31 +9,52 @@ 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 DetailsPage from './Details'; +import StatsPage from './Stats'; +import ParticipantsPage from './Participants'; +import ManagePage from './Manage'; import SidebarLayout from 'components/layouts/SidebarLayout'; -const OrganiserEditEvent = ({ updateEvent, updateOrganiserProfiles, event, user, match, location }) => { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(false); +const OrganiserEditEvent = ({ + updateEvent, + updateOrganiserProfiles, + updateRegistrations, + updateTeams, + updateFilterGroups, + loading, + error, + event, + user, + match, + location +}) => { const { slug } = match.params; useEffect(() => { - setLoading(true); - updateEvent(slug) - .catch(err => { - setError(true); - }) - .finally(() => { - setLoading(false); - }); - }, [updateEvent, slug]); + updateEvent(slug); + }, [slug, updateEvent]); + + const updateData = useCallback(() => { + if (event.owner) { + updateOrganiserProfiles(event.owner, event.organisers); + updateRegistrations(slug); + updateTeams(slug); + updateFilterGroups(slug); + } + }, [ + slug, + event.owner, + event.organisers, + updateTeams, + updateRegistrations, + updateOrganiserProfiles, + updateFilterGroups + ]); useEffect(() => { - updateOrganiserProfiles(event.owner, event.organisers); - }, [event.owner, event.organisers, updateOrganiserProfiles]); + updateData(); + }, [updateData]); return ( @@ -62,25 +83,25 @@ const OrganiserEditEvent = ({ updateEvent, updateOrganiserProfiles, event, user, path: '', icon: 'home', label: 'Edit', - render: routeProps => + render: routeProps => }, { path: '/stats', icon: 'line-chart', label: 'Stats', - render: routeProps => + render: routeProps => }, { - path: '/review', + path: '/participants', icon: 'star', - label: 'Review', - render: routeProps => + label: 'Participants', + render: routeProps => }, { path: '/manage', icon: 'setting', label: 'Manage', - render: routeProps => + render: routeProps => } ]} /> @@ -91,13 +112,18 @@ const OrganiserEditEvent = ({ updateEvent, updateOrganiserProfiles, event, user, const mapStateToProps = state => ({ idToken: AuthSelectors.getIdToken(state), user: AuthSelectors.getCurrentUser(state), - event: OrganiserSelectors.event(state) + event: OrganiserSelectors.event(state), + loading: OrganiserSelectors.eventLoading(state), + error: OrganiserSelectors.eventError(state) }); const mapDispatchToProps = dispatch => ({ updateEvent: slug => dispatch(OrganiserActions.updateEvent(slug)), updateOrganiserProfiles: (owner, organisers) => - dispatch(OrganiserActions.updateOrganisersForEvent(owner, organisers)) + dispatch(OrganiserActions.updateOrganisersForEvent(owner, organisers)), + updateRegistrations: slug => dispatch(OrganiserActions.updateRegistrationsForEvent(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 d5a6192ab..1ba63684d 100644 --- a/frontend/src/redux/organiser/actionTypes.js +++ b/frontend/src/redux/organiser/actionTypes.js @@ -9,6 +9,10 @@ export const ADD_ORGANISER = 'organiser/ADD_ORGANISER'; export const UPDATE_REGISTRATIONS = 'organiser/UPDATE_REGISTRATIONS'; 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_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 23fdd7050..025287b7c 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 FilterGroupsService from 'services/filterGroups'; /** Update event with loading/error data */ export const updateEvent = slug => async (dispatch, getState) => { @@ -128,10 +129,56 @@ export const updateTeamsForEvent = slug => async (dispatch, getState) => { }); }; -/** Set filters for attendees table */ -export const setRegistrationsFilters = filters => dispatch => { +/** 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.SET_REGISTRATIONS_FILTERS, - payload: filters + 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 e636ee673..7fffad80f 100644 --- a/frontend/src/redux/organiser/reducer.js +++ b/frontend/src/redux/organiser/reducer.js @@ -28,14 +28,19 @@ const initialState = { error: false, updated: 0, data: [], - map: {}, - filters: [] + map: {} }, teams: { loading: false, error: false, updated: 0, data: [] + }, + filterGroups: { + loading: false, + error: false, + updated: 0, + data: [] } }; @@ -44,6 +49,7 @@ const eventHandler = buildHandler('event'); const statsHandler = buildHandler('stats'); const organisersHandler = buildHandler('organisers', 'userId'); const registrationsHandler = buildHandler('registrations', 'user'); +const filterGroupsHandler = buildHandler('filterGroups'); const teamsHandler = buildHandler('teams'); const editEvent = buildUpdatePath('event.data'); const editEventOrganisers = buildUpdatePath('event.data.organisers'); @@ -68,6 +74,46 @@ export default function reducer(state = initialState, action) { case ActionTypes.UPDATE_TEAMS: { return teamsHandler(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.EDIT_REGISTRATION: { const registration = action.payload; return { @@ -87,15 +133,6 @@ export default function reducer(state = initialState, action) { } }; } - case ActionTypes.SET_REGISTRATIONS_FILTERS: { - return { - ...state, - registrations: { - ...state.registrations, - filters: action.payload - } - }; - } case ActionTypes.REMOVE_ORGANISER: { const data = filter(state.event.data.organisers, userId => { return userId !== action.payload; diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js index 706f47683..973431389 100644 --- a/frontend/src/redux/organiser/selectors.js +++ b/frontend/src/redux/organiser/selectors.js @@ -1,6 +1,6 @@ import { createSelector } from 'reselect'; -import { meanBy, countBy, groupBy, mapValues } from 'lodash-es'; -import * as FilterUtils from 'utils/filters'; +import { meanBy, countBy, groupBy, mapValues, sumBy } from 'lodash-es'; +import { RegistrationStatuses } from '@hackjunction/shared'; import * as AuthSelectors from 'redux/auth/selectors'; import moment from 'moment'; @@ -25,15 +25,16 @@ export const registrationsMap = state => state.organiser.registrations.map; export const registrationsLoading = state => state.organiser.registrations.loading; 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 registrationsFiltered = createSelector( - registrations, - registrationsFilters, - (registrations, filters) => { - return FilterUtils.applyFilters(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 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, @@ -54,10 +55,50 @@ 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 registrationsEligibleForTravelGrant = createSelector( + registrationsConfirmed, + registrations => + registrations.filter(r => { + return !r.travelGrant && r.travelGrant !== 0 && r.answers.needsTravelGrant; + }) +); + +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; + }); + } +); + +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, 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/frontend/src/services/registrations.js b/frontend/src/services/registrations.js index 16297882c..c63e32f48 100644 --- a/frontend/src/services/registrations.js +++ b/frontend/src/services/registrations.js @@ -68,6 +68,17 @@ 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)); +}; + +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/styles/main.scss b/frontend/src/styles/main.scss index e624712bc..70ba590f4 100644 --- a/frontend/src/styles/main.scss +++ b/frontend/src/styles/main.scss @@ -24,6 +24,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 16px; + padding: 0 !important; } body.body { @@ -39,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; } 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..025d28272 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "dev:backend": "cd backend && npm run dev", "db-sync:local": "sh ./scripts/sync-production-to-local.sh", "db-sync:dev": "sh ./scripts/sync-production-to-dev.sh", - "db-sync:staging": "sh ./scripts/sync-production-to-staging.sh" + "db-sync:staging": "sh ./scripts/sync-production-to-staging.sh", + "pre-commit:frontend": "cd frontend && npm run precommit", + "pre-commit:backend": "cd backend && npm run precommit", + "pre-commit:shared": "cd shared && npm run precommit" }, "betterScripts": { "setup": "better-npm-run setup:backend && better-npm-run setup:frontend", @@ -23,10 +26,16 @@ "start:prod": "cd backend && npm start", "build": "rm -rf ./backend/build && cd frontend && npm run build && cp -r ./build ../backend/build" }, + "pre-commit": [ + "pre-commit:frontend", + "pre-commit:backend", + "pre-commit:shared" + ], "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", 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 diff --git a/shared/constants/field-types.js b/shared/constants/field-types.js new file mode 100644 index 000000000..5a7aaa127 --- /dev/null +++ b/shared/constants/field-types.js @@ -0,0 +1,70 @@ +const FieldTypes = { + SHORT_TEXT: { + id: 'SHORT_TEXT' + }, + LONG_TEXT: { + id: 'LONG_TEXT' + }, + BOOLEAN: { + id: 'BOOLEAN' + }, + EMAIL: { + id: 'EMAIL' + }, + PHONE_NUMBER: { + id: 'PHONE_NUMBER' + }, + DATE: { + id: 'DATE' + }, + GENDER: { + id: 'GENDER' + }, + NATIONALITY: { + id: 'NATIONALITY' + }, + LANGUAGES: { + id: 'LANGUAGES' + }, + COUNTRY: { + id: 'COUNTRY' + }, + ROLES: { + id: 'ROLES' + }, + SKILLS: { + id: 'SKILLS' + }, + INDUSTRIES: { + id: 'INDUSTRIES' + }, + THEMES: { + id: 'THEMES' + }, + EDUCATION: { + id: 'EDUCATION' + }, + SMALL_NUMBER: { + id: 'SMALL_NUMBER' + }, + NUM_HACKATHONS: { + id: 'NUM_HACKATHONS' + }, + T_SHIRT_SIZE: { + id: 'T_SHIRT_SIZE' + }, + URL: { + id: 'URL' + }, + DIETARY_RESTRICTIONS: { + id: 'DIETARY_RESTRICTIONS' + }, + TEAM_OPTIONS: { + id: 'TEAM_OPTIONS' + }, + RECRUITMENT_OPTIONS: { + id: 'RECRUITMENT_OPTIONS' + } +}; + +module.exports = FieldTypes; diff --git a/shared/constants/filter-types.js b/shared/constants/filter-types.js new file mode 100644 index 000000000..3bcae4ece --- /dev/null +++ b/shared/constants/filter-types.js @@ -0,0 +1,134 @@ +const filterTypes = { + IS_EMPTY: { + id: 'IS_EMPTY', + label: 'Is empty' + }, + NOT_EMPTY: { + id: 'NOT_EMPTY', + label: "Isn't empty" + }, + EQUALS: { + id: 'EQUALS', + label: 'Is equal to' + }, + NOT_EQUALS: { + 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' + }, + NOT_CONTAINS: { + 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', + helper: 'Or length is less than' + }, + NOT_LESS_THAN: { + id: 'NOT_LESS_THAN', + label: 'Is at least', + helper: 'Or length is at least' + }, + MORE_THAN: { + id: 'MORE_THAN', + label: 'Is more than', + helper: 'Or length is more than' + }, + NOT_MORE_THAN: { + id: 'NOT_MORE_THAN', + label: 'Is at most', + helper: 'Or length is at most' + }, + BOOLEAN_TRUE: { + id: 'BOOLEAN_TRUE', + label: 'Yes' + }, + BOOLEAN_FALSE: { + id: 'BOOLEAN_FALSE', + label: 'No', + helper: 'Or unanswered' + } +}; + +const stringFilterTypes = [ + filterTypes.IS_EMPTY.id, + filterTypes.NOT_EMPTY.id, + filterTypes.EQUALS.id, + 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, + filterTypes.NOT_MORE_THAN.id +]; + +const arrayFilterTypes = [ + filterTypes.IS_EMPTY.id, + filterTypes.NOT_EMPTY.id, + filterTypes.CONTAINS.id, + filterTypes.NOT_CONTAINS.id, + filterTypes.LESS_THAN.id, + filterTypes.NOT_LESS_THAN.id, + filterTypes.MORE_THAN.id, + filterTypes.NOT_MORE_THAN.id, + filterTypes.CONTAINS_ONE_OF.id, + filterTypes.NOT_CONTAINS_ONE_OF.id +]; + +const numberFilterTypes = [ + filterTypes.IS_EMPTY.id, + filterTypes.NOT_EMPTY.id, + filterTypes.EQUALS.id, + filterTypes.NOT_EQUALS.id, + filterTypes.LESS_THAN.id, + filterTypes.NOT_LESS_THAN.id, + filterTypes.MORE_THAN.id, + filterTypes.NOT_MORE_THAN.id +]; + +const booleanFilterTypes = [filterTypes.BOOLEAN_TRUE, filterTypes.BOOLEAN_FALSE]; + +const STRING = 'STRING'; +const ARRAY = 'ARRAY'; +const NUMBER = 'NUMBER'; +const BOOLEAN = 'BOOLEAN'; +const DATE = 'DATE'; + +module.exports = { + filterTypes, + filterTypesForType: { + STRING: stringFilterTypes, + ARRAY: arrayFilterTypes, + NUMBER: numberFilterTypes, + BOOLEAN: booleanFilterTypes, + DATE: [] + }, + STRING, + ARRAY, + NUMBER, + BOOLEAN, + DATE +}; diff --git a/shared/constants/filter-values.js b/shared/constants/filter-values.js new file mode 100644 index 000000000..61f790a06 --- /dev/null +++ b/shared/constants/filter-values.js @@ -0,0 +1,15 @@ +const FilterValues = { + STRING: 'STRING', + BOOLEAN: 'BOOLEAN', + NUMBER: 'NUMBER', + RATING: 'RATING', + DATE: 'DATE', + GENDER: 'GENDER', + NATIONALITY: 'NATIONALITY', + COUNTRY: 'COUNTRY', + LANGUAGE: 'LANGUAGE', + STATUS: 'STATUS', + TAG: 'TAG' +}; + +module.exports = FilterValues; diff --git a/shared/constants/misc.js b/shared/constants/misc.js index 7d7c3c49f..ccccf9c69 100644 --- a/shared/constants/misc.js +++ b/shared/constants/misc.js @@ -57,6 +57,21 @@ const relocationOptions = { } }; +const travelGrantStatuses = { + accepted: { + id: 'accepted', + label: 'Accepted' + }, + rejected: { + id: 'rejected', + label: 'Rejected' + }, + confirmed: { + id: 'confirmed', + label: 'Confirmed' + } +}; + const dietaryRestrictions = [ 'Vegan', 'Vegetarian', @@ -91,6 +106,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; diff --git a/shared/constants/registration-fields.js b/shared/constants/registration-fields.js index 95df860d3..051f5a239 100644 --- a/shared/constants/registration-fields.js +++ b/shared/constants/registration-fields.js @@ -6,6 +6,9 @@ const Themes = require('../constants/themes'); const Roles = require('../constants/roles'); const Skills = require('../constants/skills'); const Misc = require('../constants/misc'); +const FieldTypes = require('./field-types'); +const FilterTypes = require('./filter-types'); +const FilterValues = require('./filter-values'); const Categories = { basicDetails: { @@ -34,75 +37,6 @@ const Categories = { } }; -const FieldTypes = { - SHORT_TEXT: { - id: 'SHORT_TEXT' - }, - LONG_TEXT: { - id: 'LONG_TEXT' - }, - BOOLEAN: { - id: 'BOOLEAN' - }, - EMAIL: { - id: 'EMAIL' - }, - PHONE_NUMBER: { - id: 'PHONE_NUMBER' - }, - DATE: { - id: 'DATE' - }, - GENDER: { - id: 'GENDER' - }, - NATIONALITY: { - id: 'NATIONALITY' - }, - LANGUAGES: { - id: 'LANGUAGES' - }, - COUNTRY: { - id: 'COUNTRY' - }, - ROLES: { - id: 'ROLES' - }, - SKILLS: { - id: 'SKILLS' - }, - INDUSTRIES: { - id: 'INDUSTRIES' - }, - THEMES: { - id: 'THEMES' - }, - EDUCATION: { - id: 'EDUCATION' - }, - SMALL_NUMBER: { - id: 'SMALL_NUMBER' - }, - NUM_HACKATHONS: { - id: 'NUM_HACKATHONS' - }, - T_SHIRT_SIZE: { - id: 'T_SHIRT_SIZE' - }, - URL: { - id: 'URL' - }, - DIETARY_RESTRICTIONS: { - id: 'DIETARY_RESTRICTIONS' - }, - TEAM_OPTIONS: { - id: 'TEAM_OPTIONS' - }, - RECRUITMENT_OPTIONS: { - id: 'RECRUITMENT_OPTIONS' - } -}; - const FieldProps = { firstName: { label: 'First name', @@ -120,7 +54,15 @@ const FieldProps = { defaultEnable: true, defaultRequire: true, editable: false - } + }, + filters: [ + { + path: '', + label: 'First name', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, lastName: { label: 'Last name', @@ -138,7 +80,15 @@ const FieldProps = { defaultEnable: true, defaultRequire: true, editable: false - } + }, + filters: [ + { + path: '', + label: 'Last name', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, email: { label: 'Email', @@ -155,7 +105,15 @@ const FieldProps = { defaultEnable: true, defaultRequire: true, editable: false - } + }, + filters: [ + { + path: '', + label: 'Email', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] }, phoneNumber: { label: 'Phone number', @@ -188,7 +146,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Date of birth', + type: FilterTypes.DATE, + valueType: FilterValues.DATE + } + ] }, gender: { label: 'Gender', @@ -203,7 +169,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Gender', + type: FilterTypes.STRING, + valueType: FilterValues.GENDER + } + ] }, nationality: { label: 'Nationality', @@ -218,7 +192,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Nationality', + type: FilterTypes.STRING, + valueType: FilterValues.NATIONALITY + } + ] }, spokenLanguages: { label: 'Spoken languages', @@ -235,7 +217,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Spoken languages', + type: FilterTypes.ARRAY, + valueType: FilterValues.LANGUAGE + } + ] }, countryOfResidence: { label: 'Country of residence', @@ -250,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', @@ -424,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', @@ -440,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', @@ -456,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', @@ -471,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', @@ -483,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', @@ -494,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?', @@ -506,7 +552,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?', @@ -523,7 +577,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?', @@ -535,7 +597,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Needs accommodation', + type: FilterTypes.BOOLEAN, + valueType: FilterValues.BOOLEAN + } + ] }, recruitmentOptions: { label: 'Job opportunities', @@ -563,7 +633,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', @@ -575,7 +659,15 @@ const FieldProps = { defaultEnable: false, defaultRequire: false, editable: true - } + }, + filters: [ + { + path: '', + label: 'Secret Code', + type: FilterTypes.STRING, + valueType: FilterValues.STRING + } + ] } }; @@ -978,6 +1070,45 @@ function buildFieldToLabelMap() { return result; } +function buildFiltersArray() { + const fields = Object.keys(Fields); + const baseFilters = [ + { + path: 'rating', + label: 'Rating', + type: FilterTypes.NUMBER, + valueType: FilterValues.NUMBER + }, + { + path: 'status', + label: 'Status', + type: FilterTypes.STRING, + valueType: FilterValues.STATUS + }, + { + path: 'tags', + label: 'Tags', + type: FilterTypes.ARRAY, + valueType: FilterValues.TAG + } + ]; + const answerFilters = fields.reduce((res, fieldKey) => { + const field = Fields[fieldKey]; + if (!Array.isArray(field.filters) || !field.filters.length) return res; + const filters = field.filters.map(filter => { + if (filter.path.length) { + filter.path = `answers.${fieldKey}.${filter.path}`; + } else { + filter.path = `answers.${fieldKey}`; + } + return filter; + }); + return res.concat(filters); + }, []); + + return baseFilters.concat(answerFilters); +} + const Helpers = { getLabel: field => { if (Fields.hasOwnProperty(field)) { @@ -987,6 +1118,8 @@ const Helpers = { }, getFields: () => Fields, getField: field => Fields[field], + getFieldType: field => (Fields[field] ? Fields[field].fieldType.id : null), + filters: buildFiltersArray(), fieldToLabelMap: buildFieldToLabelMap(), fieldTypes: FieldTypes, getCategory: field => { diff --git a/shared/constants/select-options.js b/shared/constants/select-options.js new file mode 100644 index 000000000..f9101679b --- /dev/null +++ b/shared/constants/select-options.js @@ -0,0 +1,49 @@ +const Countries = require('./countries'); +const Genders = require('./genders'); +const Industries = require('./industries'); +const Languages = require('./languages'); +const Roles = require('./roles'); +const Skills = require('./skills'); +const Themes = require('./themes'); +const RegistrationStatuses = require('./registration-statuses'); + +const SelectOptions = { + COUNTRIES: Countries.asArrayOfName.map(country => ({ + label: country, + value: country + })), + NATIONALITIES: Countries.asArrayOfNationalities.map(nationality => ({ + label: nationality, + value: nationality + })), + GENDERS: Genders.map(gender => ({ + label: gender, + value: gender + })), + INDUSTRIES: Industries.industries.map(industry => ({ + label: industry, + value: industry + })), + LANGUAGES: Languages.asArrayOfNames.map(language => ({ + label: language, + value: language + })), + ROLES: Roles.items.map(role => ({ + label: role, + value: role + })), + SKILLS: Skills.items.map(skill => ({ + label: skill, + value: skill + })), + THEMES: Themes.themes.map(theme => ({ + label: theme, + value: theme + })), + STATUSES: RegistrationStatuses.asArray.map(status => ({ + label: status.label, + value: status.id + })) +}; + +module.exports = SelectOptions; 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); } diff --git a/shared/helpers/filterFunctions.js b/shared/helpers/filterFunctions.js new file mode 100644 index 000000000..2a5c977f0 --- /dev/null +++ b/shared/helpers/filterFunctions.js @@ -0,0 +1,151 @@ +const objectPath = require('object-path'); +const _ = require('lodash'); + +const _isEmpty = value => { + switch (typeof value) { + case 'object': + return _.isEmpty(value); + case 'number': + return isNaN(value); + case 'string': + return value.length === 0; + case 'undefined': + return true; + default: + return false; + } +}; + +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); + case 'number': + return value === targetValue; + case 'string': + if (typeof targetValue === 'string') { + return value.trim().toLowerCase() === targetValue.trim().toLowerCase(); + } else { + return value.trim().toLowerCase() == targetValue; + } + case 'undefined': + default: + return false; + } +}; + +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 ( + value + .trim() + .toLowerCase() + .indexOf(targetValue.trim().toLowerCase()) !== -1 + ); + } + return false; + } + + if (Array.isArray(value)) { + return value.indexOf(targetValue) !== -1; + } + + return false; +}; + +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; + } + const numTarget = parseInt(targetValue); + + if (isNaN(numValue) || isNaN(numTarget)) return false; + + return numValue >= numTarget; +}; + +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; + } + const numTarget = parseInt(targetValue); + + if (isNaN(numValue) || isNaN(numTarget)) return false; + + 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, + isOneOf, + contains, + containsOneOf, + _isEmpty, + _isEqualTo, + _isGte, + _isLte, + _isOneOf, + _contains, + _containsOneOf +}; diff --git a/shared/helpers/filterHelpers.js b/shared/helpers/filterHelpers.js new file mode 100644 index 000000000..c58e15bea --- /dev/null +++ b/shared/helpers/filterHelpers.js @@ -0,0 +1,100 @@ +const _FilterTypes = require('../constants/filter-types'); +const FilterFunctions = require('./filterFunctions'); + +const FilterTypes = _FilterTypes.filterTypes; + +const buildFiltersArray = filters => { + return filters.map((filter = {}) => { + switch (filter.type) { + case FilterTypes.NOT_EMPTY.id: { + return item => { + return !FilterFunctions.isEmpty(item, filter.path); + }; + } + case FilterTypes.IS_EMPTY.id: { + return item => { + return FilterFunctions.isEmpty(item, filter.path); + }; + } + case FilterTypes.EQUALS.id: { + return item => { + return FilterFunctions.isEqualTo(item, filter.path, filter.value); + }; + } + case FilterTypes.NOT_EQUALS.id: { + return item => { + 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); + }; + } + case FilterTypes.NOT_CONTAINS.id: { + return item => { + 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); + }; + } + case FilterTypes.NOT_LESS_THAN.id: { + return item => { + return FilterFunctions.isGte(item, filter.path, filter.value); + }; + } + case FilterTypes.MORE_THAN.id: { + return item => { + return !FilterFunctions.isLte(item, filter.path, filter.value); + }; + } + case FilterTypes.NOT_MORE_THAN.id: { + return item => { + return FilterFunctions.isLte(item, filter.path, filter.value); + }; + } + case FilterTypes.BOOLEAN_TRUE.id: + case FilterTypes.BOOLEAN_FALSE.id: + default: + return () => true; + } + }); +}; + +const applyFilters = (items, filters) => { + const filtersArray = buildFiltersArray(filters); + + return items.filter(item => { + for (let filter of filtersArray) { + if (!filter(item)) return false; + } + return true; + }); +}; + +module.exports = { + applyFilters +}; diff --git a/shared/index.js b/shared/index.js index 84e83b68f..8bc146a20 100644 --- a/shared/index.js +++ b/shared/index.js @@ -9,11 +9,16 @@ 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'), RegistrationFields: require('./constants/registration-fields'), RegistrationStatuses: require('./constants/registration-statuses'), RegistrationValidator: require('./helpers/registrationValidator'), Roles: require('./constants/roles'), Skills: require('./constants/skills'), + SelectOptions: require('./constants/select-options'), Themes: require('./constants/themes'), Universities: require('./constants/universities'), Utils: require('./helpers/utils') diff --git a/shared/package-lock.json b/shared/package-lock.json index eb0651471..6f3ad5873 100644 --- a/shared/package-lock.json +++ b/shared/package-lock.json @@ -1,5 +1,1004 @@ { "name": "@hackjunction/shared", "version": "1.1.45", - "lockfileVersion": 1 + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "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" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "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=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "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 + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", + "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.0.0", + "string.prototype.trimright": "^2.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "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" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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 + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "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 + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "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=", + "dev": true + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-path": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", + "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "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 + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.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 + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "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.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "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.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + } + } } diff --git a/shared/package.json b/shared/package.json index c25a3ff65..fef368a3f 100644 --- a/shared/package.json +++ b/shared/package.json @@ -4,10 +4,18 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha", + "precommit": "npm run test" }, "author": "Juuso Lappalainen", "license": "ISC", + "dependencies": { + "lodash": "^4.17.15", + "object-path": "^0.11.4" + }, + "devDependencies": { + "mocha": "^6.2.0" + }, "engines": { "node": "12.3.1", "npm": "6.9.0" diff --git a/shared/test/filterFunctions.test.js b/shared/test/filterFunctions.test.js new file mode 100644 index 000000000..64b6fa3c1 --- /dev/null +++ b/shared/test/filterFunctions.test.js @@ -0,0 +1,439 @@ +/* eslint-disable */ + +const assert = require('assert'); +const filterFunctions = require('../helpers/filterFunctions.js'); + +const EMPTY_STRING = ''; +const STRING = 'Hello world'; +const EMPTY_ARRAY = []; +const ARRAY = ['one', 'two', 'three']; +const EMPTY_OBJECT = {}; +const OBJECT = { + foo: 'bar' +}; +const ZERO_NUMBER = 0; +const NUMBER = 777; +const STRING_ZERO_NUMBER = '0'; +const STRING_NUMBER = '777'; + +const testObject = { + emptyString: EMPTY_STRING, + string: STRING, + stringPadded: STRING + ' ', + emptyObject: EMPTY_OBJECT, + object: OBJECT, + emptyArray: EMPTY_ARRAY, + array: ARRAY, + zeroNumber: ZERO_NUMBER, + stringZeroNumber: STRING_ZERO_NUMBER, + number: NUMBER, + stringNumber: STRING_NUMBER, + stringNumberPadded: STRING_NUMBER + ' ' +}; + +const nestedTestObject = { + nested: testObject +}; + +describe('Filter functions', function() { + describe('isEmpty', function() { + it('should return true when value is empty string', function() { + const value = filterFunctions.isEmpty(testObject, 'emptyString'); + assert.equal(value, true); + }); + + it('should return true when value is nested empty string', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.emptyString'); + assert.equal(value, true); + }); + + it('should return true when value is empty array', function() { + const value = filterFunctions.isEmpty(testObject, 'emptyArray'); + assert.equal(value, true); + }); + + it('should return true when value is nested empty array', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.emptyArray'); + assert.equal(value, true); + }); + + it('should return true when value is empty object', function() { + const value = filterFunctions.isEmpty(testObject, 'emptyObject'); + assert.equal(value, true); + }); + + it('should return true when value is nested empty object', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.emptyObject'); + assert.equal(value, true); + }); + + it('should return true when value is undefined', function() { + const value = filterFunctions.isEmpty(testObject, 'nonExistingPath'); + assert.equal(value, true); + }); + + it('should return true when value is nested undefined', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.nonExistingPath'); + assert.equal(value, true); + }); + + it('should return false when value is non-empty string', function() { + const value = filterFunctions.isEmpty(testObject, 'string'); + assert.equal(value, false); + }); + + it('should return false when value is nested non-empty string', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.string'); + assert.equal(value, false); + }); + + it('should return false when value is non-empty array', function() { + const value = filterFunctions.isEmpty(testObject, 'array'); + assert.equal(value, false); + }); + + it('should return false when value is nested non-empty array', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.array'); + assert.equal(value, false); + }); + + it('should return false when value is non-empty object', function() { + const value = filterFunctions.isEmpty(testObject, 'object'); + assert.equal(value, false); + }); + + it('should return false when value is nested non-empty object', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.object'); + assert.equal(value, false); + }); + + it('should return false when value is zero', function() { + const value = filterFunctions.isEmpty(testObject, 'zeroNumber'); + assert.equal(value, false); + }); + + it('should return false when value is nested zero', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.zeroNumber'); + assert.equal(value, false); + }); + + it('should return false when value is non-zero number', function() { + const value = filterFunctions.isEmpty(testObject, 'number'); + assert.equal(value, false); + }); + + it('should return false when value is nested non-zero number', function() { + const value = filterFunctions.isEmpty(nestedTestObject, 'nested.number'); + assert.equal(value, false); + }); + }); + + describe('isEqualTo', function() { + describe('strings', function() { + it('should return true when comparing two empty strings', function() { + const value = filterFunctions.isEqualTo(testObject, 'emptyString', ''); + assert.equal(value, true); + }); + + it('should return true when comparing two matching strings', function() { + const value = filterFunctions.isEqualTo(testObject, 'string', STRING); + assert.equal(value, true); + }); + it('should return true when comparing two matching strings with extra whitespace', function() { + const value = filterFunctions.isEqualTo(testObject, 'stringPadded', ' ' + STRING); + assert.equal(value, true); + }); + it('should return false when comparing two non-matching strings', function() { + const value = filterFunctions.isEqualTo(testObject, 'string', 'different string'); + assert.equal(value, false); + }); + it('should return false when comparing empty string to non-empty string', function() { + const value = filterFunctions.isEqualTo(testObject, 'emptyString', STRING); + assert.equal(value, false); + }); + it('should return false when comparing non-empty string to arbitrary number', function() { + const value = filterFunctions.isEqualTo(testObject, 'string', NUMBER); + assert.equal(value, false); + }); + }); + + describe('numbers', function() { + it('should return true when comparing two zeros', function() { + const value = filterFunctions.isEqualTo(testObject, 'zeroNumber', ZERO_NUMBER); + assert.equal(value, true); + }); + it('should return true when comparing two arbitrary matching numbers', function() { + const value = filterFunctions.isEqualTo(testObject, 'number', NUMBER); + assert.equal(value, true); + }); + it('should reutrn false when comparing two arbitrary non-matching numbers', function() { + const value = filterFunctions.isEqualTo( + testObject, + 'number', + NUMBER + Math.floor(Math.random() * 1000) + ); + assert.equal(value, false); + }); + it('should return true when comparing string number to matching number', function() { + const value = filterFunctions.isEqualTo(testObject, 'stringNumber', NUMBER); + assert.equal(value, true); + }); + it('should return false when comparing string number to non-matching number', function() { + const value = filterFunctions.isEqualTo(testObject, 'stringNumber', NUMBER + 1); + assert.equal(value, false); + }); + it('should return true when comparing string number with whitespace to matching number', function() { + const value = filterFunctions.isEqualTo(testObject, 'stringNumberPadded', NUMBER); + assert.equal(value, true); + }); + }); + describe('arrays', function() { + it('should return true when comparing two empty arrays', function() { + const value = filterFunctions.isEqualTo(testObject, 'emptyArray', []); + assert.equal(value, true); + }); + it('should return true when comparing two non-empty arrays', function() { + const value = filterFunctions.isEqualTo(testObject, 'array', ARRAY); + assert.equal(value, true); + }); + it('should return false when comparing two non-matching arrays', function() { + const value = filterFunctions.isEqualTo(testObject, 'array', ARRAY.concat('foo')); + assert.equal(value, false); + }); + it('should return false when comparing empty array to empty object', function() { + const value = filterFunctions.isEqualTo(testObject, 'emptyArray', {}); + assert.equal(value, false); + }); + }); + describe('objects', function() { + it('should return true when comparing two empty objects', function() { + const value = filterFunctions.isEqualTo(testObject, 'emptyObject', {}); + assert.equal(value, true); + }); + it('should return true when comparing two matching non-empty objects', function() { + const value = filterFunctions.isEqualTo(testObject, 'object', JSON.parse(JSON.stringify(OBJECT))); + assert.equal(value, true); + }); + it('should return false when comparing two non-matching objects', function() { + const value = filterFunctions.isEqualTo(testObject, 'object', { ...OBJECT, baz: 'bizz' }); + assert.equal(value, false); + }); + }); + describe('undefined', function() { + it('should return false when comparing undefined to anything, even undefined', function() { + const values = [ + filterFunctions.isEqualTo(testObject, 'some.undefined.path', []), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', {}), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', OBJECT), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', NUMBER), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', ZERO_NUMBER), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', STRING), + filterFunctions.isEqualTo(testObject, 'some.undefined.path', STRING_ZERO_NUMBER), + filterFunctions.isEqualTo(testObject, 'some.undefined.path') + ]; + assert.equal(values.indexOf(true), -1); + }); + }); + }); + describe('contains', function() { + describe('strings', function() { + it('should return true when a string value contains the substring, case-insensitive', function() { + const values = [ + filterFunctions.contains(testObject, 'string', 'hello'), + filterFunctions.contains(testObject, 'string', 'HeLLo') + ]; + + assert.equal(values.indexOf(false), -1); + }); + it('should return true when comparing a string value to an empty string', function() { + const value = filterFunctions.contains(testObject, 'string', ''); + assert.equal(value, true); + }); + it('should return false when comparing a string value to a non-matching string', function() { + const value = filterFunctions.contains(testObject, 'string', 'something not matching'); + assert.equal(value, false); + }); + it('should return false when comparing a string value to a number', function() { + const value = filterFunctions.contains(testObject, 'string', NUMBER); + assert.equal(value, false); + }); + }); + describe('arrays', function() { + it('should return true when the source array contains the target value', function() { + const values = [ + filterFunctions.contains(testObject, 'array', 'one'), + filterFunctions.contains(testObject, 'array', 'two') + ]; + assert.equal(values.indexOf(false), -1); + }); + it('should return false when the source array does not contain the target value', function() { + const value = filterFunctions.contains(testObject, 'array', 'four'); + assert.equal(value, false); + }); + it('should return false when the source array is empty', function() { + const values = [ + filterFunctions.contains(testObject, 'emptyArray', ''), + filterFunctions.contains(testObject, 'emptyArray', 'two') + ]; + assert.equal(values.indexOf(true), -1); + }); + }); + describe('others', function() { + it('should return false when the source array is not an array', function() { + const values = [ + filterFunctions.contains(testObject, 'object', ''), + filterFunctions.contains(testObject, 'object', 'foo'), + filterFunctions.contains(testObject, 'number', ''), + filterFunctions.contains(testObject, 'number', NUMBER), + filterFunctions.contains(testObject, 'something.undefined', ''), + filterFunctions.contains(testObject, 'something.undefined', undefined) + ]; + assert.equal(values.indexOf(true), -1); + }); + }); + }); + describe('isGte / isLte', function() { + describe('numbers', function() { + it('isGte should return true when the source number is equal', function() { + const value = filterFunctions.isGte(testObject, 'number', NUMBER); + assert.equal(value, true); + }); + + it('isGte should return true when the source number is larger than the target number', function() { + const value = filterFunctions.isGte(testObject, 'number', NUMBER - 1); + assert.equal(value, true); + }); + + it('isGte should return false when the source number is smaller than the target number', function() { + const value = filterFunctions.isGte(testObject, 'number', NUMBER + 1); + assert.equal(value, false); + }); + + it('isLte should return true when the source number is equal', function() { + const value = filterFunctions.isLte(testObject, 'number', NUMBER); + assert.equal(value, true); + }); + + it('isLte should return true when the source number is smaller than the target number', function() { + const value = filterFunctions.isLte(testObject, 'number', NUMBER + 1); + assert.equal(value, true); + }); + + it('isLte should return false when the source number is larger than the target number', function() { + const value = filterFunctions.isLte(testObject, 'number', NUMBER - 1); + assert.equal(value, false); + }); + }); + describe('arrays', function() { + it('isGte / isLte should work with arrays, and compare their length to the target number', function() { + const passingValues = [ + filterFunctions.isGte(testObject, 'array', ARRAY.length), + filterFunctions.isGte(testObject, 'array', ARRAY.length - 1), + filterFunctions.isGte(testObject, 'emptyArray', 0), + filterFunctions.isGte(testObject, 'array', '1'), + filterFunctions.isLte(testObject, 'array', ARRAY.length), + filterFunctions.isLte(testObject, 'array', ARRAY.length + 1), + filterFunctions.isLte(testObject, 'emptyArray', 0), + filterFunctions.isLte(testObject, 'emptyArray', 3), + filterFunctions.isLte(testObject, 'emptyArray', '0'), + filterFunctions.isLte(testObject, 'emptyArray', '7') + ]; + + const failingValues = [ + filterFunctions.isLte(testObject, 'array', ARRAY.length - 1), + filterFunctions.isGte(testObject, 'array', ARRAY.length + 1), + filterFunctions.isGte(testObject, 'array', 10), + filterFunctions.isGte(testObject, 'array', '10') + ]; + + assert.equal(passingValues.indexOf(false), -1); + assert.equal(failingValues.indexOf(true), -1); + }); + }); + describe('strings', function() { + it('isGte / isLte should work with strings, and compare their length to the target number', function() { + const passingValues = [ + filterFunctions.isGte(testObject, 'string', STRING.length - 1), + filterFunctions.isGte(testObject, 'string', STRING.length), + filterFunctions.isGte(testObject, 'emptyString', 0), + filterFunctions.isLte(testObject, 'string', STRING.length + 1), + filterFunctions.isLte(testObject, 'string', STRING.length), + filterFunctions.isLte(testObject, 'emptyString', 0) + ]; + const failingValues = [ + filterFunctions.isGte(testObject, 'string', STRING.length + 1), + filterFunctions.isLte(testObject, 'string', STRING.length - 1) + ]; + + assert.equal(passingValues.indexOf(false), -1); + assert.equal(failingValues.indexOf(true), -1); + }); + }); + describe('others', function() { + it('both should return false when the source number is an object or undefined', function() { + const values = [ + filterFunctions.isGte(testObject, 'object', 0), + filterFunctions.isGte(testObject, 'object', 1), + filterFunctions.isGte(testObject, 'something.undefined', 0) + ]; + assert.equal(values.indexOf(true), -1); + }); + it('both should return false when the target value is not a number', function() { + const values = [ + filterFunctions.isGte(testObject, 'string', OBJECT), + filterFunctions.isLte(testObject, 'string', OBJECT), + filterFunctions.isGte(testObject, 'string', ARRAY), + filterFunctions.isLte(testObject, 'string', ARRAY), + filterFunctions.isGte(testObject, 'string'), + filterFunctions.isLte(testObject, 'string'), + filterFunctions.isGte(testObject, 'emptyString'), + filterFunctions.isLte(testObject, 'emptyString') + ]; + + assert.equal(values.indexOf(true), -1); + }); + }); + }); + 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); + }); + }); +});