From 2b81310b9834610dea2c799510017d3b2347df96 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Fri, 20 Sep 2019 09:56:27 +0300
Subject: [PATCH 01/21] Add filter for rated registrations
---
.../AssignAttendeesPage.js | 22 +++++++++++++++----
.../AssignAttendeesPage.module.scss | 11 ++++++++++
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js
index 0c1b7152a..a4b30e9b7 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.js
@@ -1,7 +1,7 @@
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
import styles from './AssignAttendeesPage.module.scss';
-import { Button as AntButton, Modal, message } from 'antd';
+import { Button as AntButton, Modal, message, Switch } from 'antd';
import { connect } from 'react-redux';
import Divider from 'components/generic/Divider';
@@ -15,6 +15,7 @@ import RegistrationsService from 'services/registrations';
import BulkEditRegistrationDrawer from 'components/modals/BulkEditRegistrationDrawer';
const SearchAttendeesPage = ({ idToken, event, registrations = [], registrationsLoading, updateRegistrations }) => {
+ const [hideRated, setHideRated] = useState(false);
const { slug } = event;
const handleSelfAssign = () => {
Modal.confirm({
@@ -42,11 +43,24 @@ const SearchAttendeesPage = ({ idToken, event, registrations = [], registrations
});
};
+ const filtered = useMemo(() => {
+ return registrations.filter(registration => {
+ if (hideRated) {
+ if (registration.rating) return false;
+ }
+ return true;
+ });
+ }, [registrations, hideRated]);
+
return (
-
{registrations.length} registrations
+
{filtered.length} registrations
+
+ Hide rated registrations
+
+
Assign random registrations
@@ -54,7 +68,7 @@ const SearchAttendeesPage = ({ idToken, event, registrations = [], registrations
r._id)} />
-
+
);
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss
index 14e6227f4..df0249912 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AssignAttendeesPage.module.scss
@@ -15,3 +15,14 @@
flex-wrap: wrap;
justify-content: flex-end;
}
+
+.toggle {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 0 0.5rem;
+}
+
+.toggleText {
+ padding-right: 1rem;
+}
From 8867ca32aabbc852311f333b5931f7f659aa52c6 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Fri, 20 Sep 2019 10:43:37 +0300
Subject: [PATCH 02/21] Improve stats page
---
.../src/components/plots/ApplicationsCount.js | 15 +++
.../components/plots/ApplicationsLast24h.js | 15 +++
.../components/plots/ApplicationsOverTime.js | 8 +-
frontend/src/components/plots/RatingsSplit.js | 9 +-
.../src/components/plots/ReviewedAverage.js | 15 +++
.../src/components/plots/ReviewedPercent.js | 15 +++
.../src/components/plots/ReviewersList.js | 36 +++--
frontend/src/components/plots/TeamsCount.js | 15 +++
.../OrganiserEditEventStats/index.js | 125 +++++-------------
frontend/src/redux/organiser/selectors.js | 96 ++++++++++++++
10 files changed, 246 insertions(+), 103 deletions(-)
create mode 100644 frontend/src/components/plots/ApplicationsCount.js
create mode 100644 frontend/src/components/plots/ApplicationsLast24h.js
create mode 100644 frontend/src/components/plots/ReviewedAverage.js
create mode 100644 frontend/src/components/plots/ReviewedPercent.js
create mode 100644 frontend/src/components/plots/TeamsCount.js
diff --git a/frontend/src/components/plots/ApplicationsCount.js b/frontend/src/components/plots/ApplicationsCount.js
new file mode 100644
index 000000000..a4b681a32
--- /dev/null
+++ b/frontend/src/components/plots/ApplicationsCount.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { Statistic } from 'antd';
+import { connect } from 'react-redux';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const ApplicationsCount = ({ value }) => {
+ return ;
+};
+
+const mapState = state => ({
+ value: OrganiserSelectors.registrationsCount(state)
+});
+
+export default connect(mapState)(ApplicationsCount);
diff --git a/frontend/src/components/plots/ApplicationsLast24h.js b/frontend/src/components/plots/ApplicationsLast24h.js
new file mode 100644
index 000000000..b11c0c566
--- /dev/null
+++ b/frontend/src/components/plots/ApplicationsLast24h.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { Statistic } from 'antd';
+import { connect } from 'react-redux';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const ApplicationsLast24h = ({ value }) => {
+ return ;
+};
+
+const mapState = state => ({
+ value: OrganiserSelectors.registrationsLast24h(state)
+});
+
+export default connect(mapState)(ApplicationsLast24h);
diff --git a/frontend/src/components/plots/ApplicationsOverTime.js b/frontend/src/components/plots/ApplicationsOverTime.js
index 58e00f78b..bbb3dfe54 100644
--- a/frontend/src/components/plots/ApplicationsOverTime.js
+++ b/frontend/src/components/plots/ApplicationsOverTime.js
@@ -1,7 +1,9 @@
import React, { useMemo } from 'react';
import { sortBy } from 'lodash-es';
+import { connect } from 'react-redux';
import { ResponsiveContainer, BarChart, XAxis, YAxis, Tooltip, Legend, CartesianGrid, Bar } from 'recharts';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
const ApplicationsOverTime = ({ data }) => {
const formattedData = useMemo(() => {
@@ -39,4 +41,8 @@ const ApplicationsOverTime = ({ data }) => {
);
};
-export default ApplicationsOverTime;
+const mapState = state => ({
+ data: OrganiserSelectors.registrationsByDay(state)
+});
+
+export default connect(mapState)(ApplicationsOverTime);
diff --git a/frontend/src/components/plots/RatingsSplit.js b/frontend/src/components/plots/RatingsSplit.js
index efa98e3ac..43fc69e96 100644
--- a/frontend/src/components/plots/RatingsSplit.js
+++ b/frontend/src/components/plots/RatingsSplit.js
@@ -1,5 +1,8 @@
import React, { useMemo } from 'react';
import { sortBy } from 'lodash-es';
+import { connect } from 'react-redux';
+
+import * as OrganiserSelectors from 'redux/organiser/selectors';
import { ResponsiveContainer, BarChart, XAxis, YAxis, Tooltip, Legend, CartesianGrid, Bar } from 'recharts';
@@ -39,4 +42,8 @@ const RatingsSplit = ({ data }) => {
);
};
-export default RatingsSplit;
+const mapState = state => ({
+ data: OrganiserSelectors.registrationsByRating(state)
+});
+
+export default connect(mapState)(RatingsSplit);
diff --git a/frontend/src/components/plots/ReviewedAverage.js b/frontend/src/components/plots/ReviewedAverage.js
new file mode 100644
index 000000000..15ebe312a
--- /dev/null
+++ b/frontend/src/components/plots/ReviewedAverage.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { Statistic, Icon } from 'antd';
+import { connect } from 'react-redux';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const ReviewedAverage = ({ value }) => {
+ return } />;
+};
+
+const mapState = state => ({
+ value: OrganiserSelectors.averageRating(state)
+});
+
+export default connect(mapState)(ReviewedAverage);
diff --git a/frontend/src/components/plots/ReviewedPercent.js b/frontend/src/components/plots/ReviewedPercent.js
new file mode 100644
index 000000000..ec9e87db8
--- /dev/null
+++ b/frontend/src/components/plots/ReviewedPercent.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { Statistic } from 'antd';
+import { connect } from 'react-redux';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const ReviewedPercent = ({ value }) => {
+ return ;
+};
+
+const mapState = state => ({
+ value: OrganiserSelectors.percentReviewed(state)
+});
+
+export default connect(mapState)(ReviewedPercent);
diff --git a/frontend/src/components/plots/ReviewersList.js b/frontend/src/components/plots/ReviewersList.js
index e864a4e8b..72fc35ada 100644
--- a/frontend/src/components/plots/ReviewersList.js
+++ b/frontend/src/components/plots/ReviewersList.js
@@ -1,17 +1,27 @@
import React, { useMemo } from 'react';
import { List, Avatar } from 'antd';
-import { map, sortBy } from 'lodash-es';
+import { sortBy } from 'lodash-es';
+import { connect } from 'react-redux';
-const ReviewersList = ({ data, userProfilesMap = {} }) => {
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const ReviewersList = ({ data, userProfilesMap = {}, averagesMap = {} }) => {
const formattedData = useMemo(() => {
- const asArray = map(Object.keys(data), userId => ({
- user: userProfilesMap[userId] || {},
- ratings: data[userId]
- }));
+ const arr = [];
+ Object.keys(data).forEach(userId => {
+ const user = userProfilesMap[userId];
+ if (user) {
+ arr.push({
+ user,
+ ratings: data[userId],
+ average: averagesMap[userId]
+ });
+ }
+ });
- return sortBy(asArray, 'ratings');
- }, [data, userProfilesMap]);
+ return sortBy(arr, item => item.ratings * -1);
+ }, [data, userProfilesMap, averagesMap]);
return (
{
}
title={`${item.user.firstName} ${item.user.lastName}`}
- description={`${item.ratings} applications reviewed`}
+ description={`${item.ratings} reviews / ${item.average} average rating`}
/>
)}
@@ -30,4 +40,10 @@ const ReviewersList = ({ data, userProfilesMap = {} }) => {
);
};
-export default ReviewersList;
+const mapState = state => ({
+ data: OrganiserSelectors.registrationsByReviewer(state),
+ userProfilesMap: OrganiserSelectors.organisersMap(state),
+ averagesMap: OrganiserSelectors.reviewAverageByReviewer(state)
+});
+
+export default connect(mapState)(ReviewersList);
diff --git a/frontend/src/components/plots/TeamsCount.js b/frontend/src/components/plots/TeamsCount.js
new file mode 100644
index 000000000..d0f105de8
--- /dev/null
+++ b/frontend/src/components/plots/TeamsCount.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { Statistic } from 'antd';
+import { connect } from 'react-redux';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+
+const TeamsCount = ({ value }) => {
+ return ;
+};
+
+const mapState = state => ({
+ value: OrganiserSelectors.teamsCount(state)
+});
+
+export default connect(mapState)(TeamsCount);
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js
index 606854993..750dd16d6 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventStats/index.js
@@ -1,138 +1,79 @@
-import React, { useEffect, useCallback } from 'react';
+import React, { useEffect } from 'react';
import styles from './OrganiserEditEventStats.module.scss';
-import { isEmpty } from 'lodash-es';
import { connect } from 'react-redux';
-import moment from 'moment';
-import { PageHeader, Row, Col, Statistic, Card, List, Empty, Icon, Rate } from 'antd';
+import { PageHeader, Row, Col, Card } from 'antd';
-import Divider from 'components/generic/Divider';
-import Button from 'components/generic/Button';
-import PageWrapper from 'components/PageWrapper';
import * as OrganiserSelectors from 'redux/organiser/selectors';
import * as OrganiserActions from 'redux/organiser/actions';
+
+import Divider from 'components/generic/Divider';
+import PageWrapper from 'components/PageWrapper';
import ApplicationsOverTime from 'components/plots/ApplicationsOverTime';
import RatingsSplit from 'components/plots/RatingsSplit';
import ReviewersList from 'components/plots/ReviewersList';
-const OrganiserEditEventStats = ({ stats, statsLoading, slug, updateEventStats, organisersMap }) => {
- const updateStats = useCallback(() => {
- updateEventStats(slug);
- }, [slug, updateEventStats]);
+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';
- console.log(stats);
+const OrganiserEditEventStats = ({ slug, loading, updateRegistrations, updateTeams }) => {
+ useEffect(() => {
+ updateRegistrations(slug);
+ updateTeams(slug);
+ }, [slug, updateRegistrations, updateTeams]);
const renderContent = () => {
- if (isEmpty(stats)) {
- return (
-
-
-
- }
- />
- );
- }
-
return (
-
+
-
+
-
+
- }
- />
+
-
+
-
+
-
+
-
-
-
-
-
-
- (
-
-
-
- )}
- />
-
-
-
-
-
- (
-
-
-
- )}
- />
+
@@ -140,23 +81,25 @@ const OrganiserEditEventStats = ({ stats, statsLoading, slug, updateEventStats,
};
return (
-
+
Key stats for the event
} footer={renderContent()} />
);
};
-const mapStateToProps = state => ({
- stats: OrganiserSelectors.stats(state),
- statsLoading: OrganiserSelectors.statsLoading(state),
- organisersMap: OrganiserSelectors.organisersMap(state)
+const mapState = state => ({
+ loading:
+ OrganiserSelectors.registrationsLoading(state) ||
+ OrganiserSelectors.teamsLoading(state) ||
+ OrganiserSelectors.organisersLoading(state)
});
-const mapDispatchToProps = dispatch => ({
- updateEventStats: slug => dispatch(OrganiserActions.updateEventStats(slug))
+const mapDispatch = dispatch => ({
+ updateRegistrations: slug => dispatch(OrganiserActions.updateRegistrationsForEvent(slug)),
+ updateTeams: slug => dispatch(OrganiserActions.updateTeamsForEvent(slug))
});
export default connect(
- mapStateToProps,
- mapDispatchToProps
+ mapState,
+ mapDispatch
)(OrganiserEditEventStats);
diff --git a/frontend/src/redux/organiser/selectors.js b/frontend/src/redux/organiser/selectors.js
index 02c9dacc2..706f47683 100644
--- a/frontend/src/redux/organiser/selectors.js
+++ b/frontend/src/redux/organiser/selectors.js
@@ -1,6 +1,8 @@
import { createSelector } from 'reselect';
+import { meanBy, countBy, groupBy, mapValues } from 'lodash-es';
import * as FilterUtils from 'utils/filters';
import * as AuthSelectors from 'redux/auth/selectors';
+import moment from 'moment';
export const event = state => state.organiser.event.data;
export const eventLoading = state => state.organiser.event.loading;
@@ -43,6 +45,15 @@ export const registrationsAssigned = createSelector(
}
);
+export const registrationsReviewed = createSelector(
+ registrations,
+ registrations => {
+ return registrations.filter(registration => {
+ return !!registration.rating;
+ });
+ }
+);
+
export const teams = state => state.organiser.teams.data;
export const teamsLoading = state => state.organiser.teams.loading;
export const teamsError = state => state.organiser.teams.error;
@@ -61,3 +72,88 @@ export const teamsPopulated = createSelector(
});
}
);
+
+/** Stats selectors */
+export const registrationsCount = createSelector(
+ registrations,
+ registrations => registrations.length
+);
+
+export const teamsCount = createSelector(
+ teams,
+ teams => teams.length
+);
+
+export const percentReviewed = createSelector(
+ registrations,
+ registrations => {
+ const { reviewed, total } = registrations.reduce(
+ (res, registration) => {
+ res.total += 1;
+ if (registration.rating) {
+ res.reviewed += 1;
+ }
+ return res;
+ },
+ {
+ reviewed: 0,
+ total: 0
+ }
+ );
+ return (reviewed * 100) / total;
+ }
+);
+
+export const averageRating = createSelector(
+ registrationsReviewed,
+ registrations => {
+ return meanBy(registrations, 'rating');
+ }
+);
+
+export const registrationsLast24h = createSelector(
+ registrations,
+ registrations => {
+ return registrations.filter(registration => {
+ return registration.createdAt > Date.now() - 1000 * 60 * 60 * 24;
+ }).length;
+ }
+);
+
+export const registrationsByDay = createSelector(
+ registrations,
+ registrations => {
+ return countBy(registrations, r => moment(r.createdAt).format('YYYY-MM-DD'));
+ }
+);
+
+export const registrationsByRating = createSelector(
+ registrationsReviewed,
+ registrations => {
+ return countBy(registrations, 'rating');
+ }
+);
+
+export const registrationsByReviewer = createSelector(
+ registrationsReviewed,
+ registrations => {
+ return countBy(registrations, 'ratedBy');
+ }
+);
+
+export const registrationsBySecretCode = createSelector(
+ registrations,
+ registrations => {
+ return countBy(registrations, 'answers.secretCode');
+ }
+);
+
+export const reviewAverageByReviewer = createSelector(
+ registrationsReviewed,
+ registrations => {
+ const grouped = groupBy(registrations, 'ratedBy');
+ return mapValues(grouped, registrations => {
+ return meanBy(registrations, 'rating');
+ });
+ }
+);
From 2a01edf80dbcb0c904c603f7e48ee6249bd6aa24 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Fri, 20 Sep 2019 10:59:09 +0300
Subject: [PATCH 03/21] add rounding to avg rating
---
frontend/src/components/plots/ReviewersList.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/plots/ReviewersList.js b/frontend/src/components/plots/ReviewersList.js
index 72fc35ada..36b582807 100644
--- a/frontend/src/components/plots/ReviewersList.js
+++ b/frontend/src/components/plots/ReviewersList.js
@@ -32,7 +32,7 @@ const ReviewersList = ({ data, userProfilesMap = {}, averagesMap = {} }) => {
}
title={`${item.user.firstName} ${item.user.lastName}`}
- description={`${item.ratings} reviews / ${item.average} average rating`}
+ description={`${item.ratings} reviews / ${item.average.toFixed(2)} average rating`}
/>
)}
From 713c89721773585213979c121fdbab53d4adb142 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Fri, 20 Sep 2019 11:03:08 +0300
Subject: [PATCH 04/21] Fix self assign sorting
---
backend/modules/registration/controller.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js
index c7ff08e54..b3de96b7a 100644
--- a/backend/modules/registration/controller.js
+++ b/backend/modules/registration/controller.js
@@ -75,7 +75,7 @@ controller.selfAssignRegistrationsForEvent = (eventId, userId) => {
rating: null,
assignedTo: null
})
- .sort([['createdAt', -1]])
+ .sort({ createdAt: 'asc' })
.limit(10)
.then(registrations => {
const updates = registrations.map(reg => {
From eee1469cacc6cd979b4b44756dfdb4d239ddde52 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Fri, 20 Sep 2019 12:06:14 +0300
Subject: [PATCH 05/21] download size optimisation
---
backend/modules/registration/controller.js | 38 +++++++++++++---------
1 file changed, 22 insertions(+), 16 deletions(-)
diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js
index b3de96b7a..e75ac85dc 100644
--- a/backend/modules/registration/controller.js
+++ b/backend/modules/registration/controller.js
@@ -47,22 +47,28 @@ controller.updateRegistration = (user, event, data) => {
};
controller.getRegistrationsForEvent = eventId => {
- return Registration.find(
- {
- event: eventId
- }
- // {
- // user: 1,
- // rating: 1,
- // ratedBy: 1,
- // assignedTo: 1,
- // tags: 1,
- // status: 1,
- // 'answers.email': 1,
- // 'answers.firstName': 1,
- // 'answers.lastName': 1
- // }
- );
+ return Registration.find({
+ event: 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);
+ }
+ return subAnswer;
+ });
+ }
+ return answer;
+ });
+ return reg;
+ });
+ });
};
controller.searchRegistrationsForEvent = (eventId, userId, params) => {
From f63a12bbb8b5fc74a6435b319ef32ccd467c8a6d Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sat, 21 Sep 2019 18:52:49 +0300
Subject: [PATCH 06/21] Create email tasks on registration status changes
---
.gitignore | 4 +-
backend/misc/config.js | 7 +-
backend/modules/devtools/routes.js | 35 ++++++
backend/modules/email-task/controller.js | 94 +++++++++-----
backend/modules/email-task/model.js | 29 +++--
backend/modules/email-task/types.js | 3 +-
backend/modules/event/routes.js | 15 ---
backend/modules/registration/controller.js | 119 ++++--------------
backend/modules/registration/model.js | 34 ++++-
backend/modules/registration/routes.js | 79 ++++--------
backend/modules/routes.js | 6 +
.../OrganiserEditEventManage/index.js | 7 ++
.../OrganiserEditEventReview/AdminPage.js | 26 +++-
frontend/src/services/events.js | 4 -
frontend/src/services/registrations.js | 63 ++++++----
package.json | 6 +-
16 files changed, 284 insertions(+), 247 deletions(-)
create mode 100644 backend/modules/devtools/routes.js
diff --git a/.gitignore b/.gitignore
index 61debd9ec..bfe0efc46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
node_modules
.DS_Store
scripts/sync-production-to-local.sh
-scripts/sync-staging-to-dev.sh
-scripts/sync-staging-to-local.sh
\ No newline at end of file
+scripts/sync-production-to-dev.sh
+scripts/sync-production-to-staging.sh
\ No newline at end of file
diff --git a/backend/misc/config.js b/backend/misc/config.js
index baa5409b9..cb4eeb3ba 100644
--- a/backend/misc/config.js
+++ b/backend/misc/config.js
@@ -74,6 +74,10 @@ const settings = {
ENVIRONMENT_TAG: {
value: process.env.ENVIRONMENT_TAG,
default: 'none'
+ },
+ DEVTOOLS_ENABLED: {
+ value: process.env.DEVTOOLS_ENABLED === 'true' && process.env.NODE_ENV !== 'production',
+ default: false
}
};
@@ -81,7 +85,7 @@ const buildConfig = () => {
const config = {};
_.forOwn(settings, (obj, key) => {
if (!obj.value) {
- if (obj.default) {
+ if (obj.default || obj.default === false) {
config[key] = obj.default;
} else {
throw new Error(
@@ -94,7 +98,6 @@ const buildConfig = () => {
});
console.log('Running app with config', config);
- console.log('Running app with env', process.env);
return config;
};
diff --git a/backend/modules/devtools/routes.js b/backend/modules/devtools/routes.js
new file mode 100644
index 000000000..8b1dff554
--- /dev/null
+++ b/backend/modules/devtools/routes.js
@@ -0,0 +1,35 @@
+const express = require('express');
+const router = express.Router();
+const Registration = require('../registration/model');
+
+router.route('/').get((req, res) => {
+ return res.status(200).send('DEVTOOLS HERE');
+});
+
+router.route('/anonymize-registrations').get(async (req, res) => {
+ const registrations = await Registration.find({});
+
+ const updates = registrations.map(registration => {
+ return {
+ updateOne: {
+ filter: {
+ _id: registration._id
+ },
+ update: {
+ $set: {
+ 'answers.firstName': 'Anonymous',
+ 'answers.lastName': 'Owl',
+ 'answers.email':
+ 'juuso.lappalainen+' + Math.floor(Math.random() * 1000000) + '@hackjunction.com'
+ }
+ }
+ }
+ };
+ });
+
+ const result = await Registration.bulkWrite(updates);
+
+ return res.status(200).json(result);
+});
+
+module.exports = router;
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index ff83fe1b8..1d7d4cc04 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -1,14 +1,24 @@
const EmailTask = require('./model');
const SendgridService = require('../../common/services/sendgrid');
const EmailTypes = require('./types');
+const EventController = require('../event/controller');
+const UserController = require('../user-profile/controller');
const controller = {};
-controller.createTask = (msg, taskParams) => {
+controller.createTask = (userId, eventId, type, message, schedule) => {
const task = new EmailTask({
- message: msg,
- ...taskParams
+ user: userId,
+ event: eventId,
+ type: type
});
+ if (schedule) {
+ task.schedule = schedule;
+ }
+
+ if (message) {
+ task.message = message;
+ }
return task.save().catch(err => {
if (err.code === 11000) {
//The task already exists, so it's ok
@@ -19,37 +29,61 @@ controller.createTask = (msg, taskParams) => {
});
};
-controller.sendEmail = (msg, taskParams) => {
- return SendgridService.send(msg).catch(message => {
- /** If sending the email fails, save a task to the DB and we can retry later */
- return controller.createTask(message, taskParams);
- });
+controller.createAcceptedTask = (userId, eventId) => {
+ return controller.createTask(userId, eventId, EmailTypes.registrationAccepted);
};
-controller.sendAcceptanceEmail = (event, user) => {
- const msgParams = {
- event_name: event.name
- };
- const msg = SendgridService.buildAcceptanceEmail(user.email, msgParams);
- const taskParams = {
- userId: user.userId,
- eventId: event._id,
- type: EmailTypes.registrationAccepted
- };
- return controller.sendEmail(msg, taskParams);
+controller.createRejectedTask = (userId, eventId) => {
+ return controller.createTask(userId, eventId, EmailTypes.registrationRejected);
};
-controller.sendRejectionEmail = (event, user) => {
- const msgParams = {
- event_name: event.name
- };
- const msg = SendgridService.buildRejectionEmail(user.email, msgParams);
- const taskParams = {
- userId: user.userId,
- eventId: event._id,
- type: EmailTypes.registrationRejected
- };
- return controller.sendEmail(msg, taskParams);
+controller.createRegisteredTask = (userId, eventId) => {
+ return controller.createTask(userId, eventId, EmailTypes.registrationReceived);
};
+// controller.sendEmail = (msg, taskParams) => {
+// return SendgridService.send(msg).catch(message => {
+// /** If sending the email fails, save a task to the DB and we can retry later */
+// return controller.createTask(message, taskParams);
+// });
+// };
+
+// controller.sendAcceptanceEmail = async (eventId, userId) => {
+// const event = await EventController.getEventById(eventId);
+// const user = await UserController.getUserProfile(userId);
+
+// const msgParams = {
+// event_name: event.name,
+// event_logo: event.logo.url
+// };
+
+// const msg = SendgridService.buildAcceptanceEmail('juuso.lappalainen@hackjunction.com', msgParams);
+// const taskParams = {
+// userId: user.userId,
+// eventId: event._id,
+// type: EmailTypes.registrationAccepted
+// };
+// return controller
+// .sendEmail(msg, taskParams)
+// .then(res => {
+// console.log('SENT EMAIL', res);
+// })
+// .catch(err => {
+// console.log('ERR SENDING EMAIL', err);
+// });
+// };
+
+// controller.sendRejectionEmail = (event, user) => {
+// const msgParams = {
+// event_name: event.name
+// };
+// const msg = SendgridService.buildRejectionEmail(user.email, msgParams);
+// const taskParams = {
+// userId: user.userId,
+// eventId: event._id,
+// type: EmailTypes.registrationRejected
+// };
+// return controller.sendEmail(msg, taskParams);
+// };
+
module.exports = controller;
diff --git a/backend/modules/email-task/model.js b/backend/modules/email-task/model.js
index fd15ca675..7da3387fe 100644
--- a/backend/modules/email-task/model.js
+++ b/backend/modules/email-task/model.js
@@ -1,24 +1,37 @@
const mongoose = require('mongoose');
const EmailTaskSchema = new mongoose.Schema({
- message: mongoose.Mixed,
- deliveredAt: {
- type: Date
+ message: {
+ type: String,
+ default: null
},
schedule: {
type: Date,
default: Date.now
},
- eventId: String,
- userId: String,
- type: String
+ deliveredAt: {
+ type: Date,
+ default: null
+ },
+ event: {
+ type: String,
+ required: true
+ },
+ user: {
+ type: String,
+ required: true
+ },
+ type: {
+ type: String,
+ required: true
+ }
});
EmailTaskSchema.set('timestamps', true);
EmailTaskSchema.index(
{
- eventId: 1,
- userId: 1,
+ event: 1,
+ user: 1,
type: 1
},
{
diff --git a/backend/modules/email-task/types.js b/backend/modules/email-task/types.js
index 6877e7b0f..4f8983f62 100644
--- a/backend/modules/email-task/types.js
+++ b/backend/modules/email-task/types.js
@@ -1,6 +1,7 @@
const EmailTypes = {
registrationAccepted: 'registration-accepted',
- registrationRejected: 'registration-rejected'
+ registrationRejected: 'registration-rejected',
+ registrationReceived: 'registration-received'
};
module.exports = EmailTypes;
diff --git a/backend/modules/event/routes.js b/backend/modules/event/routes.js
index 2ce9ea0fd..c49a6a9e2 100644
--- a/backend/modules/event/routes.js
+++ b/backend/modules/event/routes.js
@@ -39,17 +39,6 @@ const updateEvent = asyncHandler(async (req, res) => {
return res.status(200).json(updatedEvent);
});
-const getEventStats = asyncHandler(async (req, res) => {
- const eventId = req.event._id.toString();
- const registrationStats = await RegistrationController.getRegistrationStatsForEvent(eventId);
- const teamStats = await TeamController.getTeamStatsForEvent(eventId);
-
- return res.status(200).json({
- ...registrationStats,
- ...teamStats
- });
-});
-
const getEventAsOrganiser = asyncHandler(async (req, res) => {
const event = await EventController.getEventBySlug(req.event.slug);
return res.status(200).json(event);
@@ -105,10 +94,6 @@ router
.patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, updateEvent)
.delete(hasToken, hasPermission(Auth.Permissions.DELETE_EVENT), isEventOwner, deleteEvent);
-router
- .route('/:slug/stats')
- .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getEventStats);
-
/** Get organisers for single event */
router.get('/organisers/:slug', hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOwner, getOrganisers);
diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js
index e75ac85dc..e97b90bab 100644
--- a/backend/modules/registration/controller.js
+++ b/backend/modules/registration/controller.js
@@ -1,11 +1,10 @@
const _ = require('lodash');
-const moment = require('moment');
-
+const Promise = require('bluebird');
+const { RegistrationStatuses } = require('@hackjunction/shared');
const Registration = require('./model');
const { NotFoundError } = require('../../common/errors/errors');
const UserProfileController = require('../user-profile/controller');
const RegistrationHelpers = require('./helpers');
-const { RegistrationStatuses } = require('@hackjunction/shared');
const controller = {};
@@ -71,11 +70,6 @@ controller.getRegistrationsForEvent = eventId => {
});
};
-controller.searchRegistrationsForEvent = (eventId, userId, params) => {
- const aggregationSteps = RegistrationHelpers.buildAggregation(eventId, userId, params);
- return Registration.aggregate(aggregationSteps);
-};
-
controller.selfAssignRegistrationsForEvent = (eventId, userId) => {
return Registration.find({
rating: null,
@@ -84,24 +78,18 @@ controller.selfAssignRegistrationsForEvent = (eventId, userId) => {
.sort({ createdAt: 'asc' })
.limit(10)
.then(registrations => {
- const updates = registrations.map(reg => {
- return {
- updateOne: {
- filter: {
- _id: reg._id
- },
- update: {
- assignedTo: userId
- }
+ const registrationIds = registrations.map(r => r._id.toString());
+ return Registration.updateMany(
+ {
+ event: eventId,
+ _id: {
+ $in: registrationIds
}
- };
- });
-
- if (updates.length === 0) {
- return 0;
- }
-
- return Registration.bulkWrite(updates).then(data => {
+ },
+ {
+ assignedTo: userId
+ }
+ ).then(data => {
return data.nModified;
});
});
@@ -137,14 +125,6 @@ controller.getFullRegistration = (eventId, registrationId) => {
});
};
-controller.rateRegistration = (registrationId, event, user, rating) => {
- return controller.getFullRegistration(event._id.toString(), registrationId).then(registration => {
- registration.rating = rating;
- registration.ratedBy = user.sub;
- return registration.save();
- });
-};
-
controller.editRegistration = (registrationId, event, data, user) => {
return controller.getFullRegistration(event._id.toString(), registrationId).then(registration => {
registration.status = data.status;
@@ -156,73 +136,28 @@ controller.editRegistration = (registrationId, event, data, user) => {
});
};
-controller.acceptRegistration = (registrationId, event) => {
- return controller.getFullRegistration(event._id.toString(), registrationId).then(registration => {
- registration.status = RegistrationStatuses.asObject.accepted.id;
- return registration.save();
- });
-};
-
-controller.rejectRegistration = (registrationId, event) => {
- return controller.getFullRegistration(event._id.toString(), registrationId).then(registration => {
- registration.status = RegistrationStatuses.asObject.rejected.id;
- return registration.save();
- });
-};
-
controller.getFullRegistrationsForEvent = eventId => {
return Registration.find({
event: eventId
});
};
-controller.getRegistrationStatsForEvent = async eventId => {
- const registrations = await Registration.find({ event: eventId }, [
- 'answers.firstName',
- 'answers.lastName',
- 'answers.secretCode',
- 'rating',
- 'ratedBy',
- 'createdAt'
- ]);
-
- const registrationsByDay = _.countBy(registrations, r => moment(r.createdAt).format('YYYY-MM-DD'));
-
- const numRegistrations = registrations.length;
- const numRegistrationsLastDay = registrations.filter(r => {
- return Date.now() - r.createdAt < 24 * 60 * 60 * 1000;
- }).length;
- const reviewedRegistrations = registrations.filter(r => {
- return !isNaN(r.rating);
- });
- const registrationsByReviewer = _.countBy(reviewedRegistrations, r => r.ratedBy);
- const numRegistrationsReviewed = reviewedRegistrations.length;
- const registrationsLastFive = _.sortBy(registrations, r => r.createdAt).slice(-5);
- const topSecretCodes = _.countBy(registrations, r => r.answers.secretCode);
- const topSecretCodesArray = [];
- _.forOwn(topSecretCodes, (count, code) => {
- if (!_.isEmpty(code) && code !== 'undefined') {
- topSecretCodesArray.push({
- code,
- count
- });
- }
+controller.acceptSoftAccepted = async eventId => {
+ const users = await Registration.find({ event: eventId, status: RegistrationStatuses.asObject.accepted.id });
+ const accepted = await Promise.each(users, user => {
+ user.status = RegistrationStatuses.asObject.softAccepted.id;
+ user.save();
});
+ return accepted;
+};
- const registrationsAvgRating = _.meanBy(reviewedRegistrations, 'rating');
- const registrationsSplit = _.countBy(reviewedRegistrations, 'rating');
-
- return {
- numRegistrations,
- numRegistrationsLastDay,
- numRegistrationsReviewed,
- registrationsByDay,
- registrationsByReviewer,
- registrationsAvgRating,
- registrationsLastFive,
- registrationsSplit,
- registrationsTopSecretCodes: _.sortBy(topSecretCodesArray, item => item.count * -1)
- };
+controller.rejectSoftRejected = async eventId => {
+ const users = await Registration.find({ event: eventId, status: RegistrationStatuses.asObject.softRejected.id });
+ const rejected = await Promise.each(users, user => {
+ user.status = RegistrationStatuses.asObject.rejected.id;
+ user.save();
+ });
+ return rejected;
};
module.exports = controller;
diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js
index e21f5b687..1be8601ce 100644
--- a/backend/modules/registration/model.js
+++ b/backend/modules/registration/model.js
@@ -1,7 +1,8 @@
const mongoose = require('mongoose');
const _ = require('lodash');
-const RegistrationStatuses = require('@hackjunction/shared');
+const { RegistrationStatuses } = require('@hackjunction/shared');
const updateAllowedPlugin = require('../../common/plugins/updateAllowed');
+const EmailTaskController = require('../email-task/controller');
const RegistrationSchema = new mongoose.Schema({
event: {
@@ -14,7 +15,7 @@ const RegistrationSchema = new mongoose.Schema({
status: {
type: String,
enum: RegistrationStatuses.ids,
- default: 'pending'
+ default: RegistrationStatuses.asObject.pending.id
},
assignedTo: {
type: String
@@ -43,6 +44,35 @@ RegistrationSchema.plugin(updateAllowedPlugin, {
blacklisted: ['__v', '_id', 'event', 'user', 'createdAt', 'updatedAt']
});
+RegistrationSchema.pre('save', function(next) {
+ this._wasNew = this.isNew;
+ this._previousStatus = this.status;
+ next();
+});
+
+/** Trigger email sending on status changes etc. */
+RegistrationSchema.post('save', function(doc, next) {
+ const ACCEPTED = RegistrationStatuses.asObject.accepted.id;
+ const REJECTED = RegistrationStatuses.asObject.rejected.id;
+
+ /** If a registration was just created, create an email notification about it */
+ if (doc._wasNew) {
+ EmailTaskController.createRegisteredTask(doc.user, doc.event);
+ }
+
+ /** If a registration is accepted, create an email notification about it */
+ if (doc._previousStatus !== ACCEPTED && doc.status === ACCEPTED) {
+ EmailTaskController.createAcceptedTask(doc.user, doc.event);
+ }
+
+ /** If a registration is rejected, create an email notification about it */
+ if ( && doc._previousStatus !== REJECTED && doc.status === REJECTED) {
+ EmailTaskController.createRejectedTask(doc.user, doc.event);
+ }
+
+ next();
+});
+
RegistrationSchema.index({ event: 1, user: 1 }, { unique: true });
const Registration = mongoose.model('Registration', RegistrationSchema);
diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js
index e0c3eb4a1..026bb682b 100644
--- a/backend/modules/registration/routes.js
+++ b/backend/modules/registration/routes.js
@@ -1,11 +1,9 @@
const express = require('express');
const router = express.Router();
const asyncHandler = require('express-async-handler');
-const { Auth } = require('@hackjunction/shared');
+const { Auth, RegistrationStatuses } = require('@hackjunction/shared');
const RegistrationController = require('./controller');
const EventController = require('../event/controller');
-const UserProfileController = require('../user-profile/controller');
-const EmailTaskController = require('../email-task/controller');
const { hasToken } = require('../../common/middleware/token');
const { hasPermission } = require('../../common/middleware/permissions');
@@ -42,44 +40,11 @@ const editRegistration = asyncHandler(async (req, res) => {
return res.status(200).json(registration);
});
-const rateRegistration = asyncHandler(async (req, res) => {
- const registration = await RegistrationController.rateRegistration(
- req.params.registrationId,
- req.event,
- req.user,
- req.body.rating
- );
- return res.status(200).json(registration);
-});
-
-const acceptRegistration = asyncHandler(async (req, res) => {
- const registration = await RegistrationController.acceptRegistration(req.params.registrationId, req.event);
- const user = await UserProfileController.getUserProfile(registration.user);
- await EmailTaskController.sendAcceptanceEmail(req.event, user);
- return res.status(200).json(registration);
-});
-
-const rejectRegistration = asyncHandler(async (req, res) => {
- const registration = await RegistrationController.rejectRegistration(req.params.registrationId, req.event);
- const user = await UserProfileController.getUserProfile(registration.user);
- await EmailTaskController.sendAcceptanceEmail(req.event, user);
- return res.status(200).json(registration);
-});
-
const getRegistrationsForEvent = asyncHandler(async (req, res) => {
const registrations = await RegistrationController.getRegistrationsForEvent(req.event._id.toString());
return res.status(200).json(registrations);
});
-const searchRegistrationsForEvent = asyncHandler(async (req, res) => {
- const registrations = await RegistrationController.searchRegistrationsForEvent(
- req.event._id.toString(),
- req.user.sub,
- req.query
- );
- return res.status(200).json(registrations);
-});
-
const selfAssignRegistrationsForEvent = asyncHandler(async (req, res) => {
const registrations = await RegistrationController.selfAssignRegistrationsForEvent(
req.event._id.toString(),
@@ -112,6 +77,18 @@ const bulkEditRegistrations = asyncHandler(async (req, res) => {
return res.status(200).json([]);
});
+const bulkAcceptRegistrations = asyncHandler(async (req, res) => {
+ const eventId = req.event._id.toString();
+ const accepted = await RegistrationController.acceptSoftAccepted(eventId);
+ return res.status(200).json(accepted);
+});
+
+const bulkRejectRegistrations = asyncHandler(async (req, res) => {
+ const eventId = req.event._id.toString();
+ const rejected = await RegistrationController.rejectSoftRejected(eventId);
+ return res.status(200).json(rejected);
+});
+
router.route('/').get(hasToken, getUserRegistrations);
/** Get, create or update a registration */
@@ -130,15 +107,6 @@ router.get(
getRegistrationsForEvent
);
-/** Search registrations as organiser */
-router.get(
- '/:slug/search',
- hasToken,
- hasPermission(Auth.Permissions.MANAGE_EVENT),
- isEventOrganiser,
- searchRegistrationsForEvent
-);
-
router
.route('/:slug/assign')
.get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, selfAssignRegistrationsForEvent)
@@ -148,23 +116,18 @@ router
.route('/:slug/bulk')
.patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkEditRegistrations);
-/** Get or edit single registration as an organiser */
-router
- .route('/:slug/:registrationId')
- .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getFullRegistration)
- .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, editRegistration);
-
-/** Rate a single registration */
router
- .route('/:slug/:registrationId/rate')
- .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, rateRegistration);
+ .route('/:slug/bulk/accept')
+ .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkAcceptRegistrations);
router
- .route('/:slug/:registrationId/accept')
- .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, acceptRegistration);
+ .route('/:slug/bulk/reject')
+ .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, bulkRejectRegistrations);
+/** Get or edit single registration as an organiser */
router
- .route('/:slug/:registrationId/reject')
- .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, rejectRegistration);
+ .route('/:slug/:registrationId')
+ .get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getFullRegistration)
+ .patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, editRegistration);
module.exports = router;
diff --git a/backend/modules/routes.js b/backend/modules/routes.js
index 0c948788d..ec0979e0f 100644
--- a/backend/modules/routes.js
+++ b/backend/modules/routes.js
@@ -5,6 +5,7 @@ const userProfileRouter = require('./user-profile/routes');
const registrationRouter = require('./registration/routes');
const newsletterRouter = require('./newsletter/routes');
const teamRouter = require('./team/routes');
+const devToolsRouter = require('./devtools/routes');
module.exports = function(app) {
app.get('/api', (req, res) => {
@@ -21,4 +22,9 @@ module.exports = function(app) {
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) {
+ app.use('/api/devtools', devToolsRouter);
+ }
};
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js
index 231eed2cd..81934eaf8 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventManage/index.js
@@ -68,6 +68,10 @@ const OrganiserEditEventManage = ({
});
}
+ const testEmail = () => {
+ const recipient = 'juuso.lappalainen@hackjunction.com';
+ };
+
return (
+
+ Test sending email to juuso.lappalainen@hackjunction.com
+
)}
/>
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
index eab4a21a5..cdbf46411 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
@@ -7,10 +7,12 @@ import { RegistrationStatuses } from '@hackjunction/shared';
import { Row, Col, Card, Statistic, Tag, List, Button as AntButton } from 'antd';
import Divider from 'components/generic/Divider';
import * as OrganiserSelectors from 'redux/organiser/selectors';
+import * as AuthSelectors from 'redux/auth/selectors';
+import RegistrationsService from 'services/registrations';
const STATUSES = RegistrationStatuses.asObject;
-const AdminPage = ({ registrations }) => {
+const AdminPage = ({ registrations, idToken, event }) => {
const groupedByStatus = useMemo(() => {
return groupBy(registrations, 'status');
}, [registrations]);
@@ -24,6 +26,20 @@ const AdminPage = ({ registrations }) => {
}, 0);
};
+ const handleBulkAccept = () => {
+ console.log('BULK ACCEPT BEGIN');
+ RegistrationsService.bulkAcceptRegistrationsForEvent(idToken, event.slug)
+ .then(data => {
+ console.log('BULK ACCEPT DONE', data);
+ })
+ .catch(err => {
+ console.log('BULK ACCEPT ERR', err);
+ })
+ .finally(() => {
+ console.log('BULK ACCEPT FINALLY');
+ });
+ };
+
const total = registrations.length;
const rated = filter(registrations, reg => reg.rating).length;
const ratedOrAssigned = filter(registrations, reg => reg.rating || reg.assignedTo).length;
@@ -34,7 +50,7 @@ const AdminPage = ({ registrations }) => {
description:
'Change the status of all Soft Accepted participants to Accepted, and notify them via email that they have been accepted to the event!',
extra: (
- window.alert('Get permission from Juuso to do this ;--)')} type="link">
+
Accept
)
@@ -51,8 +67,6 @@ const AdminPage = ({ registrations }) => {
}
];
- console.log('RATED', rated);
-
return (
@@ -152,6 +166,8 @@ const AdminPage = ({ registrations }) => {
};
const mapState = state => ({
- registrations: OrganiserSelectors.registrations(state)
+ registrations: OrganiserSelectors.registrations(state),
+ event: OrganiserSelectors.event(state),
+ idToken: AuthSelectors.getIdToken(state)
});
export default connect(mapState)(AdminPage);
diff --git a/frontend/src/services/events.js b/frontend/src/services/events.js
index 731df5576..d0ec8efab 100644
--- a/frontend/src/services/events.js
+++ b/frontend/src/services/events.js
@@ -20,10 +20,6 @@ EventsService.getEventsByOrganiser = idToken => {
return _axios.get(`${BASE_ROUTE}`, config(idToken));
};
-EventsService.getEventStats = (idToken, slug) => {
- return _axios.get(`${BASE_ROUTE}/${slug}/stats`, config(idToken));
-};
-
EventsService.getPublicEvents = () => {
return _axios.get(`${BASE_ROUTE}/public`);
};
diff --git a/frontend/src/services/registrations.js b/frontend/src/services/registrations.js
index 9f1bd1888..1e145261a 100644
--- a/frontend/src/services/registrations.js
+++ b/frontend/src/services/registrations.js
@@ -12,64 +12,77 @@ function config(idToken) {
const BASE_ROUTE = '/registrations';
+/** Get all of your registrations as the logged in user
+ * GET /
+ */
RegistrationsService.getUserRegistrations = idToken => {
return _axios.get(`${BASE_ROUTE}`, config(idToken));
};
+/** Get your registration for an event as the logged in user
+ * GET /:slug
+ */
RegistrationsService.getRegistration = (idToken, slug) => {
return _axios.get(`${BASE_ROUTE}/${slug}`, config(idToken));
};
-RegistrationsService.createRegistration = (idToken, slug, data, subscribe = false) => {
- return _axios.post(`${BASE_ROUTE}/${slug}/?subscribe=${subscribe}`, data, config(idToken));
+/** Create a registration for an event as the logged in user
+ * POST /:slug
+ */
+RegistrationsService.createRegistration = (idToken, slug, data) => {
+ return _axios.post(`${BASE_ROUTE}/${slug}`, data, config(idToken));
};
RegistrationsService.updateRegistration = (idToken, slug, data) => {
return _axios.patch(`${BASE_ROUTE}/${slug}`, data, config(idToken));
};
-
+/** Get all registrations for event
+ * GET /:slug/all
+ */
RegistrationsService.getRegistrationsForEvent = (idToken, slug) => {
return _axios.get(`${BASE_ROUTE}/${slug}/all`, config(idToken));
};
+/** Edit registrations in bulk
+ * PATCH /:slug/bulk
+ */
RegistrationsService.bulkEditRegistrationsForEvent = (idToken, slug, registrationIds, edits) => {
return _axios.patch(`${BASE_ROUTE}/${slug}/bulk`, { registrationIds, edits }, config(idToken));
};
-RegistrationsService.searchRegistrationsForEvent = (idToken, slug, filters) => {
- const options = {
- ...config(idToken),
- params: filters
- };
- return _axios.get(`${BASE_ROUTE}/${slug}/search`, options);
+/** Accept all soft-accepted registrations
+ * PATCH /:slug/bulk/accept
+ */
+RegistrationsService.bulkAcceptRegistrationsForEvent = (idToken, slug) => {
+ return _axios.patch(`${BASE_ROUTE}/${slug}/bulk/accept`, {}, config(idToken));
};
-RegistrationsService.assignRandomRegistrations = (idToken, slug) => {
- return _axios.get(`${BASE_ROUTE}/${slug}/assign`, config(idToken));
+/** Reject all soft-rejected registrations
+ * PATCH /:slug/bulk/reject
+ */
+RegistrationsService.bulkRejectRegistrationsForEvent = (idToken, slug) => {
+ return _axios.patch(`${BASE_ROUTE}/${slug}/bulk/reject`);
};
-RegistrationsService.assignRegistration = (idToken, slug, registrationId, userId) => {
- return _axios.patch(`${BASE_ROUTE}/${slug}/assign`, { registrationId, userId }, config(idToken));
+/** Assign 10 registrations to logged in user
+ * GET /:slug/assign
+ */
+RegistrationsService.assignRandomRegistrations = (idToken, slug) => {
+ return _axios.get(`${BASE_ROUTE}/${slug}/assign`, config(idToken));
};
+/** Get a single registration with all fields
+ * GET /:slug/:registrationId
+ */
RegistrationsService.getFullRegistration = (idToken, slug, registrationId) => {
return _axios.get(`${BASE_ROUTE}/${slug}/${registrationId}`, config(idToken));
};
+/** Edit a single registration
+ * PATCH /:slug/:registrationId
+ */
RegistrationsService.editRegistration = (idToken, slug, registrationId, data) => {
return _axios.patch(`${BASE_ROUTE}/${slug}/${registrationId}`, data, config(idToken));
};
-RegistrationsService.rateRegistration = (idToken, slug, registrationId, rating) => {
- return _axios.patch(`${BASE_ROUTE}/${slug}/${registrationId}/rate`, { rating }, config(idToken));
-};
-
-RegistrationsService.acceptRegistration = (idToken, slug, registrationId) => {
- return _axios.patch(`${BASE_ROUTE}/${slug}/${registrationId}/accept`, {}, config(idToken));
-};
-
-RegistrationsService.rejectRegistration = (idToken, slug, registrationId) => {
- return _axios.patch(`${BASE_ROUTE}/${slug}/${registrationId}/reject`, {}, config(idToken));
-};
-
export default RegistrationsService;
diff --git a/package.json b/package.json
index 9241a7250..045da08f0 100644
--- a/package.json
+++ b/package.json
@@ -11,9 +11,9 @@
"setup": "better-npm-run setup",
"dev:frontend": "cd frontend && npm start",
"dev:backend": "cd backend && npm run dev",
- "db-sync:prod-to-local": "sh ./scripts/sync-production-to-local.sh",
- "db-sync:staging-to-dev": "sh ./scripts/sync-staging-to-dev.sh",
- "db-sync:staging-to-local": "sh ./scripts/sync-staging-to-local.sh"
+ "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"
},
"betterScripts": {
"setup": "better-npm-run setup:backend && better-npm-run setup:frontend",
From 4e8c2aa8444186b6469064c0803ca896166cbd84 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 17:57:18 +0300
Subject: [PATCH 07/21] Bulk email to filtered participants feature
---
backend/common/services/sendgrid.js | 25 ++-
backend/misc/config.js | 4 +
backend/modules/email-task/controller.js | 103 ++++-----
backend/modules/email-task/routes.js | 21 ++
backend/modules/registration/controller.js | 29 ++-
backend/modules/registration/model.js | 20 +-
backend/modules/registration/routes.js | 14 ++
backend/modules/routes.js | 2 +
.../NotificationBlock.module.scss | 67 +++---
.../generic/NotificationBlock/index.js | 37 ++--
.../BulkEditRegistrationDrawer/index.js | 7 +-
.../BulkEmailDrawer.module.scss | 4 +
.../modals/BulkEmailDrawer/index.js | 203 ++++++++++++++++++
.../pages/Account/AccountDashboard/index.js | 2 +-
.../RegistrationStatusBlock.js | 168 +++++++++++++++
.../TeamStatusBlock.js | 100 +++++++++
.../EventDashboardHomeRegistration/index.js | 140 +-----------
.../EventDashboardHome/index.js | 3 -
.../SearchAttendeesPage.js | 9 +-
frontend/src/services/email.js | 24 +++
shared/constants/registration-statuses.js | 8 +
21 files changed, 743 insertions(+), 247 deletions(-)
create mode 100644 backend/modules/email-task/routes.js
create mode 100644 frontend/src/components/modals/BulkEmailDrawer/BulkEmailDrawer.module.scss
create mode 100644 frontend/src/components/modals/BulkEmailDrawer/index.js
create mode 100644 frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
create mode 100644 frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TeamStatusBlock.js
create mode 100644 frontend/src/services/email.js
diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js
index 33256e379..bceb1e645 100644
--- a/backend/common/services/sendgrid.js
+++ b/backend/common/services/sendgrid.js
@@ -34,11 +34,30 @@ const sendgridAddRecipientsToList = (list_id, recipient_ids) => {
};
const SendgridService = {
- buildAcceptanceEmail: (to, { event_name }) => {
- return SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
- event_name
+ sendAcceptanceEmail: (to, event, user) => {
+ const msg = SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
+ event_name: event.name,
+ first_name: user.firstName,
+ dashboard_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`,
+ website_link: 'https://2019.hackjunction.com',
+ fb_event_link: 'https://facebook.com',
+ contact_email: 'participants@hackjunction.com'
});
+ return SendgridService.send(msg);
},
+ sendGenericEmail: (to, params) => {
+ const msg = SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
+ subject: params.subject,
+ subtitle: params.subtitle,
+ header_image: params.header_image,
+ body: params.body,
+ cta_text: params.cta_text,
+ cta_link: params.cta_link
+ });
+
+ return SendgridService.send(msg);
+ },
+
buildRejectionEmail: (to, { event_name }) => {
return SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_REJECTED_TEMPLATE, {
event_name
diff --git a/backend/misc/config.js b/backend/misc/config.js
index cb4eeb3ba..05213d198 100644
--- a/backend/misc/config.js
+++ b/backend/misc/config.js
@@ -66,6 +66,10 @@ const settings = {
value: process.env.SENDGRID_REJECTED_TEMPLATE,
required: true
},
+ SENDGRID_GENERIC_TEMPLATE: {
+ value: process.env.SENDGRID_GENERIC_TEMPLATE,
+ required: true
+ },
FRONTEND_URL: {
value: process.env.FRONTEND_URL,
default: '',
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index 1d7d4cc04..b4d0580d3 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -25,65 +25,70 @@ controller.createTask = (userId, eventId, type, message, schedule) => {
return Promise.resolve();
}
// For other types of errors, we'll want to throw the error normally
- return Promise.reject();
+ return Promise.reject(err);
});
};
-controller.createAcceptedTask = (userId, eventId) => {
- return controller.createTask(userId, eventId, EmailTypes.registrationAccepted);
+controller.createAcceptedTask = async (userId, eventId, deliverNow = false) => {
+ const task = await controller.createTask(userId, eventId, EmailTypes.registrationAccepted);
+ if (deliverNow) {
+ return controller.deliverEmailTask(task);
+ }
+ return task;
};
-controller.createRejectedTask = (userId, eventId) => {
- return controller.createTask(userId, eventId, EmailTypes.registrationRejected);
+controller.createRejectedTask = async (userId, eventId, deliverNow = false) => {
+ const task = await controller.createTask(userId, eventId, EmailTypes.registrationRejected);
+ if (deliverNow) {
+ return controller.deliverEmailTask(task);
+ }
+ return task;
};
-controller.createRegisteredTask = (userId, eventId) => {
- return controller.createTask(userId, eventId, EmailTypes.registrationReceived);
+controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => {
+ const task = await controller.createTask(userId, eventId, EmailTypes.registrationReceived);
+ if (deliverNow) {
+ return controller.deliverEmailTask(task);
+ }
+ return task;
};
-// controller.sendEmail = (msg, taskParams) => {
-// return SendgridService.send(msg).catch(message => {
-// /** If sending the email fails, save a task to the DB and we can retry later */
-// return controller.createTask(message, taskParams);
-// });
-// };
-
-// controller.sendAcceptanceEmail = async (eventId, userId) => {
-// const event = await EventController.getEventById(eventId);
-// const user = await UserController.getUserProfile(userId);
-
-// const msgParams = {
-// event_name: event.name,
-// event_logo: event.logo.url
-// };
+controller.deliverEmailTask = async task => {
+ const [user, event] = await Promise.all([
+ UserController.getUserProfile(task.user),
+ EventController.getEventById(task.event)
+ ]);
+ switch (task.type) {
+ case EmailTypes.registrationAccepted: {
+ await SendgridService.sendAcceptanceEmail('juuso.lappalainen@hackjunction.com', event, user);
+ break;
+ }
+ case EmailTypes.registrationRejected: {
+ console.log('PERFORM REG REJECTED EMAIL');
+ break;
+ }
+ case EmailTypes.registrationReceived: {
+ console.log('PERFORM REG RECEIVED');
+ break;
+ }
+ default: {
+ console.log('PERFORM GENERIC EMAIL!');
+ break;
+ }
+ }
-// const msg = SendgridService.buildAcceptanceEmail('juuso.lappalainen@hackjunction.com', msgParams);
-// const taskParams = {
-// userId: user.userId,
-// eventId: event._id,
-// type: EmailTypes.registrationAccepted
-// };
-// return controller
-// .sendEmail(msg, taskParams)
-// .then(res => {
-// console.log('SENT EMAIL', res);
-// })
-// .catch(err => {
-// console.log('ERR SENDING EMAIL', err);
-// });
-// };
+ /** Here we'll have success so we can set the task as delivered */
+ task.deliveredAt = Date.now();
+ return task.save();
+};
-// controller.sendRejectionEmail = (event, user) => {
-// const msgParams = {
-// event_name: event.name
-// };
-// const msg = SendgridService.buildRejectionEmail(user.email, msgParams);
-// const taskParams = {
-// userId: user.userId,
-// eventId: event._id,
-// type: EmailTypes.registrationRejected
-// };
-// return controller.sendEmail(msg, taskParams);
-// };
+controller.sendPreviewEmail = async (to, msgParams) => {
+ console.log('SENDING TEST TO', to);
+ console.log('WITH PARAMS', msgParams);
+ return SendgridService.sendGenericEmail(to, msgParams).catch(err => {
+ console.log('DA ERR', err);
+ return;
+ });
+};
module.exports = controller;
diff --git a/backend/modules/email-task/routes.js b/backend/modules/email-task/routes.js
new file mode 100644
index 000000000..d7e53165f
--- /dev/null
+++ b/backend/modules/email-task/routes.js
@@ -0,0 +1,21 @@
+const express = require('express');
+const router = express.Router();
+const asyncHandler = require('express-async-handler');
+const { Auth } = require('@hackjunction/shared');
+
+const { hasToken } = require('../../common/middleware/token');
+const { hasPermission } = require('../../common/middleware/permissions');
+const { isEventOrganiser } = require('../../common/middleware/events');
+
+const EmailTaskController = require('./controller');
+
+const sendPreviewEmail = asyncHandler(async (req, res) => {
+ await EmailTaskController.sendPreviewEmail(req.body.to, req.body.params);
+ return res.status(200).json({});
+});
+
+router
+ .route('/:slug/preview')
+ .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);
+
+module.exports = router;
diff --git a/backend/modules/registration/controller.js b/backend/modules/registration/controller.js
index e97b90bab..2e9fbbc99 100644
--- a/backend/modules/registration/controller.js
+++ b/backend/modules/registration/controller.js
@@ -2,10 +2,11 @@ const _ = require('lodash');
const Promise = require('bluebird');
const { RegistrationStatuses } = require('@hackjunction/shared');
const Registration = require('./model');
-const { NotFoundError } = require('../../common/errors/errors');
+const { NotFoundError, ForbiddenError } = require('../../common/errors/errors');
const UserProfileController = require('../user-profile/controller');
const RegistrationHelpers = require('./helpers');
+const STATUSES = RegistrationStatuses.asObject;
const controller = {};
controller.getUserRegistrations = user => {
@@ -45,6 +46,28 @@ controller.updateRegistration = (user, event, data) => {
});
};
+controller.confirmRegistration = (user, event) => {
+ return controller.getRegistration(user.sub, event._id.toString()).then(registration => {
+ if (registration.status === STATUSES.accepted.id) {
+ registration.status = STATUSES.confirmed.id;
+ return registration.save();
+ }
+
+ throw new ForbiddenError('Only accepted registrations can be confirmed');
+ });
+};
+
+controller.cancelRegistration = (user, event) => {
+ return controller.getRegistration(user.sub, event._id.toString()).then(registration => {
+ if (registration.status === STATUSES.confirmed.id) {
+ registration.status = STATUSES.cancelled.id;
+ return registration.save();
+ }
+
+ throw new ForbiddenError('Only confirmed registrations can be cancelled');
+ });
+};
+
controller.getRegistrationsForEvent = eventId => {
return Registration.find({
event: eventId
@@ -143,9 +166,9 @@ controller.getFullRegistrationsForEvent = eventId => {
};
controller.acceptSoftAccepted = async eventId => {
- const users = await Registration.find({ event: eventId, status: RegistrationStatuses.asObject.accepted.id });
+ const users = await Registration.find({ event: eventId, status: RegistrationStatuses.asObject.softAccepted.id });
const accepted = await Promise.each(users, user => {
- user.status = RegistrationStatuses.asObject.softAccepted.id;
+ user.status = RegistrationStatuses.asObject.accepted.id;
user.save();
});
return accepted;
diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js
index 1be8601ce..c397cef3f 100644
--- a/backend/modules/registration/model.js
+++ b/backend/modules/registration/model.js
@@ -15,7 +15,11 @@ const RegistrationSchema = new mongoose.Schema({
status: {
type: String,
enum: RegistrationStatuses.ids,
- default: RegistrationStatuses.asObject.pending.id
+ default: RegistrationStatuses.asObject.pending.id,
+ set: function(status) {
+ this._previousStatus = this.status;
+ return status;
+ }
},
assignedTo: {
type: String
@@ -46,7 +50,6 @@ RegistrationSchema.plugin(updateAllowedPlugin, {
RegistrationSchema.pre('save', function(next) {
this._wasNew = this.isNew;
- this._previousStatus = this.status;
next();
});
@@ -54,20 +57,19 @@ RegistrationSchema.pre('save', function(next) {
RegistrationSchema.post('save', function(doc, next) {
const ACCEPTED = RegistrationStatuses.asObject.accepted.id;
const REJECTED = RegistrationStatuses.asObject.rejected.id;
-
/** If a registration was just created, create an email notification about it */
- if (doc._wasNew) {
- EmailTaskController.createRegisteredTask(doc.user, doc.event);
+ if (this._wasNew) {
+ EmailTaskController.createRegisteredTask(doc.user, doc.event, true);
}
/** If a registration is accepted, create an email notification about it */
- if (doc._previousStatus !== ACCEPTED && doc.status === ACCEPTED) {
- EmailTaskController.createAcceptedTask(doc.user, doc.event);
+ if (this._previousStatus !== ACCEPTED && this.status === ACCEPTED) {
+ EmailTaskController.createAcceptedTask(doc.user, doc.event, true);
}
/** If a registration is rejected, create an email notification about it */
- if ( && doc._previousStatus !== REJECTED && doc.status === REJECTED) {
- EmailTaskController.createRejectedTask(doc.user, doc.event);
+ if (this._previousStatus !== REJECTED && this.status === REJECTED) {
+ EmailTaskController.createRejectedTask(doc.user, doc.event, true);
}
next();
diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js
index 026bb682b..e625ba90e 100644
--- a/backend/modules/registration/routes.js
+++ b/backend/modules/registration/routes.js
@@ -30,6 +30,16 @@ const updateRegistration = asyncHandler(async (req, res) => {
return res.status(200).json(registration);
});
+const confirmRegistration = asyncHandler(async (req, res) => {
+ const registration = await RegistrationController.confirmRegistration(req.user, req.event);
+ return res.status(200).json(registration);
+});
+
+const cancelRegistration = asyncHandler(async (req, res) => {
+ const registration = await RegistrationController.cancelRegistration(req.user, req.event);
+ return res.status(200).json(registration);
+});
+
const editRegistration = asyncHandler(async (req, res) => {
const registration = await RegistrationController.editRegistration(
req.params.registrationId,
@@ -98,6 +108,10 @@ router
.post(hasToken, canRegisterToEvent, createRegistration)
.patch(hasToken, canRegisterToEvent, updateRegistration);
+router.route('/:slug/confirm').patch(hasToken, confirmRegistration);
+
+router.route('/:slug/cancel').patch(hasToken, cancelRegistration);
+
/** Get all registration as organiser */
router.get(
'/:slug/all',
diff --git a/backend/modules/routes.js b/backend/modules/routes.js
index ec0979e0f..cdd954964 100644
--- a/backend/modules/routes.js
+++ b/backend/modules/routes.js
@@ -5,6 +5,7 @@ const userProfileRouter = require('./user-profile/routes');
const registrationRouter = require('./registration/routes');
const newsletterRouter = require('./newsletter/routes');
const teamRouter = require('./team/routes');
+const emailRouter = require('./email-task/routes');
const devToolsRouter = require('./devtools/routes');
module.exports = function(app) {
@@ -16,6 +17,7 @@ module.exports = function(app) {
app.use('/api/auth', authRouter);
app.use('/api/upload', uploadRouter);
app.use('/api/newsletter', newsletterRouter);
+ app.use('/api/email', emailRouter);
/** Model related routes */
app.use('/api/events', eventRouter);
diff --git a/frontend/src/components/generic/NotificationBlock/NotificationBlock.module.scss b/frontend/src/components/generic/NotificationBlock/NotificationBlock.module.scss
index 2d1046e8c..236fe42cd 100644
--- a/frontend/src/components/generic/NotificationBlock/NotificationBlock.module.scss
+++ b/frontend/src/components/generic/NotificationBlock/NotificationBlock.module.scss
@@ -1,48 +1,63 @@
@import '~styles/variables';
.wrapper {
- padding: 1rem;
display: flex;
flex-direction: column;
- align-items: flex-start;
+ align-items: stretch;
border-radius: 10px;
box-shadow: 4px 6px 20px #f3f3f3;
+ overflow: hidden;
- .title {
- font-size: 18px;
- text-transform: uppercase;
- color: $black;
+ .iconWrapper {
display: flex;
- flex-direction: row;
+ flex-direction: column;
align-items: center;
- }
-
- .body {
- font-size: 18px;
- }
+ justify-content: center;
+ background: rgba($black, 0.5);
+ padding: 1rem;
- .titleExtra {
- padding-left: 5px;
- }
+ &Warning {
+ background: rgba($junction-orange, 0.7);
+ }
- .typeIcon {
- margin-right: 10px;
- font-size: 24px;
+ &Error {
+ background: rgba($color-error, 0.5);
+ }
- &Info {
- color: $green;
+ &Success {
+ background: $green;
}
- &Warning {
- color: $junction-orange;
+ .icon {
+ color: white;
+ font-size: 32px;
}
+ }
- &Error {
- color: $color-error;
+ .contentWrapper {
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: $lightgrey;
+
+ .title {
+ font-size: 18px;
+ text-transform: uppercase;
+ color: $black;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ &Extra {
+ padding-left: 5px;
+ font-weight: 900;
+ }
}
- &Success {
- color: green;
+ .body {
+ font-size: 18px;
+ text-align: center;
}
}
}
diff --git a/frontend/src/components/generic/NotificationBlock/index.js b/frontend/src/components/generic/NotificationBlock/index.js
index 699fd2538..3e6ce62e6 100644
--- a/frontend/src/components/generic/NotificationBlock/index.js
+++ b/frontend/src/components/generic/NotificationBlock/index.js
@@ -11,30 +11,39 @@ const NotificationBlock = ({ title, titleExtra, body, bottom, type }) => {
if (!type) return null;
switch (type) {
case 'success':
- return ;
+ return ;
case 'error':
- return ;
+ return ;
case 'warning':
- return (
-
- );
+ return ;
case 'info':
- return ;
+ return ;
default:
return null;
}
};
+ const iconWrapperClass = classNames(styles.iconWrapper, {
+ [styles.iconWrapperSuccess]: type === 'success',
+ [styles.iconWrapperInfo]: type === 'info',
+ [styles.iconWrapperWarning]: type === 'warning',
+ [styles.iconWrapperError]: type === 'error'
+ });
+
return (
-
- {renderTypeIcon()}
- {title}
- {titleExtra && {titleExtra} }
-
-
-
{body}
-
{bottom}
+
+
+
+ {title}
+ {titleExtra && {titleExtra} }
+
+
+
{body}
+
{bottom}
+
);
};
diff --git a/frontend/src/components/modals/BulkEditRegistrationDrawer/index.js b/frontend/src/components/modals/BulkEditRegistrationDrawer/index.js
index a88029476..acca46b96 100644
--- a/frontend/src/components/modals/BulkEditRegistrationDrawer/index.js
+++ b/frontend/src/components/modals/BulkEditRegistrationDrawer/index.js
@@ -273,12 +273,7 @@ const BulkEditRegistrationDrawer = ({
/>
-
+
);
};
diff --git a/frontend/src/components/modals/BulkEmailDrawer/BulkEmailDrawer.module.scss b/frontend/src/components/modals/BulkEmailDrawer/BulkEmailDrawer.module.scss
new file mode 100644
index 000000000..437085590
--- /dev/null
+++ b/frontend/src/components/modals/BulkEmailDrawer/BulkEmailDrawer.module.scss
@@ -0,0 +1,4 @@
+.label {
+ display: block;
+ font-weight: bold;
+}
diff --git a/frontend/src/components/modals/BulkEmailDrawer/index.js b/frontend/src/components/modals/BulkEmailDrawer/index.js
new file mode 100644
index 000000000..ca2492573
--- /dev/null
+++ b/frontend/src/components/modals/BulkEmailDrawer/index.js
@@ -0,0 +1,203 @@
+import React, { useState, useCallback } from 'react';
+import styles from './BulkEmailDrawer.module.scss';
+
+import { connect } from 'react-redux';
+import { Drawer, Button as AntButton, Divider as AntDivider, Input, Modal, notification, Popconfirm } from 'antd';
+
+import Divider from 'components/generic/Divider';
+import * as AuthSelectors from 'redux/auth/selectors';
+import * as OrganiserSelectors from 'redux/organiser/selectors';
+import EmailService from 'services/email';
+
+const BulkEmailDrawer = ({ registrationIds, buttonProps, user, idToken, event }) => {
+ const [visible, setVisible] = useState(false);
+ const [testEmail, setTestEmail] = useState(user.email);
+ const [testModalVisible, setTestModalVisible] = useState(false);
+ const [testModalLoading, setTestModalLoading] = useState(false);
+
+ const [subject, setSubject] = useState();
+ const [subtitle, setSubtitle] = useState();
+ const [headerImage, setHeaderImage] = useState();
+ const [body, setBody] = useState();
+ const [ctaText, setCtaText] = useState();
+ const [ctaLink, setCtaLink] = useState();
+ const [messageId, setMessageId] = useState();
+
+ const params = {
+ subject: subject,
+ subtitle: subtitle,
+ header_image: headerImage,
+ body: body,
+ cta_text: ctaText,
+ cta_link: ctaLink
+ };
+
+ const handleClose = useCallback(() => {
+ setVisible(false);
+ }, []);
+
+ const handleCloseModal = useCallback(() => {
+ setTestModalVisible(false);
+ }, []);
+
+ const handleTestEmail = useCallback(() => {
+ setTestModalLoading(true);
+ EmailService.sendPreviewEmail(idToken, event.slug, testEmail, params)
+ .then(() => {
+ notification.success({
+ message: 'Success',
+ description: 'Check your inbox!'
+ });
+ })
+ .catch(err => {
+ console.log(err);
+ notification.error({
+ message: 'Something went wrong...',
+ description: 'Did you enter a valid email address?'
+ });
+ })
+ .finally(() => {
+ setTestModalLoading(false);
+ setTestModalVisible(false);
+ });
+ return null;
+ }, [idToken, event.slug, testEmail, params]);
+
+ const handleConfirm = useCallback(() => {
+ window.alert('Try again later :)');
+ }, []);
+
+ return (
+
+
+ setTestEmail(e.target.value)}
+ />
+
+
+ Bulk email
+
+ Here you can send an email to all selected participants. Type in your own email address below to
+ test the email before sending it out to everyone!
+
+ Header image url
+ setHeaderImage(e.target.value)}
+ type="text"
+ size="large"
+ placeholder="https://"
+ >
+ Url to a header image for your email
+
+ Message subject
+ setSubject(e.target.value)}
+ type="text"
+ size="large"
+ placeholder="Subject"
+ >
+ The subject line of your message
+
+ Message subtitle
+ setSubtitle(e.target.value)}
+ type="text"
+ size="large"
+ placeholder="Your subtitle"
+ >
+ A subtitle to be shown under the subject (optional)
+
+ Message body
+ setBody(e.target.value)}
+ autosize={{
+ minRows: 10,
+ maxRows: 20
+ }}
+ >
+ The content of your email
+
+ Unique message id
+ setMessageId(e.target.value)}
+ size="large"
+ placeholder="something-you-will-remember"
+ />
+
+ If you want, you can enter a unique message id here. Messages with the same id will only be sent
+ once to a given participant - this is useful if you want to avoid sending out the same message to a
+ participant who has already received it earlier.
+
+
+ Call to action
+ setCtaText(e.target.value)}
+ size="large"
+ placeholder="Click this button"
+ />
+
+ If your want a Call to Action button in your email, enter the text for the button here.
+
+
+ Call to action link
+ setCtaLink(e.target.value)}
+ size="large"
+ placeholder="https://..."
+ />
+ Enter the link where your Call to Action button should lead
+
+
+ Test email
+
+
+
+
+ Send to {registrationIds.length} recipients
+
+
+
+
+
+ );
+};
+
+const mapState = state => ({
+ user: AuthSelectors.getCurrentUser(state),
+ event: OrganiserSelectors.event(state),
+ idToken: AuthSelectors.getIdToken(state)
+});
+
+export default connect(mapState)(BulkEmailDrawer);
diff --git a/frontend/src/pages/Account/AccountDashboard/index.js b/frontend/src/pages/Account/AccountDashboard/index.js
index dbcbd7833..7351aaed9 100644
--- a/frontend/src/pages/Account/AccountDashboard/index.js
+++ b/frontend/src/pages/Account/AccountDashboard/index.js
@@ -19,7 +19,7 @@ const AccountDashboard = ({ registrations, updateRegistrations }) => {
Your registrations
{registrations.map(registration => (
-
+
))}
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
new file mode 100644
index 000000000..0309583ba
--- /dev/null
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
@@ -0,0 +1,168 @@
+import React from 'react';
+
+import { connect } from 'react-redux';
+import { Col } from 'antd';
+
+import { RegistrationStatuses } from '@hackjunction/shared';
+import NotificationBlock from 'components/generic/NotificationBlock';
+import Button from 'components/generic/Button';
+import Divider from 'components/generic/Divider';
+
+import * as DashboardSelectors from 'redux/dashboard/selectors';
+
+const STATUSES = RegistrationStatuses.asObject;
+
+const RegistrationStatusBlock = ({ event, registration }) => {
+ if (!registration || !event) return null;
+
+ const PENDING_STATUSES = [STATUSES.pending.id, STATUSES.softAccepted.id, STATUSES.softRejected.id];
+
+ if (PENDING_STATUSES.indexOf(registration.status) !== -1) {
+ return (
+
+
+
+ }
+ />
+
+ );
+ }
+
+ if (registration.status === STATUSES.accepted.id) {
+ return (
+
+
+ window.alert('Confirm this stuff!') }}
+ />
+ }
+ />
+
+ );
+ }
+
+ if (registration.status === STATUSES.rejected.id) {
+ return (
+
+
+
+ }
+ />
+
+ );
+ }
+
+ if (registration.status === STATUSES.confirmed.id) {
+ return (
+
+
+
+
+
+
+
+ window.alert('Cancel this shit!') }}
+ />
+
+ }
+ />
+
+ );
+ }
+
+ if (registration.status === STATUSES.cancelled.id) {
+ return (
+
+
+
+
+
+
+ If something has gone horribly wrong and you've cancelled your registration in error,
+ please reach out to us at hello@hackjunction.com as soon as possible and we might be
+ able to still sort this out.
+
+
+ }
+ />
+
+ );
+ }
+
+ return null;
+};
+
+const mapState = state => ({
+ event: DashboardSelectors.event(state),
+ registration: DashboardSelectors.registration(state)
+});
+
+export default connect(mapState)(RegistrationStatusBlock);
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TeamStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TeamStatusBlock.js
new file mode 100644
index 000000000..099fa0193
--- /dev/null
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TeamStatusBlock.js
@@ -0,0 +1,100 @@
+import React from 'react';
+
+import { connect } from 'react-redux';
+import { Col } from 'antd';
+
+import { RegistrationStatuses } from '@hackjunction/shared';
+import NotificationBlock from 'components/generic/NotificationBlock';
+import Button from 'components/generic/Button';
+import Divider from 'components/generic/Divider';
+
+import * as DashboardSelectors from 'redux/dashboard/selectors';
+
+const STATUSES = RegistrationStatuses.asObject;
+
+const TeamStatusBlock = ({ event, registration, hasTeam, appliedAsTeam, isTeamLocked }) => {
+ if (!registration || !event) return null;
+
+ const PENDING_STATUSES = [STATUSES.pending.id, STATUSES.softAccepted.id, STATUSES.softRejected.id];
+
+ if (PENDING_STATUSES.indexOf(registration.status) !== -1) {
+ if (appliedAsTeam) {
+ if (!hasTeam) {
+ return (
+
+
+
+ }
+ />
+
+ );
+ }
+
+ if (!isTeamLocked) {
+ return (
+
+
+
+ }
+ />
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+ }
+ }
+ return null;
+};
+
+const mapState = state => ({
+ event: DashboardSelectors.event(state),
+ registration: DashboardSelectors.registration(state),
+ appliedAsTeam: DashboardSelectors.appliedAsTeam(state),
+ hasTeam: DashboardSelectors.hasTeam(state),
+ isTeamLocked: DashboardSelectors.isTeamLocked(state)
+});
+
+export default connect(mapState)(TeamStatusBlock);
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
index a6b371d2a..3ac25ef75 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
@@ -1,140 +1,18 @@
import React from 'react';
import styles from './EventDashboardHomeRegistration.module.scss';
-import { connect } from 'react-redux';
+import { Row } from 'antd';
-import * as DashboardSelectors from 'redux/dashboard/selectors';
-
-import Divider from 'components/generic/Divider';
-import Button from 'components/generic/Button';
-import NotificationBlock from 'components/generic/NotificationBlock';
-
-const EventDashboardHomeRegistration = ({ event, registration, appliedAsTeam, hasTeam, isTeamLocked }) => {
- if (!registration || !event) return null;
-
- function renderRegistrationStatus() {
- switch (registration.status) {
- case 'rejected': {
- return (
-
- );
- }
- case 'accepted': {
- return (
-
- );
- }
- default: {
- return (
-
- }
- />
- );
- }
- }
- }
-
- function renderTeamStatus() {
- if (appliedAsTeam) {
- if (!hasTeam) {
- return (
-
- }
- />
- );
- }
-
- if (!isTeamLocked) {
- return (
-
- }
- />
- );
- }
-
- return (
-
- );
- }
- return null;
- }
+import RegistrationStatusBlock from './RegistrationStatusBlock';
+import TeamStatusBlock from './TeamStatusBlock';
+const EventDashboardHomeRegistration = () => {
return (
-
- {renderTeamStatus()}
-
- {renderRegistrationStatus()}
-
+
+
+
+
);
};
-const mapStateToProps = state => ({
- event: DashboardSelectors.event(state),
- registration: DashboardSelectors.registration(state),
- appliedAsTeam: DashboardSelectors.appliedAsTeam(state),
- hasTeam: DashboardSelectors.hasTeam(state),
- isTeamLocked: DashboardSelectors.isTeamLocked(state),
- team: DashboardSelectors.team(state)
-});
-
-export default connect(mapStateToProps)(EventDashboardHomeRegistration);
+export default EventDashboardHomeRegistration;
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/index.js b/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
index 77565f301..801aa25a2 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
@@ -11,7 +11,6 @@ import EventDashboardHomeRegistration from './EventDashboardHomeRegistration';
import EventUtils from 'utils/events';
import EventConstants from 'constants/events';
import * as DashboardSelectors from 'redux/dashboard/selectors';
-
const EventDashboardHome = ({ event, registration }) => {
const [currentStep, setCurrentStep] = useState([]);
@@ -43,7 +42,6 @@ const EventDashboardHome = ({ event, registration }) => {
} />
- } />
} />
} />
@@ -51,7 +49,6 @@ const EventDashboardHome = ({ event, registration }) => {
} />
- } />
} />
} />
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
index f1c341894..838860f58 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
@@ -9,6 +9,7 @@ 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 }) => {
@@ -16,10 +17,14 @@ const SearchAttendeesPage = ({ registrations, registrationsLoading, filters }) =
const renderBulkActions = () => {
if (!registrations.length) return null;
+ const ids = registrations.map(r => r._id);
return (
-
{registrations.length} registrations
-
r._id)} />
+
+ {registrations.length} registrations
+
+
+
);
};
diff --git a/frontend/src/services/email.js b/frontend/src/services/email.js
new file mode 100644
index 000000000..5683cf06e
--- /dev/null
+++ b/frontend/src/services/email.js
@@ -0,0 +1,24 @@
+import _axios from 'services/axios';
+
+const EmailService = {};
+
+function config(idToken) {
+ return {
+ headers: {
+ Authorization: `Bearer ${idToken}`
+ }
+ };
+}
+
+const BASE_ROUTE = '/email';
+
+EmailService.sendPreviewEmail = (idToken, slug, to, params) => {
+ const data = {
+ to,
+ params
+ };
+ console.log('SENDING TEST WITH', params);
+ return _axios.post(`${BASE_ROUTE}/${slug}/preview`, data, config(idToken));
+};
+
+export default EmailService;
diff --git a/shared/constants/registration-statuses.js b/shared/constants/registration-statuses.js
index f3666d135..d462c2e3b 100644
--- a/shared/constants/registration-statuses.js
+++ b/shared/constants/registration-statuses.js
@@ -47,6 +47,14 @@ const RegistrationStatuses = {
allowAssign: false,
allowEdit: false
},
+ cancelled: {
+ id: 'cancelled',
+ label: 'Cancelled',
+ description: 'Has cancelled their participation',
+ color: '#ff7c0c',
+ allowAssign: false,
+ allowEdit: false
+ },
checkedIn: {
id: 'checkedIn',
label: 'Checked In',
From 761ac7830b2105924cefac1e4c99df2c4786ef21 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 18:17:55 +0300
Subject: [PATCH 08/21] add team page filters
---
.../OrganiserEditEventReview/TeamsPage.js | 43 ++++++++++++++-----
.../TeamsPage.module.scss | 2 +
2 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
index 93f4c2811..03a272c6c 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
@@ -2,16 +2,19 @@ import React, { useEffect, useMemo, useState } from 'react';
import styles from './TeamsPage.module.scss';
import { connect } from 'react-redux';
-import { Tag, Table, Switch } from 'antd';
+import { Tag, Table, Switch, Input } from 'antd';
import { sumBy } from 'lodash-es';
import * as OrganiserSelectors from 'redux/organiser/selectors';
+import PageWrapper from 'components/PageWrapper';
import AttendeeTable from 'components/tables/AttendeeTable';
import BulkEditRegistrationDrawer from 'components/modals/BulkEditRegistrationDrawer';
const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrations, registrationsMap }) => {
const [onlyLocked, setOnlyLocked] = useState(false);
+ const [onlyReviewed, setOnlyReviewed] = useState(false);
+ const [minRating, setMinRating] = useState(0);
const renderAttendees = team => {
return (
@@ -43,11 +46,14 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
.filter(member => typeof member !== 'undefined');
const ownerMapped = registrationsMap[team.owner] || {};
const allMembers = membersMapped.concat(ownerMapped);
+ const reviewedCount = allMembers.filter(member => member && member.rating).length;
+ const memberCount = allMembers.length;
return {
...team,
owner: ownerMapped,
members: allMembers,
- avgRating: (sumBy(allMembers, m => m.rating || 0) / allMembers.length).toFixed(2)
+ avgRating: (sumBy(allMembers, m => m.rating || 0) / allMembers.length).toFixed(2),
+ reviewedPercent: Math.floor((reviewedCount * 100) / memberCount)
};
});
}, [teams, registrationsMap]);
@@ -56,16 +62,35 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
if (onlyLocked && !team.locked) {
return false;
}
+ if (onlyReviewed && team.reviewedPercent < 100) {
+ return false;
+ }
+ if (minRating && team.avgRating < minRating) {
+ return false;
+ }
return true;
});
return (
-
+
Only locked teams
+
+ Only fully reviewed teams
+
+
+
+ Min. team rating
+ setMinRating(e.target.value)}
+ />
+
{teamsFiltered.length} teams
@@ -101,15 +126,13 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
/>
{
- const reviewedCount = members.filter(member => member && member.rating).length;
- const memberCount = members.length;
- if (reviewedCount === memberCount) {
+ render={percent => {
+ if (percent === 100) {
return 100% ;
} else {
- return {Math.floor((reviewedCount * 100) / memberCount)}% ;
+ return {percent}% ;
}
}}
/>
@@ -120,7 +143,7 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
render={locked => (locked ? Yes : No )}
/>
-
+
);
};
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss
index c95b6ea91..645cb84cc 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.module.scss
@@ -14,9 +14,11 @@
.filters {
display: flex;
flex-direction: row;
+ align-items: flex-start;
flex-wrap: wrap;
.filterItem {
+ margin-bottom: 1rem;
padding: 1rem;
display: flex;
flex-direction: row;
From e9597a76742fd8294f65c032cf18c5e21897a801 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 18:25:27 +0300
Subject: [PATCH 09/21] Add proper loading and feedback on bulk accept
functionality
---
.../OrganiserEditEventReview/AdminPage.js | 28 +++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
index cdbf46411..1ff4d8e0f 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AdminPage.js
@@ -1,10 +1,10 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
import './AdminPage.scss';
import { connect } from 'react-redux';
import { groupBy, filter } from 'lodash-es';
import { RegistrationStatuses } from '@hackjunction/shared';
-import { Row, Col, Card, Statistic, Tag, List, Button as AntButton } from 'antd';
+import { Row, Col, Card, Statistic, Tag, List, Button as AntButton, notification } from 'antd';
import Divider from 'components/generic/Divider';
import * as OrganiserSelectors from 'redux/organiser/selectors';
import * as AuthSelectors from 'redux/auth/selectors';
@@ -13,6 +13,8 @@ import RegistrationsService from 'services/registrations';
const STATUSES = RegistrationStatuses.asObject;
const AdminPage = ({ registrations, idToken, event }) => {
+ const [bulkAcceptLoading, setBulkAcceptLoading] = useState(false);
+ const [bulkRejectLoading, setBulkRejectLoading] = useState(false);
const groupedByStatus = useMemo(() => {
return groupBy(registrations, 'status');
}, [registrations]);
@@ -27,16 +29,22 @@ const AdminPage = ({ registrations, idToken, event }) => {
};
const handleBulkAccept = () => {
- console.log('BULK ACCEPT BEGIN');
+ setBulkAcceptLoading(true);
RegistrationsService.bulkAcceptRegistrationsForEvent(idToken, event.slug)
.then(data => {
- console.log('BULK ACCEPT DONE', data);
+ notification.success({
+ message: 'Success!',
+ description: 'All soft accepted registrations have been accepted'
+ });
})
.catch(err => {
- console.log('BULK ACCEPT ERR', err);
+ notification.error({
+ message: 'Something went wrong...',
+ description: "Are you sure you're connected to the internet?"
+ });
})
.finally(() => {
- console.log('BULK ACCEPT FINALLY');
+ setBulkAcceptLoading(false);
});
};
@@ -50,7 +58,7 @@ const AdminPage = ({ registrations, idToken, event }) => {
description:
'Change the status of all Soft Accepted participants to Accepted, and notify them via email that they have been accepted to the event!',
extra: (
-
+
Accept
)
@@ -60,7 +68,11 @@ const AdminPage = ({ registrations, idToken, event }) => {
description:
'Change the status of all Soft Rejected participants to Rejected, and notify them via email that they did not make it.',
extra: (
- window.alert('Get permission from Juuso to do this ;--)')} type="link">
+ window.alert('Get permission from Juuso to do this ;--)')}
+ type="link"
+ loading={bulkRejectLoading}
+ >
Reject
)
From 2c6376ffaf2c9668444d113f8e068bd490905579 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 18:34:42 +0300
Subject: [PATCH 10/21] Fix filters
---
frontend/package-lock.json | 87 ++++++-------------
frontend/package.json | 1 +
.../OrganiserEditEventReview/TeamsPage.js | 2 +-
frontend/src/utils/filters.js | 23 ++---
4 files changed, 42 insertions(+), 71 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ef4da176b..f5d95cf02 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -3292,8 +3292,7 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"aproba": {
"version": "1.2.0",
@@ -3311,13 +3310,11 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3330,18 +3327,15 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3444,8 +3438,7 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"ini": {
"version": "1.3.5",
@@ -3455,7 +3448,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3468,20 +3460,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3498,7 +3487,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3571,8 +3559,7 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"object-assign": {
"version": "4.1.1",
@@ -3582,7 +3569,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3658,8 +3644,7 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -3689,7 +3674,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -3707,7 +3691,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3746,13 +3729,11 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
}
}
},
@@ -8200,8 +8181,7 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"aproba": {
"version": "1.2.0",
@@ -8219,13 +8199,11 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -8238,18 +8216,15 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"core-util-is": {
"version": "1.0.2",
@@ -8352,8 +8327,7 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"ini": {
"version": "1.3.5",
@@ -8363,7 +8337,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -8376,20 +8349,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -8406,7 +8376,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -8479,8 +8448,7 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"object-assign": {
"version": "4.1.1",
@@ -8490,7 +8458,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -8566,8 +8533,7 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -8597,7 +8563,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -8615,7 +8580,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -8654,13 +8618,11 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
}
}
}
@@ -10597,6 +10559,11 @@
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
+ "object-path": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz",
+ "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk="
+ },
"object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 686f774fb..ff82dc6aa 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,6 +22,7 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.25",
"node-sass": "^4.11.0",
+ "object-path": "^0.11.4",
"react": "^16.8.1",
"react-animate-height": "^2.0.15",
"react-app-rewired": "^2.1.3",
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
index 03a272c6c..30a0edc6d 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useMemo, useState } from 'react';
import styles from './TeamsPage.module.scss';
import { connect } from 'react-redux';
diff --git a/frontend/src/utils/filters.js b/frontend/src/utils/filters.js
index 711b0c8bb..2bf52a4af 100644
--- a/frontend/src/utils/filters.js
+++ b/frontend/src/utils/filters.js
@@ -1,4 +1,5 @@
import { difference } from 'lodash-es';
+import objectPath from 'object-path';
const isEmpty = value => {
if (Array.isArray(value)) {
@@ -19,10 +20,12 @@ const contains = (value, answer) => {
if (Array.isArray(answer)) {
return answer.indexOf(value) !== -1;
} else if (typeof answer === 'string') {
- return answer
- .toLowerCase()
- .trim()
- .indexOf(value.toLowerCase().trim());
+ return (
+ answer
+ .toLowerCase()
+ .trim()
+ .indexOf(value.toLowerCase().trim()) !== -1
+ );
}
return false;
};
@@ -74,22 +77,22 @@ const filter = (registration, filter) => {
);
}
case 'field-equals': {
- return equals(filter.value, registration.answers[filter.field]);
+ return equals(filter.value, objectPath.get(registration, `answers.${filter.field}`));
}
case 'field-nequals': {
- return !equals(filter.value, registration.answers[filter.field]);
+ return !equals(filter.value, objectPath.get(registration, `answers.${filter.field}`));
}
case 'field-contains': {
- return contains(filter.value, registration.answers[filter.field]);
+ return contains(filter.value, objectPath.get(registration, `answers.${filter.field}`));
}
case 'field-not-contains': {
- return !contains(filter.value, registration.answers[filter.field]);
+ return !contains(filter.value, objectPath.get(registration, `answers.${filter.field}`));
}
case 'field-empty': {
- return isEmpty(registration.answers[filter.field]);
+ return isEmpty(objectPath.get(registration, `answers.${filter.field}`));
}
case 'field-not-empty': {
- return !isEmpty(registration.answers[filter.field]);
+ return !isEmpty(objectPath.get(registration, `answers.${filter.field}`));
}
default:
return true;
From cf2b943447827afe0ab4831cda5054087c651cc3 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 18:54:06 +0300
Subject: [PATCH 11/21] Cleaner filters
---
.../AttendeeFilters.js | 96 ++++++-------------
1 file changed, 31 insertions(+), 65 deletions(-)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
index 6ace4eccc..942937663 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
@@ -201,76 +201,80 @@ const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) =>
);
};
- const renderItemValue = filter => {
+ const renderItemValue = (filter, label) => {
switch (filter.type) {
case 'status-equals':
case 'status-nequals': {
- return RegistrationStatuses.asArray
+ 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 ;
+ return (
+
+ {label}
+
+ );
}
case 'tags-contain':
case 'tags-not-contain':
- return event.tags
+ 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}
+
+ {filter.field} EQUALS {filter.value}
);
case 'field-nequals':
return (
-
- {filter.field}
- IS NOT
- {filter.value}
+
+ {filter.field} DOES NOT EQUAL {filter.value}
);
case 'field-empty':
return (
-
- {filter.field}
- IS EMPTY
+
+ {filter.field} IS EMPTY
);
case 'field-not-empty':
return (
-
- {filter.field}
- IS NOT EMPTY
+
+ {filter.field} IS NOT EMPTY
);
case 'field-contains': {
return (
-
- {filter.field}
- CONTAINS
- {filter.value}
+
+ {filter.field} CONTAINS {filter.value}
);
}
case 'field-not-contains': {
return (
-
- {filter.field}
- DOES NOT CONTAIN
- {filter.value}
+
+ {filter.field} DOES NOT CONTAIN {filter.value}
);
}
@@ -287,15 +291,7 @@ const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) =>
- {label}
- handleRemove(idx)}>
- Remove
-
-
- }
- description={{renderItemValue(filter)}
}
+ title={{renderItemValue(filter, label)}
}
/>
);
});
@@ -324,36 +320,6 @@ const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) =>
);
-
- // return (
- //
- //
- //
- //
- // Rating
- //
- //
- // First name
- // Last name
- //
- //
- // Terminal
- //
- //
- //
- //
- //
- // Exists
- // Does not exist
- // Is at least
- // Is at most
- //
- //
- //
- //
- //
- //
- // );
};
const mapState = state => ({
From dbfa115f8ca124f1a2fe56378d1738f097cc693a Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 19:00:31 +0300
Subject: [PATCH 12/21] re-enable removing filters
---
.../OrganiserEditEventReview/AttendeeFilters.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
index 942937663..eaf8ee494 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
@@ -292,6 +292,11 @@ const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) =>
status="finish"
key={filter.type + filter.value}
title={{renderItemValue(filter, label)}
}
+ description={
+ handleRemove(idx)}>
+ Remove filter
+
+ }
/>
);
});
From 7c6faaa28a9e2746038146435a1eb71a0db77619 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Sun, 22 Sep 2019 19:05:43 +0300
Subject: [PATCH 13/21] Fix filter labels
---
.../OrganiserEditEventReview/AttendeeFilters.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
index eaf8ee494..12c3aee23 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/AttendeeFilters.js
@@ -279,7 +279,7 @@ const AttendeeFilters = ({ event, registrations, filters = [], setFilters }) =>
);
}
default:
- return null;
+ return {label} ;
}
};
From 52f5d137e70e47aedf9434d6b9ce5b560fc662ae Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 09:05:19 +0300
Subject: [PATCH 14/21] asd
---
backend/package-lock.json | 6 +++---
backend/package.json | 2 +-
frontend/package-lock.json | 6 +++---
frontend/package.json | 2 +-
shared/package-lock.json | 2 +-
shared/package.json | 2 +-
6 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 0c6365c6c..6e53ce94f 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -25,9 +25,9 @@
}
},
"@hackjunction/shared": {
- "version": "1.1.44",
- "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.44.tgz",
- "integrity": "sha512-2EG2wNFFLGmzoMp6kF0xjmIoCop9SiRzKGxez/INVLf+4mcu8vcInIyfd8wSRD+kMPO6BSvsb2M+jJa6bJ9qig=="
+ "version": "1.1.45",
+ "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.45.tgz",
+ "integrity": "sha512-vGFDj5LnuI8ExwBYJZaQ97dLQROnJJJzX1A/R3r6EmLQlw3RT4UK2MYYQ335od1BcaIwMUmDdrHvyEKcCG6ILQ=="
},
"@sendgrid/client": {
"version": "6.4.0",
diff --git a/backend/package.json b/backend/package.json
index 165884ed8..ca8a58320 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@hackjunction/shared": "^1.1.44",
+ "@hackjunction/shared": "^1.1.45",
"@sendgrid/client": "^6.4.0",
"@sendgrid/mail": "^6.4.0",
"auth0": "^2.17.0",
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index f5d95cf02..cd2696f1e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1020,9 +1020,9 @@
"integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA=="
},
"@hackjunction/shared": {
- "version": "1.1.44",
- "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.44.tgz",
- "integrity": "sha512-2EG2wNFFLGmzoMp6kF0xjmIoCop9SiRzKGxez/INVLf+4mcu8vcInIyfd8wSRD+kMPO6BSvsb2M+jJa6bJ9qig=="
+ "version": "1.1.45",
+ "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.45.tgz",
+ "integrity": "sha512-vGFDj5LnuI8ExwBYJZaQ97dLQROnJJJzX1A/R3r6EmLQlw3RT4UK2MYYQ335od1BcaIwMUmDdrHvyEKcCG6ILQ=="
},
"@hapi/address": {
"version": "2.0.0",
diff --git a/frontend/package.json b/frontend/package.json
index ff82dc6aa..4846a5ccc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@hackjunction/shared": "^1.1.44",
+ "@hackjunction/shared": "^1.1.45",
"antd": "^3.18.2",
"auth0-js": "^9.10.0",
"axios": "^0.18.0",
diff --git a/shared/package-lock.json b/shared/package-lock.json
index 87f733287..eb0651471 100644
--- a/shared/package-lock.json
+++ b/shared/package-lock.json
@@ -1,5 +1,5 @@
{
"name": "@hackjunction/shared",
- "version": "1.1.44",
+ "version": "1.1.45",
"lockfileVersion": 1
}
diff --git a/shared/package.json b/shared/package.json
index 7297a5687..0dbb3218c 100644
--- a/shared/package.json
+++ b/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@hackjunction/shared",
- "version": "1.1.44",
+ "version": "1.1.45",
"description": "",
"main": "index.js",
"scripts": {
From 0748168bad0a07798b3096dbeb5c69f81f300e1c Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 14:32:05 +0300
Subject: [PATCH 15/21] Change shared code dependecy to use file:path instead
of remote
---
backend/modules/registration/model.js | 6 +-
backend/package-lock.json | 243 ++++--
backend/package.json | 2 +-
frontend/package-lock.json | 706 ++++++++++++++----
frontend/package.json | 3 +-
frontend/src/assets/images/visa_signature.jpg | Bin 0 -> 4866 bytes
.../src/assets/logos/wordmark_black_small.png | Bin 0 -> 5019 bytes
.../VisaInvitationDrawer.module.scss | 3 +
.../VisaInvitationDrawer/VisaInvitationPDF.js | 104 +++
.../modals/VisaInvitationDrawer/index.js | 111 +++
frontend/src/index.js | 8 +
.../RegistrationStatusBlock.js | 40 +-
.../TravelGrantStatusBlock.js | 61 ++
.../VisaInvitationBlock.js | 48 ++
.../EventDashboardHomeRegistration/index.js | 4 +
frontend/src/utils/filters.js | 10 +-
16 files changed, 1103 insertions(+), 246 deletions(-)
create mode 100644 frontend/src/assets/images/visa_signature.jpg
create mode 100644 frontend/src/assets/logos/wordmark_black_small.png
create mode 100644 frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationDrawer.module.scss
create mode 100644 frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationPDF.js
create mode 100644 frontend/src/components/modals/VisaInvitationDrawer/index.js
create mode 100644 frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
create mode 100644 frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/VisaInvitationBlock.js
diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js
index c397cef3f..aad0e1a40 100644
--- a/backend/modules/registration/model.js
+++ b/backend/modules/registration/model.js
@@ -58,9 +58,9 @@ RegistrationSchema.post('save', function(doc, next) {
const ACCEPTED = RegistrationStatuses.asObject.accepted.id;
const REJECTED = RegistrationStatuses.asObject.rejected.id;
/** If a registration was just created, create an email notification about it */
- if (this._wasNew) {
- EmailTaskController.createRegisteredTask(doc.user, doc.event, true);
- }
+ // if (this._wasNew) {
+ // EmailTaskController.createRegisteredTask(doc.user, doc.event, true);
+ // }
/** If a registration is accepted, create an email notification about it */
if (this._previousStatus !== ACCEPTED && this.status === ACCEPTED) {
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 6e53ce94f..df76a536d 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -25,9 +25,7 @@
}
},
"@hackjunction/shared": {
- "version": "1.1.45",
- "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.45.tgz",
- "integrity": "sha512-vGFDj5LnuI8ExwBYJZaQ97dLQROnJJJzX1A/R3r6EmLQlw3RT4UK2MYYQ335od1BcaIwMUmDdrHvyEKcCG6ILQ=="
+ "version": "file:../shared"
},
"@sendgrid/client": {
"version": "6.4.0",
@@ -1839,24 +1837,29 @@
"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==",
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -1866,13 +1869,17 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true,
+ "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==",
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1880,34 +1887,43 @@
},
"chownr": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
+ "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true,
+ "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=",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -1916,25 +1932,29 @@
},
"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==",
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true,
"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=",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -1943,13 +1963,15 @@
},
"fs.realpath": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true,
"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=",
"dev": true,
"optional": true,
"requires": {
@@ -1965,7 +1987,8 @@
},
"glob": {
"version": "7.1.3",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"optional": true,
"requires": {
@@ -1979,13 +2002,15 @@
},
"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=",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -1994,7 +2019,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==",
"dev": true,
"optional": true,
"requires": {
@@ -2003,7 +2029,8 @@
},
"inflight": {
"version": "1.0.6",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"optional": true,
"requires": {
@@ -2013,46 +2040,58 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true,
+ "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==",
"dev": true,
"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=",
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"isarray": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true,
"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==",
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+ "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -2060,7 +2099,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==",
"dev": true,
"optional": true,
"requires": {
@@ -2069,21 +2109,25 @@
},
"mkdirp": {
"version": "0.5.1",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
},
"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==",
"dev": true,
"optional": true
},
"needle": {
"version": "2.3.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz",
+ "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
"dev": true,
"optional": true,
"requires": {
@@ -2094,7 +2138,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==",
"dev": true,
"optional": true,
"requires": {
@@ -2112,7 +2157,8 @@
},
"nopt": {
"version": "4.0.1",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"dev": true,
"optional": true,
"requires": {
@@ -2122,13 +2168,15 @@
},
"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==",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -2138,7 +2186,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==",
"dev": true,
"optional": true,
"requires": {
@@ -2150,38 +2199,46 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true,
+ "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=",
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
},
"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=",
"dev": true,
"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=",
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"dev": true,
"optional": true,
"requires": {
@@ -2191,19 +2248,22 @@
},
"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=",
"dev": true,
"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==",
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.8",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"optional": true,
"requires": {
@@ -2215,7 +2275,8 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
}
@@ -2223,7 +2284,8 @@
},
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -2238,7 +2300,8 @@
},
"rimraf": {
"version": "2.6.3",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"optional": true,
"requires": {
@@ -2247,43 +2310,52 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "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==",
"dev": true,
"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==",
"dev": true,
"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==",
"dev": true,
"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=",
"dev": true,
"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=",
"dev": true,
"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=",
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -2292,7 +2364,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==",
"dev": true,
"optional": true,
"requires": {
@@ -2301,21 +2374,25 @@
},
"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=",
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"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=",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -2330,13 +2407,15 @@
},
"util-deprecate": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true,
"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==",
"dev": true,
"optional": true,
"requires": {
@@ -2345,13 +2424,17 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true,
- "dev": true
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+ "dev": true,
+ "optional": true
}
}
},
diff --git a/backend/package.json b/backend/package.json
index ca8a58320..553aea050 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@hackjunction/shared": "^1.1.45",
+ "@hackjunction/shared": "file:../shared",
"@sendgrid/client": "^6.4.0",
"@sendgrid/mail": "^6.4.0",
"auth0": "^2.17.0",
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index cd2696f1e..93ea280a1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1020,9 +1020,7 @@
"integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA=="
},
"@hackjunction/shared": {
- "version": "1.1.45",
- "resolved": "https://registry.npmjs.org/@hackjunction/shared/-/shared-1.1.45.tgz",
- "integrity": "sha512-vGFDj5LnuI8ExwBYJZaQ97dLQROnJJJzX1A/R3r6EmLQlw3RT4UK2MYYQ335od1BcaIwMUmDdrHvyEKcCG6ILQ=="
+ "version": "file:../shared"
},
"@hapi/address": {
"version": "2.0.0",
@@ -1410,6 +1408,95 @@
"style-value-types": "^3.1.6"
}
},
+ "@react-pdf/fontkit": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/fontkit/-/fontkit-1.13.0.tgz",
+ "integrity": "sha512-g4rxtkSkEbIwDoqZc6ZM2yHq/imqmgGXQuMDnrkM2yI9TpTGhQfJGnz9IrCadrRgOrEx9orxRuTALOxzAce7Kg==",
+ "requires": {
+ "@react-pdf/unicode-properties": "^2.2.0",
+ "brotli": "^1.2.0",
+ "clone": "^1.0.1",
+ "deep-equal": "^1.0.0",
+ "dfa": "^1.0.0",
+ "restructure": "^0.5.3",
+ "tiny-inflate": "^1.0.2",
+ "unicode-trie": "^0.3.0"
+ }
+ },
+ "@react-pdf/pdfkit": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-1.2.0.tgz",
+ "integrity": "sha1-9f4ohXhWdTbljeGZij4T7i5KyTA=",
+ "requires": {
+ "@react-pdf/fontkit": "^1.11.0",
+ "@react-pdf/png-js": "^1.0.0",
+ "lz-string": "^1.4.4"
+ }
+ },
+ "@react-pdf/png-js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-1.0.0.tgz",
+ "integrity": "sha1-APy5adykzoKgp2c0E63gOeR7Nh4="
+ },
+ "@react-pdf/renderer": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-1.6.4.tgz",
+ "integrity": "sha512-/R/NPc3plpZnw8oE6ngoRuVVYV1mlXl3QQd64gK9mUG09T/hMhTJFtGnoJAEFNfAoxoO9P2P/6jxpYCpFUbuxA==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "@react-pdf/fontkit": "^1.13.0",
+ "@react-pdf/pdfkit": "^1.2.0",
+ "@react-pdf/png-js": "^1.0.0",
+ "@react-pdf/textkit": "^0.3.7",
+ "blob-stream": "^0.1.3",
+ "cross-fetch": "^3.0.2",
+ "emoji-regex": "^8.0.0",
+ "is-url": "^1.2.4",
+ "media-engine": "^1.0.3",
+ "page-wrapping": "^1.1.0",
+ "ramda": "^0.26.1",
+ "react": "^16.8.6",
+ "react-reconciler": "^0.20.4",
+ "scheduler": "^0.14.0",
+ "yoga-layout-prebuilt": "^1.9.3"
+ },
+ "dependencies": {
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "scheduler": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.14.0.tgz",
+ "integrity": "sha512-9CgbS06Kki2f4R9FjLSITjZo5BZxPsryiRNyL3LpvrM9WxcVmhlqAOc9E+KQbeI2nqej4JIIbOsfdL51cNb4Iw==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ }
+ }
+ },
+ "@react-pdf/textkit": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-0.3.7.tgz",
+ "integrity": "sha512-JyK06VofTG4/6Mxv9ugqGyKS/nnbg7MXXHmzeYLP+wwxZsgiatjybVBSXI+DW+++UeyUKLrCYQh2RaxliRm+NQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.3",
+ "@react-pdf/unicode-properties": "^2.2.0",
+ "babel-runtime": "^6.26.0",
+ "hyphen": "^1.1.1",
+ "ramda": "^0.26.1"
+ }
+ },
+ "@react-pdf/unicode-properties": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/unicode-properties/-/unicode-properties-2.2.0.tgz",
+ "integrity": "sha1-8QnqrCRM6xCAEdQDjO5Mx4fLQPM=",
+ "requires": {
+ "unicode-trie": "^0.3.0"
+ }
+ },
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@@ -2218,6 +2305,58 @@
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
+ "ast-transform": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz",
+ "integrity": "sha1-dJRAWIh9goPhidlUYAlHvJj+AGI=",
+ "requires": {
+ "escodegen": "~1.2.0",
+ "esprima": "~1.0.4",
+ "through": "~2.3.4"
+ },
+ "dependencies": {
+ "escodegen": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz",
+ "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=",
+ "requires": {
+ "esprima": "~1.0.4",
+ "estraverse": "~1.5.0",
+ "esutils": "~1.0.0",
+ "source-map": "~0.1.30"
+ }
+ },
+ "esprima": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz",
+ "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0="
+ },
+ "estraverse": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz",
+ "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E="
+ },
+ "esutils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz",
+ "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA="
+ },
+ "source-map": {
+ "version": "0.1.43",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+ "optional": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "ast-types": {
+ "version": "0.7.8",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz",
+ "integrity": "sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk="
+ },
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
@@ -2832,6 +2971,19 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
},
+ "blob": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
+ "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE="
+ },
+ "blob-stream": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/blob-stream/-/blob-stream-0.1.3.tgz",
+ "integrity": "sha1-mNZor2mW4PMu9mbQbiFczH13aGw=",
+ "requires": {
+ "blob": "0.0.4"
+ }
+ },
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@@ -2946,6 +3098,14 @@
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
+ "brotli": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz",
+ "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=",
+ "requires": {
+ "base64-js": "^1.1.2"
+ }
+ },
"browser-process-hrtime": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
@@ -3000,6 +3160,16 @@
"safe-buffer": "^5.1.2"
}
},
+ "browserify-optional": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz",
+ "integrity": "sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk=",
+ "requires": {
+ "ast-transform": "0.0.0",
+ "ast-types": "^0.7.0",
+ "browser-resolve": "^1.8.1"
+ }
+ },
"browserify-rsa": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
@@ -3287,21 +3457,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",
@@ -3310,11 +3485,15 @@
},
"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",
"concat-map": "0.0.1"
@@ -3322,29 +3501,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"
@@ -3352,22 +3540,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"
@@ -3375,12 +3567,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",
@@ -3395,7 +3589,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",
@@ -3408,12 +3603,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"
@@ -3421,7 +3618,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"
@@ -3429,7 +3627,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",
@@ -3438,39 +3637,51 @@
},
"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"
}
},
"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"
}
},
"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",
"yallist": "^3.0.0"
@@ -3478,7 +3689,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"
@@ -3486,19 +3698,23 @@
},
"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"
}
},
"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",
@@ -3508,7 +3724,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",
@@ -3525,7 +3742,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",
@@ -3534,12 +3752,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",
@@ -3548,7 +3768,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",
@@ -3559,33 +3780,41 @@
},
"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"
}
},
"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",
@@ -3594,17 +3823,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",
@@ -3615,14 +3847,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",
@@ -3636,7 +3870,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"
@@ -3644,36 +3879,45 @@
},
"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",
"is-fullwidth-code-point": "^1.0.0",
@@ -3682,7 +3926,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"
@@ -3690,19 +3935,23 @@
},
"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"
}
},
"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",
@@ -3716,12 +3965,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"
@@ -3729,11 +3980,15 @@
},
"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
}
}
},
@@ -3835,6 +4090,11 @@
"wrap-ansi": "^2.0.0"
}
},
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
+ },
"clone-deep": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
@@ -4267,6 +4527,22 @@
"gud": "^1.0.0"
}
},
+ "cross-fetch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz",
+ "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==",
+ "requires": {
+ "node-fetch": "2.6.0",
+ "whatwg-fetch": "3.0.0"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ }
+ }
+ },
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
@@ -4832,6 +5108,11 @@
}
}
},
+ "dfa": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
+ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
+ },
"diff-sequences": {
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz",
@@ -7031,6 +7312,11 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
+ "hyphen": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.1.1.tgz",
+ "integrity": "sha512-S6KSoGZWPutjTB7koZ9Ci9xzETBa7GVlNe42r0hF+rhoE/6lLHwEvi2FQSEhyWIjVo46tV94vn96hcsuum1THg=="
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -7574,6 +7860,11 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
+ "is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+ },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@@ -8176,21 +8467,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",
@@ -8199,11 +8495,15 @@
},
"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",
"concat-map": "0.0.1"
@@ -8211,29 +8511,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"
@@ -8241,22 +8550,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"
@@ -8264,12 +8577,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",
@@ -8284,7 +8599,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",
@@ -8297,12 +8613,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"
@@ -8310,7 +8628,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"
@@ -8318,7 +8637,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",
@@ -8327,39 +8647,51 @@
},
"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"
}
},
"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"
}
},
"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",
"yallist": "^3.0.0"
@@ -8367,7 +8699,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"
@@ -8375,19 +8708,23 @@
},
"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"
}
},
"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",
@@ -8397,7 +8734,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",
@@ -8414,7 +8752,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",
@@ -8423,12 +8762,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",
@@ -8437,7 +8778,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",
@@ -8448,33 +8790,41 @@
},
"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"
}
},
"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",
@@ -8483,17 +8833,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",
@@ -8504,14 +8857,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",
@@ -8525,7 +8880,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"
@@ -8533,36 +8889,45 @@
},
"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",
"is-fullwidth-code-point": "^1.0.0",
@@ -8571,7 +8936,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"
@@ -8579,19 +8945,23 @@
},
"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"
}
},
"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",
@@ -8605,12 +8975,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"
@@ -8618,11 +8990,15 @@
},
"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
}
}
}
@@ -9828,6 +10204,11 @@
"yallist": "^2.1.2"
}
},
+ "lz-string": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY="
+ },
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -9916,6 +10297,11 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
"integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA=="
},
+ "media-engine": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
+ "integrity": "sha1-vjGI9s0kPqKkCASjXeWlsDL1ja0="
+ },
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -10809,6 +11195,11 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
+ "page-wrapping": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/page-wrapping/-/page-wrapping-1.1.0.tgz",
+ "integrity": "sha512-DAnqZJ3FHKLXVbdQfvGoHyZRFZL+N1IIZlo2RImFqrZ3scoFS8lOHZoLQxnYbsnCMQkwoEyESd0ZNs5RbEsArA=="
+ },
"pako": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
@@ -13168,6 +13559,17 @@
}
}
},
+ "react-reconciler": {
+ "version": "0.20.4",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.20.4.tgz",
+ "integrity": "sha512-kxERc4H32zV2lXMg/iMiwQHOtyqf15qojvkcZ5Ja2CPkjVohHw9k70pdDBwrnQhLVetUJBSYyqU3yqrlVTOajA==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.13.6"
+ }
+ },
"react-redux": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-6.0.1.tgz",
@@ -13767,6 +14169,14 @@
"signal-exit": "^3.0.2"
}
},
+ "restructure": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz",
+ "integrity": "sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg=",
+ "requires": {
+ "browserify-optional": "^1.0.0"
+ }
+ },
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -15165,6 +15575,11 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
+ "tiny-inflate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.2.tgz",
+ "integrity": "sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c="
+ },
"tiny-invariant": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
@@ -15427,6 +15842,22 @@
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz",
"integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw=="
},
+ "unicode-trie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
+ "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=",
+ "requires": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ },
+ "dependencies": {
+ "pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
+ }
+ }
+ },
"unified": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz",
@@ -16480,6 +16911,11 @@
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
}
}
+ },
+ "yoga-layout-prebuilt": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.9.3.tgz",
+ "integrity": "sha512-9SNQpwuEh2NucU83i2KMZnONVudZ86YNcFk9tq74YaqrQfgJWO3yB9uzH1tAg8iqh5c9F5j0wuyJ2z72wcum2w=="
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index 4846a5ccc..689cf0911 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,7 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@hackjunction/shared": "^1.1.45",
+ "@hackjunction/shared": "file:../shared",
+ "@react-pdf/renderer": "^1.6.4",
"antd": "^3.18.2",
"auth0-js": "^9.10.0",
"axios": "^0.18.0",
diff --git a/frontend/src/assets/images/visa_signature.jpg b/frontend/src/assets/images/visa_signature.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1209ea1422a7f7c41734ec5dbc142d60b7e7ced3
GIT binary patch
literal 4866
zcmd50Y!R|E}_UG8>A>8T_m~)f*48wk)<~Q=|~l&2_kI~
zq(~DLrI&z^1keR32?9nbxzXKkzkTjs_ul>fxO3h)XP)PsGiQGD&Uw!q`ZS#gaGfzhrBW?4LSJ
z01%4>Ab;vOFvjoX%3#LqKSyve=-(ELnf|2>QYZ%hO9m<#+5mkW(EQok_oi=vx38a=
z%E?oJrjfZNnfclC
zHnw*57cM$@cwY9p;*Gr;a5L~0E+{x8>euL)*tqzF^o)C%S=l+cd8K6!${#+esH}S0
z(Ad=6@>}b(7d^dw{R1xtUyY89PfSit&k#uD#iivBAAet2rEY!R-r4;^`}*xWE=D~6
zfW_$lK=$8oaWil+K_FlV>^m+H6Ye{3ZU|IdiJ8X`19Q8y$8gE&}N;j9P_
zE-nNIf|Hw90Ljh6&%=q}6XxR=6ciE?;zEjuil9UVP(mm=!w3ihg)&2#*FgKtN?0rj+EoS-;F@iq)AGSC0AGSC85AA(kh||*@*U={{
z!h?KYH2Yl**=P&PbfRc0|cDsPq+`@&Y^XkIxT>
z3+KI2K9e0bXhBJWp8adCp@J=?Zs8j*p#<5|+pOQ&^XUM@N9t{Tf0V)-KVU~{?BoWi
zzNv6^3mr2LCzyMB+-CS!R9pwtk0aI%l`Xx$rQ}nZCsL>}&G`;FrwA(Jj?seh6D`1v
zJI6KL$^$0wN>uyqaFt2l#t2qg{!bG-7F?D?O-)@_3^-W^{17k^`6qR`Qlog0;=|!X
zQC~=HYUxTLoH9WN_NCm1jLp5C21~=2`DUyu2{FWVY@XgekP^nVu~qKV#>gXZ8EtIiS6JRRt$W6%Y!4i^tyk=
zrRi;WRdJTP9{{9V8j^gRcNe=u^eV}jF|Q6UsS71iT3L`Wr`{!P10UM!dZzsMU1>Rl
z^1jBX7vV?zCbjNX6mF+x=5PwjrB7=CnH!px+mN>VRxgTJKiQ34>o`GOx>Zf~<#viU
z#++PyG+jkz(>>T-&aLkb7EWnexrwon-aul|Og{q72)aIf{Lkz-E)Z>f;Wo_L8~njH
zUcQD_wA(naPOriN1-Ccg}C9Hwo_PI6c)#YwHVfiELM?3QFw7
zF-=*Fogk3OMl9k6zXMC@K|r8%`)Q&^k{ETNmkz`i$`lel`^-^iu>7N9M@BC=WGD3r
zeYv5OW%8-Fa->I^s`E;Bku)oGvsY4n3Ayj?C?}_NT^WN|^+!lc$&pgbP9o
zTz>83#G5~QMr#`gKRwy>*vfk7<8H`V*C);9%$tg@bebpRT0Q#bZnH=g*DrUR8nfzE
zOv-+`FmvghFk!hUX1hV~t9x^kOS8*Q^Kl=4F+C`E!K6O!M*_?+Hy6~tE-mUvJQX}p
zD4u9aMWm=DR(~xXR@knF1?5c-et!2+lZ6W>AT(MCV_8FKE?cem)gx*6S6-a&wp|tn9TU3^V}4aIQ2@Lg6g1@YuXuwuGf-<
z32CXh@oPX09bm>|TBz>{9K1b;60zqldmRpst4=l~rsP~a^-;IO!3u&Gq+IFwy4*H=
zGdjbrAmYK*?9rz8OADKp;tCNu_Mr(OX(h|rbYQ?jpVfIzCSK<_IbTFu+EtDFp^`COvP7EI&eXAV17WavFT{cxU(u{ZL$-a>NMuSp4PdJ
zP0{}HMfASMRZ)IC?;~flhfKWcUb2aoZOq$AH=QjWKlT1xkA)VGl?!!y+V~T};Fdc#
zYgM*_J*8RU)E3SDE`EXjd2M^&QksP!h5Te#)kps)b_nGsD=m6{HPtmb=@TyU^)aW;T7p~&op6T-q5MMsD_Vf!yifC3B
zU2`Fv?z2-5K~>UBNo`G8t1BkcJFj2HDH(DJtF`~4JWwKTUzCH;r3DiYLKpdC+tD}3
z?=QD&QC9byp0!Vz>3eldEZGBn@~*+^-F)YxS~G8?jqgFopUIXZl!)6Zq=i_A)5n_2
zs%JE^rrHdKG7GiEAS}|b6pa6Dg4Fq2O5svLTbpFgPG6!GFsC@P^qbP9`I0x+Jw6z4
zp7KZ))o`D-a%g+7Z>(*DCDuhAMHcmQlxt)JR}GzN8gtK2U*3RAUu3(2%c_g&?!!<-
zd)V6E@zJ)Xex}SFicrFaI(N}=dlrU%KC$y$0@LfssnpAkLZ+C34y?9bQSnOiv+MDf
zeIH?W>*LnfACtKUcsLWPG@0(54|$SdVI0iaQoQdxu25Us6cOhvO1V`e?1g2{D9tU8
zw7J|Fb~;lsDq}fCSKebclxiB+j-`ygbE4jSv1&0}9XqyR*?ITN5eXx)%#7lbw>1pA
zh3$(Dr}R`CpN{Hi5PdGzHXg|L;YtE^t+iOC)PH`aN)_o;QH?trExdT+caBg*)P4jp
z8HaxA3lB;)dp?#9y`Yod_|?OyLg1~5dRo*Nb4_fhNG6}*&d^CU_HUVa)gF*JG1$(d
z`wrcWhTW7HPyi0yM&0`k=k;ogh*%H$GWTA&%V+D@@Gfd-lBd_
zM6ADisJvZ%Z%8}4a{3w8wRnLLL1E#Wnh*M0^qe}!Yism)_qE0jMek$q;-o>zW-21?
z#D`N3b3>tW12>SyTrSvK2gFxV-5c`TiZ42ZNAbr<>-r{?p#pW3ZGpTFIuT<
zzG;JZA^OPlJF`yUk3}$bKUh^yy-MvW(P@&rQ+%`gL;HYv(V|t)CSqoVA9XUB%Dpb`
zUD|nvHf=6Z3DW8H>*9HCCbO|e`Nh7kgn-A=Vj0`1hcy3~40qkT{0-7Fw%&ShFkZtW
zwV0OWHsnV0nl(8n)Z<93`keW*SeLS`Q2rvhXgnxaM7)cIm`YhE`gc#Fj)`~K{$
ztEnix<(O%ZNc-6!k_UQCxi2-O=@HH194(ishR5Nm&}0Os_vD}t>61;*Y~L+hSSgXq
zZ~aw2%U-#Yt$wUCipnvy)=GNJ*7HT4Lxtm=};BE<(s_VO)cuPie{84=9n^Pr`NTsJ(4PD
zFWa)Dq@(QREGu^riw%p<0}qd{!bqDv2kuJOrDn-PCU4I?>mL)PZlr@ERXcIO^7#P2
zTN#~H4dSEN!{g2?4l&iiHCR-0PMyf}!?X(Bnt6*%Wn(j9wGTi|n~u6H-q)w>
z6h;yclsV+h8!YP$;rlZ=TW>&Wv`vZV>Y-}!j0nYAsBOPM)jtfCmfc2J}Pt69;|lcBmKD?EBKxpQqsJYgQs
zoH8SP_Z*ApX`Y^0zKxtAr1jaJ>uc0}IxrK_g(_?bjtFJ0*wx6E-tZ9&G|xB}oU=SP
z%M8aIHIsx))y>Szu1r25O!tU1HOlhHmKszgXag|7X8Sx_G^1osdXMWN*vB`1Wz>CK
zE0cNU_o5Kcad|n>u7s`krn@#Y{mR#>4K&`fDdy)cyok{YD7eiD4Lybg5m)v^_QlX?
z`Xqq`wq`mYXVEH%Bqmj5M0QtW4hM@^7Z>ON4~vujJR(8`+&^~L@i#!!DZu$X+DdNC
zUS%VgU9>@?C)Bm$T&$dOg^QB!Lpyz?U($uN26d*e#Lbuv9$Ex4p16rv`KEObp=}Yw
zGn+=eE9IjQ{^%YG?in9Jh72
literal 0
HcmV?d00001
diff --git a/frontend/src/assets/logos/wordmark_black_small.png b/frontend/src/assets/logos/wordmark_black_small.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8e8415fe394230eed2a55052a41cafee63c40f4
GIT binary patch
literal 5019
zcmds5_ghojwj~Hk5u^qYkN^h}Pe|xB2_-0Ulz3Gc1%+iTA`*O+6>v47g%dSZey;Nm#P!NkPGWq4KJ
zl!*xdK8~%~P9E>!)50akFMx-sfgV$ShbZGXVJBR*_F!UyNd0~Q#P{0X#{g>=b1P3P
zqwAVzJPzV`2k+zzA>s(fXeK5YQS&&&IeR*Sh&ZgPhb9p&`8PuIIR0IRN`n4|cw*p^
zRz@ZuB;MT_qykZf$V(zPKp+sz{mxxYQ~fLdZa>c8lJ`A537Sx-kB<+;M+t&=zXw&&
z(9nR&D?$|&<&F_@9=@)gjzl?E52=4h{)~Gd?&DV2@2aM;yNmO&@^5~G0_<<(|Em342L}Bu
z{y&EK$LZhIW2*=b81z4%4Z(4}QO25yiFd?MU)P)nSRnbd$yo?CEwC1XKAe?~JlXI_
zu1G)hzB2Hcw38F~s(w+1ld?jQOd?r%^CXMF+lE1myp!$?0q}<>X;*pPy>P}r_jxB}E@=8hn{!_bYS7>p!lvT!vQTIfGo3>ly5l0ls|qvD
zL`L{?9`SVg1Wv#vvcsB9yt676y!uu%8WGHzT|Sk|!hi$U?0KM@#zMMeoRiixa1jp*
zxV#-Uu5ochw%^yMud#>9vRmIp54TVBC(iH>E2(-^r4#r=5ub5YV$$nJq03|4t?1GO
zBKJ#eor(mef;O~pW6dUC9n=QYJH^<%q%d$Q_t({lC1#xU5-00!b#$>=MEwyyw9bI7
zTmUGzKkEe0igU41{oLMHR{Xk+UZB@5JLiE5^?fa|e}f?I?8CQKR{kIcm@G5eNO5kY
zN5F4c3H;h%=Da!OW_~q);Ey!RNPW9MdINuGKjvWPR5iy^jQT>G3cL>Wx4ci5W9GaL
z+HVD7;2r$;u_7_u(l7n<;uC!Av(*Ki;O%1_mUAGsDdHkIZ@sqLkZ=Wd(__G5-PLo^OEc`#uofhh2Nb7iZgoGru0t!${8^2BStTYLX~An=7+MM!qHO8+r9swNc`S^-t~H$Z
z_*LAUspe~)BfUHk*~*7)j%j8yV5cYUxd5UDxWbJ(Ww2M)ZqloFw=*Zq>?}oE@+dV8
z=xXdO`@iey^e09$nf
z;**dm^qdBAyRIhB+-bEk`fU?aE@Q&GzMNaa+CQ_w9Qp&W?AyX$sA`J+b>&?7W8o$Y
zcZ}~X6+FHzJ4sO#GtbB|RlC%xPP)&-EWNee3a*G1e^g4&3OT(}UgwrKvfJr`fSoTx
zm2}}4G-t&xn
zV+`>lttCG8OeO*jL+y-kki7dV?>>7R=fuh2`7mTr0L8MApY|0fmoi>hPjxoe^d4Yt
zXAceb74#Q#>MydNG3bB%7?2%sCuK_YoKQ`E&yJ9?
zSNT#0Ki5via)x2P+xajdva6VViz?iWFqzuA5I({g(5ZbLbXE$u)3ChI$B3-9V>;UQtJ0z5~c1yC_SidVP*Z`*Ts;O7g~(J~>8%mo5sU
ztPu?H0vNf%ovh&Igg(tH`}`5$)H^jHJpwpsMTR?1*kHp*JI6sSyW!l%g&P)kPMobz
zC#t`;#7+Pn28)4VWiN5h3Zxwp1c%;)_Xw=APe=kztwJqu(no?X)2V`!L(0|{%QJ(w{TvbV)FXP
zo_*-0PKelGAUafq{I0%=dU?EHBWFb5w)0<^PW1&LRMVm6YZ{oQhii8m`L7L?E!@UG
za%wi^_Z(56e%$v7=L}zBr}A*~#Yw1wqu$&yPQ!zRgKRUGmT$aDBwc4Swdbwt6fnvs
z-8#eb+NNbz+s!IF(0MzTZ)Alnp^!!eLIfauG5m27n4hJi@cg{T178_&DbX%k`qVsC
z7BDwoR&DJNya*9YVb)4%ruzdptKpfnOhjIz>A&wI{9*6nRq^P
z&PJYc6n##p;koVRyy1_2ymf1FyXbR8JE~@%SMo12kc}_DmH3ALE8?PxaiH%Sm$4mY
zHJ957#`e4KKRPahHYYj;WReqN%ENAAuym?=laQC=HG4JMY4;v9j0!r@#5Z
zAhK%9mb;S>Z<4vC;(#z)0(8`n<*B_PUXuTClWw#*}ZwFI{kU#%~n*W?sq!uXKI@f=c
zX*U>n$9jRwmxls1K!?|Xfz$?wP#qZb(wHB5Y3g<-;0NhLeN7npEmdTQVG>x@&d56~
zMc3vFpm^=%hykWV*8XPEdyLGVspFE@tgG$u
zYih!pnUjlWmrIPCnW{DQZ)Z?Z3g^6
zRm&|z-Rf?T#q!-`nS+wubd#*s3uh%=n|qevjj$9-~aNIZ;R1wn6eO9xSGUfTLqNWGSeKYiEMtb_|0}G?>i=<3~rp<
z3(lg##HblZ)0fG(i{)CSl?{nds70uTlc*E*j
z7ZSf>yf6vw_wUbrSfW7TD_sb2NIUJ?&8!M%?x>mUuQyu6LEF8(4~BiC($>j3$A4)V
z`^skCSvs&Oi5lAY^;SM0SM#{ah6nT3;I(03($xAH!K+14(W@d8qK=6~NPtuodEB=$
zy*YbKD8iooyyZ}iQ!S}5hG+Z6TMW0E;PssCq875*EbQCMETyR0MIV#aYWBkz_E>wq
zAjs*I+*e*tH^eiC_t-xN*{$uHyqfm6Jw)UhXX{!>tnH={9A!ls-_t$rZ8R*w#rHRC
zCWIPI>S9bsPOt`97!xx%(R*pob+eM++;Bl~H0x~!
zJ$`ubu(Wxi(DC&OL)(Z}^vPRuca6n0JuxfVU?;Eg4S)W(4#8@exU&$@Q=#Cp@!8K3
z3zZbDfm`>|mb$emqQI4{;q!F*bTI~j4tz&}7Gm;_{7{Bp{vX^;{B6Nu>+bkbE{C12
TGXN{``^L=hGD<&R&mrtzy2?04
literal 0
HcmV?d00001
diff --git a/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationDrawer.module.scss b/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationDrawer.module.scss
new file mode 100644
index 000000000..1338132c3
--- /dev/null
+++ b/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationDrawer.module.scss
@@ -0,0 +1,3 @@
+.label {
+ font-size: 16px;
+}
diff --git a/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationPDF.js b/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationPDF.js
new file mode 100644
index 000000000..e1733f77e
--- /dev/null
+++ b/frontend/src/components/modals/VisaInvitationDrawer/VisaInvitationPDF.js
@@ -0,0 +1,104 @@
+import React from 'react';
+import { Page, Text, Image, Document, StyleSheet, Font, View } from '@react-pdf/renderer';
+
+Font.register({
+ family: 'Montserrat',
+ src: 'https://fonts.gstatic.com/s/montserrat/v10/zhcz-_WihjSQC0oHJ9TCYC3USBnSvpkopQaUR-2r7iU.ttf',
+ fontWeight: 'bold'
+});
+Font.register({
+ family: 'Lato',
+ src: 'https://fonts.gstatic.com/s/lato/v13/v0SdcGFAl2aezM9Vq_aFTQ.ttf',
+ fontWeight: 'light'
+});
+
+const styles = StyleSheet.create({
+ body: {
+ paddingTop: 35,
+ paddingBottom: 65,
+ paddingHorizontal: 35
+ },
+ topLeftLogo: {
+ width: 200
+ },
+ topRightTitle: {
+ fontSize: 22,
+ marginBottom: 10,
+ fontFamily: 'Montserrat',
+ textAlign: 'right'
+ },
+ topRightItem: {
+ fontSize: 13,
+ fontFamily: 'Lato',
+ textAlign: 'right'
+ },
+ paragraph: {
+ fontSize: 13,
+ fontFamily: 'Lato',
+ marginBottom: 16
+ },
+ signature: {
+ width: 200
+ }
+});
+
+const VisaInvitationPDF = ({
+ hostName = 'Karoliina Pellinen',
+ hostAddress = 'Junction Oy (2823785-1)',
+ hostAddress2 = 'PL1188, 00101 Helsinki',
+ hostPhone = '+358 45 2318287',
+ hostEmail = 'karoliina.pellinen@hackjunction.com',
+ hostTitle = 'Head of Participants',
+ hostCompany = 'Junction Oy',
+ date = '01.01.2019',
+ granteeFirstName = 'Juuso',
+ granteeLastName = 'Lappalainen',
+ granteeNationality = 'Finnish',
+ granteePassportNo = '755245118',
+ profession = 'Employed at VKontakte',
+ arrivalCity = 'Helsinki',
+ arrivalCountry = 'Finland',
+ arrivalDate = '01.01.2019'
+}) => (
+
+
+
+ {hostName}
+ {hostAddress}
+ {hostAddress2}
+ {hostPhone}
+ {hostEmail}
+ {date}
+ Dear Madame/Sir,
+
+ We hereby kindly ask you to issue Visitor Schengen Visa with one entry for {granteeFirstName}{' '}
+ {granteeLastName}, {granteeNationality}, bearer of passport number {granteePassportNo}.
+
+
+ {granteeFirstName}, {profession}, will arrive in {arrivalCity}, {arrivalCountry} on {arrivalDate}.
+
+
+ During their stay in {arrivalCountry}, {granteeFirstName} will be hosted at the Aalto University campus
+ (Väre & School of Business Building), Otaniementie 14, 02150 Espoo, and will be attending the following
+ events organised by Junction:
+
+ 1) Junction 2019, from November 15th to 17th 2019
+
+ 2) Optional Junction 2019 pre-events (networking & conferences), from November 11th to 15th 2019
+
+
+ We also invite {granteeFirstName} to get to know {arrivalCity} at their leisure and attend complementary
+ events such as those organised by Slush during the week after, November 18th to 25th.
+
+
+ Sincerely,
+
+ {hostName}
+
+ {hostTitle}, {hostCompany}
+
+
+
+);
+
+export default VisaInvitationPDF;
diff --git a/frontend/src/components/modals/VisaInvitationDrawer/index.js b/frontend/src/components/modals/VisaInvitationDrawer/index.js
new file mode 100644
index 000000000..0c139e8aa
--- /dev/null
+++ b/frontend/src/components/modals/VisaInvitationDrawer/index.js
@@ -0,0 +1,111 @@
+import React, { useState, useCallback } from 'react';
+import styles from './VisaInvitationDrawer.module.scss';
+
+import { PDFDownloadLink } from '@react-pdf/renderer';
+import { connect } from 'react-redux';
+import moment from 'moment';
+
+import { Drawer, Input, Button as AntButton } from 'antd';
+
+import Divider from 'components/generic/Divider';
+import Button from 'components/generic/Button';
+import { useFormField } from 'hooks/formHooks';
+
+import VisaInvitationPDF from './VisaInvitationPDF';
+import * as DashboardSelectors from 'redux/dashboard/selectors';
+
+const VisaInvitationDrawer = ({ registration }) => {
+ const [visible, setVisible] = useState(false);
+ const firstName = useFormField(registration ? registration.answers.firstName : '');
+ const lastName = useFormField(registration ? registration.answers.lastName : '');
+ const nationality = useFormField(registration ? registration.answers.nationality : '');
+ const passportNo = useFormField('');
+ const profession = useFormField('');
+ const arrivalDate = useFormField('');
+ const arrivalCity = 'Helsinki';
+ const arrivalCountry = 'Finland';
+
+ const [generated, setGenerated] = useState(false);
+
+ const handleClose = useCallback(() => {
+ setVisible(false);
+ }, []);
+
+ return (
+
+
+
+ Just fill in a few more travel details and we'll generate a visa invitation letter for you. We will
+ not save this information for later use - in fact it is never sent anywhere from your device.
+
+
+ Once you've generated the visa invitation letter, double check it to make sure all of the
+ information is correct. You can always generate a new invitation should you need to.
+
+
+ First name
+
+
+ Last name
+
+
+ Nationality
+
+ E.g. "Finnish", "American", "German"
+
+ Passport Number
+
+
+ Profession
+
+ E.g. "Student at Aalto University" / "Employed at BigCorp Inc."
+
+ Arrival date
+
+ The date of your arrival to the country
+
+
+ Generate PDF
+
+
+ {generated && (
+
+ }
+ fileName="visa_invitation_letter.pdf"
+ >
+
+ Download PDF
+
+
+ )}
+
+
+
+ );
+};
+
+const mapState = state => ({
+ registration: DashboardSelectors.registration(state)
+});
+
+export default connect(mapState)(VisaInvitationDrawer);
diff --git a/frontend/src/index.js b/frontend/src/index.js
index b54b76f44..8c673a36e 100755
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -13,6 +13,14 @@ import config from 'constants/config';
const { store, persistor } = configureStore();
+/** Disable log statements in production */
+function noop() {}
+if (process.env.NODE_ENV !== 'development') {
+ console.log = noop;
+ console.warn = noop;
+ console.error = noop;
+}
+
ReactDOM.render(
} persistor={persistor}>
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
index 0309583ba..7ebf41984 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
@@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
-import { Col } from 'antd';
+import { Col, Button as AntButton } from 'antd';
import { RegistrationStatuses } from '@hackjunction/shared';
import NotificationBlock from 'components/generic/NotificationBlock';
@@ -96,30 +96,20 @@ const RegistrationStatusBlock = ({ event, registration }) => {
titleExtra="Confirmed"
body={`Awesome, you've confirmed your participation! You should probably start making travel and other arrangements - see the links below to stay up-to-date on all of the information and announcements related to ${event.name}.`}
bottom={
-
-
-
-
-
- window.alert('Cancel this shit!') }}
- />
-
+
+ To stay in the loop and let your friends know you're coming, you should go attend the{' '}
+ Junction 2019 Facebook event! For any other questions and
+ further event details such as tracks and challenges, see the{' '}
+ event website .
+
+
+ Can't make it after all? Bummer. Please let us know by clicking the button below, so we can
+ accept someone else in your place.
+
+
+ Cancel participation
+
+
}
/>
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
new file mode 100644
index 000000000..c5c86df23
--- /dev/null
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import { connect } from 'react-redux';
+import { Col } from 'antd';
+
+import { RegistrationStatuses } from '@hackjunction/shared';
+import NotificationBlock from 'components/generic/NotificationBlock';
+import Button from 'components/generic/Button';
+import Divider from 'components/generic/Divider';
+
+import * as DashboardSelectors from 'redux/dashboard/selectors';
+
+const STATUSES = RegistrationStatuses.asObject;
+
+const TravelGrantStatusBlock = ({ event, registration }) => {
+ if (!registration || !event) return null;
+ if (registration.answers && !registration.answers.needsTravelGrant) return null;
+
+ if (registration.status === STATUSES.accepted.id) {
+ return (
+
+
+
+
+ );
+ }
+
+ if (registration.status === STATUSES.confirmed.id) {
+ return (
+
+
+
+ Please consult the FAQ section of our
+ website for details on the travel grant amounts available for the country you're travelling
+ from.
+
+ }
+ />
+
+ );
+ }
+ return null;
+};
+
+const mapState = state => ({
+ event: DashboardSelectors.event(state),
+ registration: DashboardSelectors.registration(state)
+});
+
+export default connect(mapState)(TravelGrantStatusBlock);
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/VisaInvitationBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/VisaInvitationBlock.js
new file mode 100644
index 000000000..1b7da8d9a
--- /dev/null
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/VisaInvitationBlock.js
@@ -0,0 +1,48 @@
+import React, { Suspense } from 'react';
+
+import { connect } from 'react-redux';
+import { Col, Icon } from 'antd';
+
+import { RegistrationStatuses } from '@hackjunction/shared';
+import NotificationBlock from 'components/generic/NotificationBlock';
+import Divider from 'components/generic/Divider';
+
+import * as DashboardSelectors from 'redux/dashboard/selectors';
+
+const VisaInvitationDrawer = React.lazy(() => import('components/modals/VisaInvitationDrawer'));
+
+const STATUSES = RegistrationStatuses.asObject;
+
+const VisaInvitationBlock = ({ event, registration }) => {
+ if (!registration || !event) return null;
+ if (registration.answers && !registration.answers.needsTravelGrant) return null;
+
+ const statuses = [STATUSES.accepted.id, STATUSES.confirmed.id];
+
+ if (statuses.indexOf(registration.status) !== -1) {
+ return (
+
+
+ }>
+
+
+ }
+ />
+
+ );
+ }
+
+ return null;
+};
+
+const mapState = state => ({
+ event: DashboardSelectors.event(state),
+ registration: DashboardSelectors.registration(state)
+});
+
+export default connect(mapState)(VisaInvitationBlock);
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
index 3ac25ef75..5fe3fc708 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/index.js
@@ -5,11 +5,15 @@ import { Row } from 'antd';
import RegistrationStatusBlock from './RegistrationStatusBlock';
import TeamStatusBlock from './TeamStatusBlock';
+import TravelGrantStatusBlock from './TravelGrantStatusBlock';
+import VisaInvitationBlock from './VisaInvitationBlock';
const EventDashboardHomeRegistration = () => {
return (
+
+
);
diff --git a/frontend/src/utils/filters.js b/frontend/src/utils/filters.js
index 2bf52a4af..e3fb0f22f 100644
--- a/frontend/src/utils/filters.js
+++ b/frontend/src/utils/filters.js
@@ -32,8 +32,16 @@ const contains = (value, answer) => {
const equals = (value, answer) => {
if (!value || !answer) return false;
+ const trimmed = value.trim().toLowerCase();
if (typeof answer === 'string') {
- return value.toLowerCase().trim() === answer.toLowerCase().trim();
+ return trimmed === answer.toLowerCase().trim();
+ }
+ if (typeof answer === 'boolean') {
+ if (answer) {
+ return trimmed === 'true' || trimmed === 'yes';
+ } else {
+ return trimmed === 'false' || trimmed === 'no';
+ }
}
return answer === value;
From e0e8285af6f9dad178676898ee1a2d8f8578a23c Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 15:55:26 +0300
Subject: [PATCH 16/21] Fix buggy hasRegistration conditions
---
.../EventDetail/EventButtons/index.js | 19 ++++++++++++++-----
.../pages/EventDetail/EventDetail/index.js | 9 +--------
.../pages/EventDetail/EventRegister/index.js | 3 ++-
frontend/src/redux/configureStore.js | 2 +-
frontend/src/redux/eventdetail/selectors.js | 4 ++++
5 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/frontend/src/pages/EventDetail/EventDetail/EventButtons/index.js b/frontend/src/pages/EventDetail/EventDetail/EventButtons/index.js
index 369f65b29..c3b75d82d 100644
--- a/frontend/src/pages/EventDetail/EventDetail/EventButtons/index.js
+++ b/frontend/src/pages/EventDetail/EventDetail/EventButtons/index.js
@@ -9,15 +9,17 @@ import { Events } from '@hackjunction/shared';
import Button from 'components/generic/Button';
import Divider from 'components/generic/Divider';
+import * as EventDetailSelectors from 'redux/eventdetail/selectors';
+import * as AuthSelectors from 'redux/auth/selectors';
const { REGISTRATION_OPEN, REGISTRATION_CLOSED, REGISTRATION_UPCOMING } = Events.status;
-const EventButtons = ({ event, eventStatus, user, registration, match, location, pushLogin }) => {
+const EventButtons = ({ event, eventStatus, user, hasRegistration, match, location, pushLogin }) => {
function renderApplyButton() {
switch (eventStatus) {
case REGISTRATION_OPEN.id: {
if (user) {
- if (registration) {
+ if (hasRegistration) {
return (
{renderApplyButton()};
};
-const mapDispatchToProps = dispatch => ({
+const mapState = state => ({
+ event: EventDetailSelectors.event(state),
+ eventStatus: EventDetailSelectors.eventStatus(state),
+ hasRegistration: EventDetailSelectors.hasRegistration(state),
+ user: AuthSelectors.getCurrentUser(state)
+});
+
+const mapDispatch = dispatch => ({
pushLogin: nextRoute => dispatch(push('/login', { nextRoute }))
});
export default connect(
- null,
- mapDispatchToProps
+ mapState,
+ mapDispatch
)(EventButtons);
diff --git a/frontend/src/pages/EventDetail/EventDetail/index.js b/frontend/src/pages/EventDetail/EventDetail/index.js
index 3d0d70581..da3de9f84 100644
--- a/frontend/src/pages/EventDetail/EventDetail/index.js
+++ b/frontend/src/pages/EventDetail/EventDetail/index.js
@@ -46,14 +46,7 @@ const EventDetail = ({ event, registration, slug, user, match, location, pushLog
-
+
diff --git a/frontend/src/pages/EventDetail/EventRegister/index.js b/frontend/src/pages/EventDetail/EventRegister/index.js
index dc4e33428..c371e8091 100644
--- a/frontend/src/pages/EventDetail/EventRegister/index.js
+++ b/frontend/src/pages/EventDetail/EventRegister/index.js
@@ -33,13 +33,13 @@ const EventRegister = ({
slug,
event,
registration,
+ hasRegistration,
createRegistration,
editRegistration,
userProfile,
idTokenPayload
}) => {
const [submitted, setSubmitted] = useState(false);
- const hasRegistration = registration && registration.hasOwnProperty('_id');
useEffect(() => {
if (!hasRegistration) {
@@ -290,6 +290,7 @@ const EventRegister = ({
const mapStateToProps = state => ({
event: EventDetailSelectors.event(state),
registration: EventDetailSelectors.registration(state),
+ hasRegistration: EventDetailSelectors.hasRegistration(state),
userProfile: UserSelectors.userProfile(state),
idTokenPayload: AuthSelectors.getCurrentUser(state)
});
diff --git a/frontend/src/redux/configureStore.js b/frontend/src/redux/configureStore.js
index 8e03843fd..7f25a600a 100644
--- a/frontend/src/redux/configureStore.js
+++ b/frontend/src/redux/configureStore.js
@@ -13,7 +13,7 @@ import createRootReducer from './rootReducer';
const persistConfig = {
key: 'root',
storage,
- blacklist: ['router', 'organiser', 'dashboard', 'events'],
+ whitelist: ['auth', 'user'],
stateReconciler: autoMergeLevel2
};
diff --git a/frontend/src/redux/eventdetail/selectors.js b/frontend/src/redux/eventdetail/selectors.js
index 2030d44b3..c245f2680 100644
--- a/frontend/src/redux/eventdetail/selectors.js
+++ b/frontend/src/redux/eventdetail/selectors.js
@@ -11,6 +11,10 @@ export const registration = state => state.eventdetail.registration.data;
export const registrationLoading = state => state.eventdetail.registration.loading;
export const registrationError = state => state.eventdetail.registration.error;
export const registrationUpdated = state => state.eventdetail.registration.updated;
+export const hasRegistration = createSelector(
+ registration,
+ registration => registration && registration.hasOwnProperty('_id')
+);
export const eventStatus = createSelector(
event,
From 267560cf37a1bc9875e227379ce246d2d853f380 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 17:14:45 +0300
Subject: [PATCH 17/21] Final acceptance email stuff
---
backend/common/errors/errorHandler.js | 4 +-
backend/common/services/sendgrid.js | 4 +-
backend/modules/email-task/controller.js | 6 +-
backend/modules/email-task/routes.js | 4 ++
backend/modules/registration/model.js | 6 +-
backend/modules/registration/routes.js | 6 +-
frontend/src/constants/filters.js | 8 +++
.../RegistrationStatusBlock.js | 62 +++++++++++++++----
.../TravelGrantStatusBlock.js | 9 ++-
.../EventDashboardHome/index.js | 8 ++-
.../OrganiserEditEventReview/TeamsPage.js | 8 +++
frontend/src/redux/dashboard/actions.js | 36 ++++++++---
frontend/src/services/registrations.js | 18 ++++++
frontend/src/utils/filters.js | 8 +++
14 files changed, 149 insertions(+), 38 deletions(-)
diff --git a/backend/common/errors/errorHandler.js b/backend/common/errors/errorHandler.js
index dc4389077..7c0983983 100644
--- a/backend/common/errors/errorHandler.js
+++ b/backend/common/errors/errorHandler.js
@@ -40,9 +40,7 @@ const errorHandler = (error, request, response, next) => {
}
}
}
- console.log('ERROR', error.message);
-
- // console.error('Unexpected error', error.toJSON());
+ console.log('FOOBAR', 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 bceb1e645..f473e4a18 100644
--- a/backend/common/services/sendgrid.js
+++ b/backend/common/services/sendgrid.js
@@ -34,8 +34,8 @@ const sendgridAddRecipientsToList = (list_id, recipient_ids) => {
};
const SendgridService = {
- sendAcceptanceEmail: (to, event, user) => {
- const msg = SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
+ sendAcceptanceEmail: (event, user) => {
+ const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
event_name: event.name,
first_name: user.firstName,
dashboard_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`,
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index b4d0580d3..d86f9b5af 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -21,7 +21,6 @@ controller.createTask = (userId, eventId, type, message, schedule) => {
}
return task.save().catch(err => {
if (err.code === 11000) {
- //The task already exists, so it's ok
return Promise.resolve();
}
// For other types of errors, we'll want to throw the error normally
@@ -60,7 +59,7 @@ controller.deliverEmailTask = async task => {
]);
switch (task.type) {
case EmailTypes.registrationAccepted: {
- await SendgridService.sendAcceptanceEmail('juuso.lappalainen@hackjunction.com', event, user);
+ await SendgridService.sendAcceptanceEmail(event, user);
break;
}
case EmailTypes.registrationRejected: {
@@ -83,10 +82,7 @@ controller.deliverEmailTask = async task => {
};
controller.sendPreviewEmail = async (to, msgParams) => {
- console.log('SENDING TEST TO', to);
- console.log('WITH PARAMS', msgParams);
return SendgridService.sendGenericEmail(to, msgParams).catch(err => {
- console.log('DA ERR', err);
return;
});
};
diff --git a/backend/modules/email-task/routes.js b/backend/modules/email-task/routes.js
index d7e53165f..ac1c73dc2 100644
--- a/backend/modules/email-task/routes.js
+++ b/backend/modules/email-task/routes.js
@@ -18,4 +18,8 @@ router
.route('/:slug/preview')
.post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);
+router
+ .route('/:slug/send')
+ .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);
+
module.exports = router;
diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js
index aad0e1a40..2ba522208 100644
--- a/backend/modules/registration/model.js
+++ b/backend/modules/registration/model.js
@@ -55,7 +55,9 @@ RegistrationSchema.pre('save', function(next) {
/** Trigger email sending on status changes etc. */
RegistrationSchema.post('save', function(doc, next) {
+ const SOFT_ACCEPTED = RegistrationStatuses.asObject.softAccepted.id;
const ACCEPTED = RegistrationStatuses.asObject.accepted.id;
+ const SOFT_REJECTED = RegistrationStatuses.asObject.softRejected.id;
const REJECTED = RegistrationStatuses.asObject.rejected.id;
/** If a registration was just created, create an email notification about it */
// if (this._wasNew) {
@@ -63,12 +65,12 @@ RegistrationSchema.post('save', function(doc, next) {
// }
/** If a registration is accepted, create an email notification about it */
- if (this._previousStatus !== ACCEPTED && this.status === ACCEPTED) {
+ if (this._previousStatus === SOFT_ACCEPTED && this.status === ACCEPTED) {
EmailTaskController.createAcceptedTask(doc.user, doc.event, true);
}
/** If a registration is rejected, create an email notification about it */
- if (this._previousStatus !== REJECTED && this.status === REJECTED) {
+ if (this._previousStatus === SOFT_REJECTED && this.status === REJECTED) {
EmailTaskController.createRejectedTask(doc.user, doc.event, true);
}
diff --git a/backend/modules/registration/routes.js b/backend/modules/registration/routes.js
index e625ba90e..2fe447920 100644
--- a/backend/modules/registration/routes.js
+++ b/backend/modules/registration/routes.js
@@ -7,7 +7,7 @@ const EventController = require('../event/controller');
const { hasToken } = require('../../common/middleware/token');
const { hasPermission } = require('../../common/middleware/permissions');
-const { canRegisterToEvent, isEventOrganiser } = require('../../common/middleware/events');
+const { canRegisterToEvent, hasRegisteredToEvent, isEventOrganiser } = require('../../common/middleware/events');
const getUserRegistrations = asyncHandler(async (req, res) => {
const registrations = await RegistrationController.getUserRegistrations(req.user);
@@ -108,9 +108,9 @@ router
.post(hasToken, canRegisterToEvent, createRegistration)
.patch(hasToken, canRegisterToEvent, updateRegistration);
-router.route('/:slug/confirm').patch(hasToken, confirmRegistration);
+router.route('/:slug/confirm').patch(hasToken, hasRegisteredToEvent, confirmRegistration);
-router.route('/:slug/cancel').patch(hasToken, cancelRegistration);
+router.route('/:slug/cancel').patch(hasToken, hasRegisteredToEvent, cancelRegistration);
/** Get all registration as organiser */
router.get(
diff --git a/frontend/src/constants/filters.js b/frontend/src/constants/filters.js
index 67f2d08db..fad2ca636 100644
--- a/frontend/src/constants/filters.js
+++ b/frontend/src/constants/filters.js
@@ -35,6 +35,14 @@ const FilterOptions = [
id: 'tags-not-contain',
label: 'Tags do not contain'
},
+ {
+ id: 'apply-as-team',
+ label: 'Applying as team'
+ },
+ {
+ id: 'not-apply-as-team',
+ label: 'Not applying as team'
+ },
{
id: 'field-equals',
label: 'Field equals'
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
index 7ebf41984..28b1d8508 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/RegistrationStatusBlock.js
@@ -1,7 +1,7 @@
-import React from 'react';
+import React, { useCallback, useState } from 'react';
import { connect } from 'react-redux';
-import { Col, Button as AntButton } from 'antd';
+import { Col, Button as AntButton, Popconfirm } from 'antd';
import { RegistrationStatuses } from '@hackjunction/shared';
import NotificationBlock from 'components/generic/NotificationBlock';
@@ -9,10 +9,26 @@ import Button from 'components/generic/Button';
import Divider from 'components/generic/Divider';
import * as DashboardSelectors from 'redux/dashboard/selectors';
+import * as DashboardActions from 'redux/dashboard/actions';
const STATUSES = RegistrationStatuses.asObject;
-const RegistrationStatusBlock = ({ event, registration }) => {
+const RegistrationStatusBlock = ({ event, registration, confirmRegistration, cancelRegistration }) => {
+ const [loading, setLoading] = useState(false);
+ const handleConfirm = useCallback(() => {
+ setLoading(true);
+ confirmRegistration(event.slug).finally(() => {
+ setLoading(false);
+ });
+ }, [event.slug, confirmRegistration]);
+
+ const handleCancel = useCallback(() => {
+ setLoading(true);
+ cancelRegistration(event.slug).finally(() => {
+ setLoading(false);
+ });
+ }, [event.slug, cancelRegistration]);
+
if (!registration || !event) return null;
const PENDING_STATUSES = [STATUSES.pending.id, STATUSES.softAccepted.id, STATUSES.softRejected.id];
@@ -55,7 +71,7 @@ const RegistrationStatusBlock = ({ event, registration }) => {
theme="accent"
text="Confirm participation"
block
- button={{ onClick: () => window.alert('Confirm this stuff!') }}
+ button={{ onClick: handleConfirm, loading }}
/>
}
/>
@@ -98,17 +114,33 @@ const RegistrationStatusBlock = ({ event, registration }) => {
bottom={
To stay in the loop and let your friends know you're coming, you should go attend the{' '}
- Junction 2019 Facebook event! For any other questions and
- further event details such as tracks and challenges, see the{' '}
- event website .
+
+ Junction 2019 Facebook event!
+ {' '}
+ For any other questions and further event details such as tracks and challenges, see the{' '}
+
+ event website
+
+ .
Can't make it after all? Bummer. Please let us know by clicking the button below, so we can
accept someone else in your place.
-
- Cancel participation
-
+
+
+ Cancel participation
+
+
}
/>
@@ -155,4 +187,12 @@ const mapState = state => ({
registration: DashboardSelectors.registration(state)
});
-export default connect(mapState)(RegistrationStatusBlock);
+const mapDispatch = dispatch => ({
+ confirmRegistration: slug => dispatch(DashboardActions.confirmRegistration(slug)),
+ cancelRegistration: slug => dispatch(DashboardActions.cancelRegistration(slug))
+});
+
+export default connect(
+ mapState,
+ mapDispatch
+)(RegistrationStatusBlock);
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
index c5c86df23..2f3e67a62 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/EventDashboardHomeRegistration/TravelGrantStatusBlock.js
@@ -41,9 +41,12 @@ const TravelGrantStatusBlock = ({ event, registration }) => {
body={`Thanks for confirming your participation! We'll let you know about your eligibility for a travel grant as soon as possible!`}
bottom={
- Please consult the FAQ section of our
- website for details on the travel grant amounts available for the country you're travelling
- from.
+ Please consult the{' '}
+
+ FAQ section
+ {' '}
+ of our website for details on the travel grant amounts available for the country you're
+ travelling from.
}
/>
diff --git a/frontend/src/pages/EventDashboard/EventDashboardHome/index.js b/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
index 801aa25a2..bd96b15da 100644
--- a/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
+++ b/frontend/src/pages/EventDashboard/EventDashboardHome/index.js
@@ -15,10 +15,14 @@ const EventDashboardHome = ({ event, registration }) => {
const [currentStep, setCurrentStep] = useState([]);
useEffect(() => {
- const status = EventUtils.getEventStatus(event);
- setCurrentStep(status.index);
+ if (event) {
+ const status = EventUtils.getEventStatus(event);
+ setCurrentStep(status.index);
+ }
}, [event]);
+ if (!event || !registration) return null;
+
function renderContent() {
switch (currentStep) {
case EventConstants.STATUS.Registration.index:
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
index 30a0edc6d..fd92b3f69 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/TeamsPage.js
@@ -71,6 +71,10 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
return true;
});
+ const filteredMemberIds = teamsFiltered.reduce((res, team) => {
+ return res.concat(team.members.map(m => m._id));
+ }, []);
+
return (
@@ -94,6 +98,10 @@ const TeamsPage = ({ event, teams, registrationsLoading, teamsLoading, registrat
{teamsFiltered.length} teams
+
(dispatch, getState) => {
dispatch({
type: ActionTypes.UPDATE_REGISTRATION,
- promise: RegistrationsService.getRegistration(idToken, slug).catch(err => {
- if (err.response.status === 404) {
- return Promise.resolve({});
- }
- return Promise.reject(err);
- }),
+ promise: RegistrationsService.getRegistration(idToken, slug),
meta: {
- onFailure: e => console.log('Error updating dashboard registration', e)
+ onFailure: () => dispatch(push('/'))
}
});
};
+export const confirmRegistration = slug => async (dispatch, getState) => {
+ const idToken = AuthSelectors.getIdToken(getState());
+
+ const registration = await RegistrationsService.confirmRegistration(idToken, slug);
+
+ dispatch({
+ type: ActionTypes.EDIT_REGISTRATION,
+ payload: registration
+ });
+
+ return registration;
+};
+
+export const cancelRegistration = slug => async (dispatch, getState) => {
+ const idToken = AuthSelectors.getIdToken(getState());
+
+ const registration = await RegistrationsService.cancelRegistration(idToken, slug);
+
+ dispatch({
+ type: ActionTypes.EDIT_REGISTRATION,
+ payload: registration
+ });
+
+ return registration;
+};
+
export const editRegistration = (slug, data) => async (dispatch, getState) => {
const idToken = AuthSelectors.getIdToken(getState());
diff --git a/frontend/src/services/registrations.js b/frontend/src/services/registrations.js
index 1e145261a..16297882c 100644
--- a/frontend/src/services/registrations.js
+++ b/frontend/src/services/registrations.js
@@ -33,9 +33,27 @@ RegistrationsService.createRegistration = (idToken, slug, data) => {
return _axios.post(`${BASE_ROUTE}/${slug}`, data, config(idToken));
};
+/** Update a registration for an event as the logged in user
+ * PATCH /:slug
+ */
RegistrationsService.updateRegistration = (idToken, slug, data) => {
return _axios.patch(`${BASE_ROUTE}/${slug}`, data, config(idToken));
};
+
+/** Confirm participation for an event as the logged in user
+ * PATCH /:slug/confirm
+ */
+RegistrationsService.confirmRegistration = (idToken, slug) => {
+ return _axios.patch(`${BASE_ROUTE}/${slug}/confirm`, {}, config(idToken));
+};
+
+/** Cancel participation for an event as the logged in user
+ * PATCH /:slug/cancel
+ */
+RegistrationsService.cancelRegistration = (idToken, slug) => {
+ return _axios.patch(`${BASE_ROUTE}/${slug}/cancel`, {}, config(idToken));
+};
+
/** Get all registrations for event
* GET /:slug/all
*/
diff --git a/frontend/src/utils/filters.js b/frontend/src/utils/filters.js
index e3fb0f22f..5112d4115 100644
--- a/frontend/src/utils/filters.js
+++ b/frontend/src/utils/filters.js
@@ -84,6 +84,14 @@ const filter = (registration, filter) => {
difference(filter.value, registration.tags).length === filter.value.length
);
}
+ case 'apply-as-team': {
+ const applyAsTeam = objectPath.get(registration, 'answers.teamOptions.applyAsTeam');
+ return applyAsTeam === true;
+ }
+ case 'not-apply-as-team': {
+ const applyAsTeam = objectPath.get(registration, 'answers.teamOptions.applyAsTeam');
+ return applyAsTeam !== true;
+ }
case 'field-equals': {
return equals(filter.value, objectPath.get(registration, `answers.${filter.field}`));
}
From 3baa1fea64a5196d3a82e6aace17d877bb6cb7d7 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 17:56:26 +0300
Subject: [PATCH 18/21] Bulk email sending
---
backend/modules/email-task/controller.js | 31 +++++++++++++++----
backend/modules/email-task/model.js | 4 +--
backend/modules/email-task/routes.js | 7 ++++-
.../modals/BulkEmailDrawer/index.js | 22 +++++++++++--
.../SearchAttendeesPage.js | 3 +-
frontend/src/services/email.js | 11 ++++++-
6 files changed, 64 insertions(+), 14 deletions(-)
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index d86f9b5af..cc368d199 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -3,9 +3,11 @@ const SendgridService = require('../../common/services/sendgrid');
const EmailTypes = require('./types');
const EventController = require('../event/controller');
const UserController = require('../user-profile/controller');
+const shortid = require('shortid');
+const Promise = require('bluebird');
const controller = {};
-controller.createTask = (userId, eventId, type, message, schedule) => {
+controller.createTask = (userId, eventId, type, params, schedule) => {
const task = new EmailTask({
user: userId,
event: eventId,
@@ -16,11 +18,12 @@ controller.createTask = (userId, eventId, type, message, schedule) => {
task.schedule = schedule;
}
- if (message) {
- task.message = message;
+ if (params) {
+ task.params = params;
}
return task.save().catch(err => {
if (err.code === 11000) {
+ console.log('ALREADY EXISTS');
return Promise.resolve();
}
// For other types of errors, we'll want to throw the error normally
@@ -52,6 +55,17 @@ controller.createRegisteredTask = async (userId, eventId, deliverNow = false) =>
return task;
};
+controller.createGenericTask = async (userId, eventId, uniqueId, msgParams, deliverNow = false) => {
+ if (!uniqueId) {
+ uniqueId = shortid.generate();
+ }
+ const task = await controller.createTask(userId, eventId, 'generic_' + uniqueId, msgParams);
+ if (deliverNow) {
+ return controller.deliverEmailTask(task);
+ }
+ return task;
+};
+
controller.deliverEmailTask = async task => {
const [user, event] = await Promise.all([
UserController.getUserProfile(task.user),
@@ -63,15 +77,13 @@ controller.deliverEmailTask = async task => {
break;
}
case EmailTypes.registrationRejected: {
- console.log('PERFORM REG REJECTED EMAIL');
break;
}
case EmailTypes.registrationReceived: {
- console.log('PERFORM REG RECEIVED');
break;
}
default: {
- console.log('PERFORM GENERIC EMAIL!');
+ await SendgridService.sendGenericEmail(user.email, task.params);
break;
}
}
@@ -87,4 +99,11 @@ controller.sendPreviewEmail = async (to, msgParams) => {
});
};
+controller.sendBulkEmail = async (recipients, msgParams, event, uniqueId) => {
+ const promises = recipients.map(recipient => {
+ return controller.createGenericTask(recipient, event._id.toString(), uniqueId, msgParams, true);
+ });
+ return Promise.all(promises);
+};
+
module.exports = controller;
diff --git a/backend/modules/email-task/model.js b/backend/modules/email-task/model.js
index 7da3387fe..2dac2e93c 100644
--- a/backend/modules/email-task/model.js
+++ b/backend/modules/email-task/model.js
@@ -1,8 +1,8 @@
const mongoose = require('mongoose');
const EmailTaskSchema = new mongoose.Schema({
- message: {
- type: String,
+ params: {
+ type: mongoose.Schema.Types.Mixed,
default: null
},
schedule: {
diff --git a/backend/modules/email-task/routes.js b/backend/modules/email-task/routes.js
index ac1c73dc2..5a8fd15df 100644
--- a/backend/modules/email-task/routes.js
+++ b/backend/modules/email-task/routes.js
@@ -14,12 +14,17 @@ const sendPreviewEmail = asyncHandler(async (req, res) => {
return res.status(200).json({});
});
+const sendBulkEmail = asyncHandler(async (req, res) => {
+ await EmailTaskController.sendBulkEmail(req.body.recipients, req.body.params, req.event, req.body.uniqueId);
+ return res.status(200).json({});
+});
+
router
.route('/:slug/preview')
.post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);
router
.route('/:slug/send')
- .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);
+ .post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendBulkEmail);
module.exports = router;
diff --git a/frontend/src/components/modals/BulkEmailDrawer/index.js b/frontend/src/components/modals/BulkEmailDrawer/index.js
index ca2492573..99ea0e174 100644
--- a/frontend/src/components/modals/BulkEmailDrawer/index.js
+++ b/frontend/src/components/modals/BulkEmailDrawer/index.js
@@ -14,6 +14,7 @@ const BulkEmailDrawer = ({ registrationIds, buttonProps, user, idToken, event })
const [testEmail, setTestEmail] = useState(user.email);
const [testModalVisible, setTestModalVisible] = useState(false);
const [testModalLoading, setTestModalLoading] = useState(false);
+ const [loading, setLoading] = useState(false);
const [subject, setSubject] = useState();
const [subtitle, setSubtitle] = useState();
@@ -64,8 +65,23 @@ const BulkEmailDrawer = ({ registrationIds, buttonProps, user, idToken, event })
}, [idToken, event.slug, testEmail, params]);
const handleConfirm = useCallback(() => {
- window.alert('Try again later :)');
- }, []);
+ setLoading(true);
+ EmailService.sendBulkEmail(idToken, event.slug, registrationIds, params, messageId)
+ .then(() => {
+ notification.success({
+ message: 'Success'
+ });
+ })
+ .catch(err => {
+ notification.error({
+ message: 'Something went wrong...',
+ description: 'Are you connected to the internet?'
+ });
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [idToken, event.slug, params, registrationIds, messageId]);
return (
@@ -184,7 +200,7 @@ const BulkEmailDrawer = ({ registrationIds, buttonProps, user, idToken, event })
okText="Yes"
cancelText="No"
>
-
+
Send to {registrationIds.length} recipients
diff --git a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
index 838860f58..dfce04240 100644
--- a/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
+++ b/frontend/src/pages/OrganiserDashboard/OrganiserEditEvent/OrganiserEditEventReview/SearchAttendeesPage.js
@@ -18,12 +18,13 @@ const SearchAttendeesPage = ({ registrations, registrationsLoading, 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
-
+
);
diff --git a/frontend/src/services/email.js b/frontend/src/services/email.js
index 5683cf06e..bab14bc60 100644
--- a/frontend/src/services/email.js
+++ b/frontend/src/services/email.js
@@ -17,8 +17,17 @@ EmailService.sendPreviewEmail = (idToken, slug, to, params) => {
to,
params
};
- console.log('SENDING TEST WITH', params);
return _axios.post(`${BASE_ROUTE}/${slug}/preview`, data, config(idToken));
};
+EmailService.sendBulkEmail = (idToken, slug, recipients, params, uniqueId) => {
+ const data = {
+ recipients,
+ params,
+ uniqueId
+ };
+
+ return _axios.post(`${BASE_ROUTE}/${slug}/send`, data, config(idToken));
+};
+
export default EmailService;
From 23c77d9b2a353cdac30759fdd6d15b8b304cd3f7 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 17:56:59 +0300
Subject: [PATCH 19/21] Remove extra params from acceptance email
---
backend/common/services/sendgrid.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js
index f473e4a18..53a59bdf9 100644
--- a/backend/common/services/sendgrid.js
+++ b/backend/common/services/sendgrid.js
@@ -38,10 +38,7 @@ const SendgridService = {
const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
event_name: event.name,
first_name: user.firstName,
- dashboard_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`,
- website_link: 'https://2019.hackjunction.com',
- fb_event_link: 'https://facebook.com',
- contact_email: 'participants@hackjunction.com'
+ dashboard_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`
});
return SendgridService.send(msg);
},
From 613dfb93a38598338c34ee1759cb755a93b50bbc Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Mon, 23 Sep 2019 18:11:11 +0300
Subject: [PATCH 20/21] small fix to email task
---
backend/modules/email-task/controller.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index cc368d199..ad82b0821 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -49,7 +49,7 @@ controller.createRejectedTask = async (userId, eventId, deliverNow = false) => {
controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => {
const task = await controller.createTask(userId, eventId, EmailTypes.registrationReceived);
- if (deliverNow) {
+ if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
@@ -60,7 +60,7 @@ controller.createGenericTask = async (userId, eventId, uniqueId, msgParams, deli
uniqueId = shortid.generate();
}
const task = await controller.createTask(userId, eventId, 'generic_' + uniqueId, msgParams);
- if (deliverNow) {
+ if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
From a37060817d52e39cfe2b4298ec2341b2c147ec80 Mon Sep 17 00:00:00 2001
From: Juuso Lappalainen
Date: Tue, 24 Sep 2019 12:49:31 +0300
Subject: [PATCH 21/21] More email types, cleaning email functionality
---
backend/common/services/sendgrid.js | 28 +++++++++++++++++++-----
backend/modules/email-task/controller.js | 2 ++
backend/modules/registration/model.js | 6 ++---
3 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/backend/common/services/sendgrid.js b/backend/common/services/sendgrid.js
index 53a59bdf9..e3e12d05e 100644
--- a/backend/common/services/sendgrid.js
+++ b/backend/common/services/sendgrid.js
@@ -1,6 +1,7 @@
const sgMail = require('@sendgrid/mail');
const sgClient = require('@sendgrid/client');
const _ = require('lodash');
+const moment = require('moment');
sgMail.setApiKey(global.gConfig.SENDGRID_API_KEY);
sgClient.setApiKey(global.gConfig.SENDGRID_API_KEY);
@@ -42,6 +43,27 @@ const SendgridService = {
});
return SendgridService.send(msg);
},
+ sendRejectionEmail: (event, user) => {
+ return Promise.resolve();
+ },
+ sendRegisteredEmail: (event, user) => {
+ const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
+ header_image: event.logo.url,
+ subject: `Thanks for registering to ${event.name}!`,
+ subtitle: 'Awesome! Now just sit back and relax.',
+ body: `The application period ends ${moment(event.registrationEndTime).format(
+ 'MMMM Do'
+ )} , and we'll process all applications by ${moment(event.registrationEndTime)
+ .add(5, 'days')
+ .format(
+ 'MMMM Do'
+ )} . We'll send you an email once we've made the decision, but in the meantime you can click the link below to access your event dashboard, where you'll be able to see your registration status in real-time. If you're applying as a team, the event dashboard is where you can create and manage your team as well.`,
+ 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,
@@ -54,12 +76,6 @@ const SendgridService = {
return SendgridService.send(msg);
},
-
- buildRejectionEmail: (to, { event_name }) => {
- return SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_REJECTED_TEMPLATE, {
- event_name
- });
- },
buildTemplateMessage: (to, templateId, data) => {
return {
to,
diff --git a/backend/modules/email-task/controller.js b/backend/modules/email-task/controller.js
index ad82b0821..8080c5fe0 100644
--- a/backend/modules/email-task/controller.js
+++ b/backend/modules/email-task/controller.js
@@ -77,9 +77,11 @@ controller.deliverEmailTask = async task => {
break;
}
case EmailTypes.registrationRejected: {
+ await SendgridService.sendRejectionEmail(event, user);
break;
}
case EmailTypes.registrationReceived: {
+ await SendgridService.sendRegisteredEmail(event, user);
break;
}
default: {
diff --git a/backend/modules/registration/model.js b/backend/modules/registration/model.js
index 2ba522208..ffddd9cf0 100644
--- a/backend/modules/registration/model.js
+++ b/backend/modules/registration/model.js
@@ -60,9 +60,9 @@ RegistrationSchema.post('save', function(doc, next) {
const SOFT_REJECTED = RegistrationStatuses.asObject.softRejected.id;
const REJECTED = RegistrationStatuses.asObject.rejected.id;
/** If a registration was just created, create an email notification about it */
- // if (this._wasNew) {
- // EmailTaskController.createRegisteredTask(doc.user, doc.event, true);
- // }
+ if (this._wasNew) {
+ EmailTaskController.createRegisteredTask(doc.user, doc.event, true);
+ }
/** If a registration is accepted, create an email notification about it */
if (this._previousStatus === SOFT_ACCEPTED && this.status === ACCEPTED) {