diff --git a/backend/typescript/package.json b/backend/typescript/package.json index bcaca6d4..b8aa7215 100644 --- a/backend/typescript/package.json +++ b/backend/typescript/package.json @@ -32,6 +32,7 @@ "graphql-middleware": "^6.0.6", "graphql-upload": "^12.0.0", "json2csv": "^5.0.6", + "jspdf": "^2.5.1", "lodash": "^4.17.21", "mongoose": "^6.2.3", "multer": "^1.4.2", diff --git a/backend/typescript/rest/camperRoutes.ts b/backend/typescript/rest/camperRoutes.ts index 52e27aca..7757fab5 100644 --- a/backend/typescript/rest/camperRoutes.ts +++ b/backend/typescript/rest/camperRoutes.ts @@ -14,11 +14,16 @@ import { getErrorMessage } from "../utilities/errorUtils"; import { sendResponseByMimeType } from "../utilities/responseUtil"; import { CamperDTO, CreateCampersDTO, WaitlistedCamperDTO } from "../types"; import { createWaitlistedCamperDtoValidator } from "../middlewares/validators/waitlistedCamperValidators"; +import IEmailService from "../services/interfaces/emailService"; +import EmailService from "../services/implementations/emailService"; +import nodemailerConfig from "../nodemailer.config"; const camperRouter: Router = Router(); const camperService: ICamperService = new CamperService(); +const emailService: IEmailService = new EmailService(nodemailerConfig); + // ROLES: Leaving unprotected as the registration flow probs needs this endpoint to register @dhruv /* Create a camper */ camperRouter.post("/register", createCampersDtoValidator, async (req, res) => { @@ -34,6 +39,16 @@ camperRouter.post("/register", createCampersDtoValidator, async (req, res) => { } }); +camperRouter.post("/email", async (req, res) => { + try { + const waiverContent = req.body; + emailService.sendWaiverEmail(waiverContent); + res.status(201); + } catch (error: unknown) { + res.status(500).json({ error: getErrorMessage(error) }); + } +}); + // ROLES: Admin + CC /* Get all campers, optionally filter by camp ID */ camperRouter.get( diff --git a/backend/typescript/server.ts b/backend/typescript/server.ts index 314f0f9b..2e3db202 100644 --- a/backend/typescript/server.ts +++ b/backend/typescript/server.ts @@ -32,7 +32,7 @@ const swaggerDocument = YAML.load("swagger.yml"); const app = express(); app.use(cookieParser()); app.use(cors(CORS_OPTIONS)); -app.use(express.json()); +app.use(express.json({ limit: "50mb" })); app.use(express.urlencoded({ extended: true })); app.use("/auth", authRouter); diff --git a/backend/typescript/services/implementations/emailService.ts b/backend/typescript/services/implementations/emailService.ts index 3a9dacd8..d3e9484e 100644 --- a/backend/typescript/services/implementations/emailService.ts +++ b/backend/typescript/services/implementations/emailService.ts @@ -1,6 +1,7 @@ import nodemailer, { Transporter } from "nodemailer"; +import { Attachment } from "nodemailer/lib/mailer"; import IEmailService from "../interfaces/emailService"; -import { NodemailerConfig } from "../../types"; +import { EmailDTO, NodemailerConfig } from "../../types"; import { getErrorMessage } from "../../utilities/errorUtils"; import logger from "../../utilities/logger"; import { Camper } from "../../models/camper.model"; @@ -34,6 +35,22 @@ class EmailService implements IEmailService { } } + async sendWaiverEmail(waiverContent: EmailDTO): Promise { + const pdfAttachment: Attachment = { + filename: "waiver.pdf", + content: Buffer.from(waiverContent.pdf), + contentType: "application/pdf", + encoding: "base64", + }; + + await this.sendEmail( + "bowenzhu@uwblueprint.org", + "Focus on Nature Camp Registration - Waiver", + "A copy of your Focus on Nature Camp Registration is attached.", + [pdfAttachment], + ); + } + async sendParentConfirmationEmail( camp: Camp, campers: Camper[], @@ -267,12 +284,14 @@ class EmailService implements IEmailService { to: string, subject: string, htmlBody: string, + attachments?: Attachment[], ): Promise { const mailOptions = { from: this.sender, to, subject, html: htmlBody, + attachments, }; try { diff --git a/backend/typescript/services/interfaces/emailService.ts b/backend/typescript/services/interfaces/emailService.ts index 74e72604..1d2f6be6 100644 --- a/backend/typescript/services/interfaces/emailService.ts +++ b/backend/typescript/services/interfaces/emailService.ts @@ -2,8 +2,10 @@ import { Camp } from "../../models/camp.model"; import { Camper } from "../../models/camper.model"; import { CampSession } from "../../models/campSession.model"; import { WaitlistedCamper } from "../../models/waitlistedCamper.model"; +import { EmailDTO } from "../../types"; interface IEmailService { + sendWaiverEmail(waiverContent: EmailDTO): Promise; /** * Send camp registration confirmation email. * @throws Error if email was not sent successfully diff --git a/backend/typescript/types.ts b/backend/typescript/types.ts index 49bd393a..462ba18a 100644 --- a/backend/typescript/types.ts +++ b/backend/typescript/types.ts @@ -239,6 +239,10 @@ export type NodemailerConfig = { }; }; +export type EmailDTO = { + pdf: string; +}; + export type WaiverDTO = { clauses: { text: string; diff --git a/frontend/src/APIClients/CamperAPIClient.ts b/frontend/src/APIClients/CamperAPIClient.ts index 2470e7b5..7c9a1dbd 100644 --- a/frontend/src/APIClients/CamperAPIClient.ts +++ b/frontend/src/APIClients/CamperAPIClient.ts @@ -1,3 +1,4 @@ +import JsPDF from "jspdf"; import { getBearerToken } from "../constants/AuthConstants"; import { WaitlistedCamper, @@ -92,6 +93,29 @@ const deleteMultipleCampersById = async (ids: string[]): Promise => { } }; +const sendWaiverEmail = async ( + waiverContent: HTMLElement, +): Promise => { + try { + const waiverPdf = new JsPDF("portrait", "pt", "a4"); + await waiverPdf.html(waiverContent, { + margin: 20, + html2canvas: { scale: 0.7 }, + }); + const waiverBuffer = waiverPdf.output(); + + await baseAPIClient.post( + `/campers/email`, + { pdf: waiverBuffer }, + { + headers: { Authorization: getBearerToken() }, + }); + return true; + } catch (error) { + return false; + } +} + const getCampersByChargeIdAndSessionId = async ( chargeId: string, sessionId: string, @@ -114,6 +138,7 @@ export default { updateCamperRegistrationStatus, deleteWaitlistedCamperById, updateCampersById, + sendWaiverEmail, getCampersByChargeIdAndSessionId, deleteMultipleCampersById, }; diff --git a/frontend/src/components/pages/RegistrantExperience/Waiver/index.tsx b/frontend/src/components/pages/RegistrantExperience/Waiver/index.tsx index f7a54736..9fd9aed2 100644 --- a/frontend/src/components/pages/RegistrantExperience/Waiver/index.tsx +++ b/frontend/src/components/pages/RegistrantExperience/Waiver/index.tsx @@ -15,6 +15,7 @@ import { HStack, Wrap, } from "@chakra-ui/react"; +import JsPDF from "jspdf"; import RequiredAsterisk from "../../../common/RequiredAsterisk"; import { OptionalClauseResponse, @@ -23,6 +24,7 @@ import { WaiverInterface, WaiverReducerDispatch, } from "../../../../types/waiverTypes"; +import CamperAPIClient from "../../../../APIClients/CamperAPIClient"; interface WaiverPageProps { waiverInterface: WaiverInterface; @@ -48,8 +50,24 @@ const WaiverPage = ({ // As a note, waiverDispatch is stable (should't change) but we need to pass it into dependency to make linter happy }, [waiverDispatch]); + const sendEmail = async () => { + const waiver = document.getElementById("waiver") as HTMLElement; + await CamperAPIClient.sendWaiverEmail(waiver); + console.log("sent"); + } + const generatePDF = () => { + const report = new JsPDF("portrait", "pt", "a4"); + report + .html(document.getElementById("waiver") as HTMLElement, { margin: 20, + html2canvas: { scale: 0.7 }, + }) + .then(() => { + report.save("report.pdf"); + }); + }; + return ( - + {waiverInterface.campName} Registration @@ -60,9 +78,8 @@ const WaiverPage = ({ In consideration of the participation of my child/children, (the “child or children“), in the Focus on Nature photography camp/workshop - and all activities associated therewith, I, the undersigned - parent/guardian of the child/children, agree to the following terms - and conditions: + and all activities associated therewith, I, the parent/guardian of the + child/children, agree to the following terms and conditions: @@ -202,8 +219,9 @@ const WaiverPage = ({ /> + ); }; -export default WaiverPage; +export default WaiverPage; \ No newline at end of file