diff --git a/controllers/bookings.js b/controllers/bookings.js index 11f7155..73f238d 100644 --- a/controllers/bookings.js +++ b/controllers/bookings.js @@ -1,6 +1,11 @@ -const Booking = require("../models/Booking"); -const validateBookingInput = require("../validation/booking"); -const { bookingConfirmationEmail } = require("../utils/notification"); +const Booking = require('../models/Booking'); +const validateBookingInput = require('../validation/booking'); +const { + bookingConfirmationEmail, + adminSendEmailToClassVolunteers, +} = require('../utils/notification'); +const Class = require('../models/Class'); +const dayjs = require('dayjs'); exports.getBookings = async (req, res) => { try { const bookings = await Booking.find(); @@ -12,7 +17,7 @@ exports.getBookings = async (req, res) => { } catch (err) { return res.status(500).json({ success: false, - error: "Server Error", + error: 'Server Error', }); } }; @@ -24,7 +29,7 @@ exports.getClassBookings = async (req, res) => { if (!booking) { return res.status(404).json({ success: false, - error: "No booking found", + error: 'No booking found', }); } return res.status(200).json({ @@ -35,7 +40,7 @@ exports.getClassBookings = async (req, res) => { } catch (err) { return res.status(400).json({ success: false, - error: "Could not get class bookings", + error: 'Could not get class bookings', }); } }; @@ -55,12 +60,12 @@ exports.createBooking = async (req, res) => { return res.status(400).json({ success: false, message: - "Email already exists, You have already booked for this class, thanks!", + 'Email already exists, You have already booked for this class, thanks!', }); } else if (err) { return res.status(500).json({ success: false, - error: "Server Error", + error: 'Server Error', }); } else { const allBooking = await Booking.find({ classId: req.body.classId }); @@ -73,6 +78,10 @@ exports.createBooking = async (req, res) => { } const newBooking = await Booking.create(req.body); + const classInfo = await Class.findById(req.body.classId); + newBooking.classDate = dayjs(classInfo.date).format('DD/MM/YYYY'); + newBooking.classStartTime = classInfo.startTime; + newBooking.classEndTime = classInfo.endTime; await bookingConfirmationEmail(newBooking); return res.status(201).json({ success: true, @@ -84,7 +93,7 @@ exports.createBooking = async (req, res) => { } catch (err) { return res.status(500).json({ success: false, - error: "Server Error", + error: 'Server Error', }); } }; @@ -98,7 +107,7 @@ exports.deleteBooking = async (req, res) => { if (!booking) { return res.status(404).json({ success: false, - error: "No booking found", + error: 'No booking found', }); } @@ -111,7 +120,7 @@ exports.deleteBooking = async (req, res) => { } catch (err) { return res.status(500).json({ success: false, - error: "Server Error, could not delete booking", + error: 'Server Error, could not delete booking', }); } }; @@ -129,7 +138,7 @@ exports.updateBooking = async (req, res) => { if (!booking) { return res.status(400).json({ success: false, - error: "booking not found!", + error: 'booking not found!', }); } return res.status(200).json({ @@ -139,7 +148,38 @@ exports.updateBooking = async (req, res) => { } catch (err) { return res.status(400).json({ success: false, - error: "Could not update booking", + error: 'Could not update booking', }); } }; + +exports.sendEmailToAllVolunteers = async (req, res) => { + try { + const emailData = req.body; + if (emailData.subject === '' || emailData.emailText === '') { + return res.status(400).json("'Subject or email can not be empty.'"); + } + const classId = emailData.classId; + const classBookings = await Booking.find({ classId }); + if (classBookings && !classBookings.length > 0) { + return res.status(400).json({ + success: false, + message: + 'Sorry, there are no volunteers signed up for this class, thanks!', + }); + } + const emails = []; + classBookings.forEach((booking) => emails.push(booking.email)); + emailData.emails = emails; + await adminSendEmailToClassVolunteers(emailData); + return res.status(200).json({ + success: true, + message: 'An email notification is sent to all volunteers, thanks!', + }); + } catch (err) { + return res.status(400).json({ + success: false, + error: 'Could not Send Emails', + }); + } +}; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 202c8df..a473a01 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1761,6 +1761,14 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, + "@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "requires": { + "parchment": "^1.1.2" + } + }, "@types/react": { "version": "16.9.44", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.44.tgz", @@ -3680,6 +3688,11 @@ } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "clone-deep": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", @@ -4032,6 +4045,15 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -5807,6 +5829,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==" + }, "fast-glob": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", @@ -9547,6 +9574,11 @@ "tslib": "^1.10.0" } }, + "parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10892,6 +10924,36 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "quill": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", + "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", + "requires": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.2", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + }, + "dependencies": { + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=" + } + } + }, + "quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "requires": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + } + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -11175,6 +11237,11 @@ "scheduler": "^0.19.1" } }, + "react-dom-factories": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-dom-factories/-/react-dom-factories-1.0.2.tgz", + "integrity": "sha1-63cFxNs2+1AbOqOP91lhaqD/luA=" + }, "react-error-overlay": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", @@ -11190,6 +11257,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-quill": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/react-quill/-/react-quill-1.3.5.tgz", + "integrity": "sha512-/W/rNCW+6QpGz8yQ9tFK5Ka/h/No1RqrcOOvCIOR092OiKzRFlU2xbPEwiP3Wgy/Dx13pi1YhjReDMX/5uotJg==", + "requires": { + "@types/quill": "1.3.10", + "create-react-class": "^15.6.0", + "lodash": "^4.17.4", + "prop-types": "^15.5.10", + "quill": "^1.3.7", + "react-dom-factories": "^1.0.0" + } + }, "react-redux": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 34e5f83..36fd74f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-hook-form": "^6.10.1", + "react-quill": "^1.3.5", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-scripts": "^3.4.4", diff --git a/frontend/src/components/ClassCard/ClassCard.jsx b/frontend/src/components/ClassCard/ClassCard.jsx index ee85860..12cb60c 100644 --- a/frontend/src/components/ClassCard/ClassCard.jsx +++ b/frontend/src/components/ClassCard/ClassCard.jsx @@ -3,6 +3,7 @@ import { MonthNames } from "../../utils/MonthNames"; import { Link } from "react-router-dom"; import NewBookingForm from "../NewBookingForm/NewBookingForm.jsx"; import ClassVolunteersList from "../ClassVolunteersList/ClassVolunteersList.jsx"; +import EmailToAll from "./EmailToAll.jsx"; import axios from "axios"; import Alert from "../Alert/Alarm.jsx"; import Loading from "../Loading/Loading.jsx"; @@ -17,11 +18,16 @@ const ClassCard = ({ user, city, component, id, Class, WeekNumber }) => { const [alertStatus, setAlertStatus] = useState(false); const [alertType, setAlertType] = useState(""); const [alertMessage, setAlertMessage] = useState("message"); + const [sendEmail, setSendEmail] = useState(false); const closeConfirmationAlert = () => { setCancelStatus(false); }; + const emailClose = () => { + setSendEmail(false); + }; + const showAlert = (type, message) => { setAlertStatus(true); setAlertType(type); @@ -180,6 +186,19 @@ const ClassCard = ({ user, city, component, id, Class, WeekNumber }) => {
| + + | +
+
+ |
+ + + | ++ + | +
+
+
+
+ + ${data} + + ++ Please feel free to contact the coordinator,if there is + anything you wish to discuss before turning up for a class. + ++ Thank you and have a great time during the class. + ++ + Best Regards, + ++ The CodeYourFuture Team + +
|
+ + + | + +
| + + | +
+
+ |
+ + + | +
- This is a notification that you have signed up for a class as a ${data.roleName}. + This is a notification that you have signed up for a class as a ${data.roleName} on ${data.classDate} between ${data.classStartTime} and ${data.classEndTime} , + add to calendar + + add to calendar +
{