From b323d07d1212289e745edf5006045342d61c7e91 Mon Sep 17 00:00:00 2001
From: Owen Rees
Date: Wed, 21 Jun 2023 14:11:16 +0200
Subject: [PATCH] mailer implemented
---
TODO | 4 +-
app/api/send-mail/route.ts | 34 ++++++++++++++
app/contactez-nous/page.tsx | 54 +---------------------
components/ContactForm.tsx | 81 +++++++++++++++++++++++++++++++++
cypress/component/scroll.cy.tsx | 4 +-
handlers/formSubmitHandlers.ts | 27 +++++++++++
hooks/useContactForm.ts | 23 ++++++++++
hooks/useHamburgerClose.ts | 2 +
lib/sendMail.ts | 22 +++++++++
package-lock.json | 23 ++++++++++
package.json | 2 +
11 files changed, 220 insertions(+), 56 deletions(-)
create mode 100644 app/api/send-mail/route.ts
create mode 100644 components/ContactForm.tsx
create mode 100644 handlers/formSubmitHandlers.ts
create mode 100644 hooks/useContactForm.ts
create mode 100644 lib/sendMail.ts
diff --git a/TODO b/TODO
index 1c99e3f..06ac2d6 100644
--- a/TODO
+++ b/TODO
@@ -6,14 +6,13 @@ TESTS
-----------------------------------------------------------------
BUGS
-//TODO small screen scroll to top button only works after a refresh. The bug appears after a screen resize - TournamentTable.tsx useEffect
//TODO about page is not centred in Safari
//TODO tournament page load is weird on 2nd click. On safari it pauses for a few seconds. Is it trying to load the entire page before displaying?
+//TODO Nodemailer 'from' reverts to echecsfrance@gmail.com
----------------------------------------------------------------
PAGES
//TODO about page
-//TODO contact page needs working mailer
----------------------------------------------------------------
DESIGN CHANGES
@@ -31,6 +30,7 @@ LOGIC
MISC
----------------------------------------------------------------
+//TODO write logger (fullstack open examples)
//TODO SEO - next headers etc
//TODO multi-language i18n support - https://nextjs.org/docs/app/building-your-application/routing/internationalization
//TODO move smaller ui components into a new folder, and make them reusable - such as using generic prop names
diff --git a/app/api/send-mail/route.ts b/app/api/send-mail/route.ts
new file mode 100644
index 0000000..fe16e80
--- /dev/null
+++ b/app/api/send-mail/route.ts
@@ -0,0 +1,34 @@
+import NodeMailer from "nodemailer";
+import { NextResponse } from "next/server";
+
+export async function POST(req: Request) {
+ const { email, subject, message } = await req.json();
+
+ try {
+ const mailContent = {
+ from: email,
+ to: process.env.GMAIL_USER,
+ subject: subject,
+ text: message,
+ html: `${message}
`,
+ };
+
+ const transporter = NodeMailer.createTransport({
+ service: "gmail",
+ auth: {
+ user: process.env.GMAIL_USER,
+ pass: process.env.GMAIL_PASS,
+ },
+ });
+
+ const info = await transporter.sendMail(mailContent);
+ console.log(info);
+
+ return NextResponse.json(
+ { success: `Message delivered to ${info.accepted}` },
+ { status: 250 }
+ );
+ } catch (error) {
+ return NextResponse.json({ error: `Connection refused` }, { status: 404 });
+ }
+}
diff --git a/app/contactez-nous/page.tsx b/app/contactez-nous/page.tsx
index 3006137..b2e8bde 100644
--- a/app/contactez-nous/page.tsx
+++ b/app/contactez-nous/page.tsx
@@ -1,4 +1,5 @@
import Layout from "@/components/Layout";
+import ContactForm from "@/components/ContactForm";
// TODO fix page sizing
export default function Contact() {
@@ -17,58 +18,7 @@ export default function Contact() {
Vous avez un problème technique? Vous aimeriez participer à ce
projet? Contactez-nous.
-
+
diff --git a/components/ContactForm.tsx b/components/ContactForm.tsx
new file mode 100644
index 0000000..8f3ddbe
--- /dev/null
+++ b/components/ContactForm.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { useState } from "react";
+import { handleEmailSubmit } from "@/handlers/formSubmitHandlers";
+import useContactForm from "@/hooks/useContactForm";
+
+const ContactForm = () => {
+ const { values, handleChange } = useContactForm();
+ const [responseMessage, setResponseMessage] = useState({
+ isSuccessful: false,
+ message: "",
+ });
+
+ console.log(responseMessage);
+
+ return (
+
+ );
+};
+
+export default ContactForm;
diff --git a/cypress/component/scroll.cy.tsx b/cypress/component/scroll.cy.tsx
index f070c18..037a1d0 100644
--- a/cypress/component/scroll.cy.tsx
+++ b/cypress/component/scroll.cy.tsx
@@ -3,13 +3,13 @@ import ScrollToTopButton from "@/components/ScrollToTopButton";
describe("Scroll to top button", () => {
it("desktop scroll button clickable", () => {
cy.viewport("macbook-15");
- cy.mount();
+ cy.mount();
cy.get("[data-cy='scroll-to-top-button']").should("exist").click();
});
it("mobile scroll button clickable", () => {
cy.viewport(600, 600);
- cy.mount();
+ cy.mount();
cy.get("[data-cy='scroll-to-top-button']").should("exist").click();
});
});
diff --git a/handlers/formSubmitHandlers.ts b/handlers/formSubmitHandlers.ts
new file mode 100644
index 0000000..29b9407
--- /dev/null
+++ b/handlers/formSubmitHandlers.ts
@@ -0,0 +1,27 @@
+import sendMail from "@/lib/sendMail";
+import { Dispatch, FormEvent, SetStateAction } from "react";
+
+export const handleEmailSubmit = async (
+ e: FormEvent,
+ values: Record,
+ setResponseMessage: Dispatch<
+ SetStateAction<{ isSuccessful: boolean; message: string }>
+ >
+) => {
+ e.preventDefault();
+ try {
+ const response = await sendMail(values);
+ if (response.status === 250) {
+ setResponseMessage({
+ isSuccessful: true,
+ message: "Thank you for your message.",
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ setResponseMessage({
+ isSuccessful: false,
+ message: "Oops something went wrong. Please try again.",
+ });
+ }
+};
diff --git a/hooks/useContactForm.ts b/hooks/useContactForm.ts
new file mode 100644
index 0000000..54b6128
--- /dev/null
+++ b/hooks/useContactForm.ts
@@ -0,0 +1,23 @@
+import { ChangeEvent, useState } from "react";
+
+const useContactForm = () => {
+ const [values, setValues] = useState({
+ email: "",
+ subject: "",
+ message: "",
+ });
+
+ const handleChange = (
+ e: ChangeEvent
+ ) => {
+ setValues((prevState) => {
+ return {
+ ...prevState,
+ [e.target.id]: e.target.value,
+ };
+ });
+ };
+ return { values, handleChange };
+};
+
+export default useContactForm;
diff --git a/hooks/useHamburgerClose.ts b/hooks/useHamburgerClose.ts
index caed63f..a26e6ae 100644
--- a/hooks/useHamburgerClose.ts
+++ b/hooks/useHamburgerClose.ts
@@ -1,3 +1,5 @@
+// TODO is this really a hook? I think it is more of a util function
+
interface HamburgerClose {
menuVisible: boolean;
setMenuVisible: Dispatch>;
diff --git a/lib/sendMail.ts b/lib/sendMail.ts
new file mode 100644
index 0000000..5735fe8
--- /dev/null
+++ b/lib/sendMail.ts
@@ -0,0 +1,22 @@
+const sendMail = async ({
+ email,
+ subject,
+ message,
+}: Record) => {
+ const data = {
+ email: email,
+ subject: subject,
+ message: message,
+ };
+
+ const response = await fetch("/api/send-mail", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+ return response;
+};
+
+export default sendMail;
diff --git a/package-lock.json b/package-lock.json
index 73b87a4..daf5dfd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"leaflet-defaulticon-compatibility": "^0.1.1",
"mongodb": "^5.5.0",
"next": "^13.4.5",
+ "nodemailer": "^6.9.3",
"postcss": "8.4.23",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -32,10 +33,15 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/leaflet": "^1.9.3",
+ "@types/nodemailer": "^6.4.8",
"cypress": "^12.13.0",
"eslint-plugin-cypress": "^2.13.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0"
+ },
+ "engines": {
+ "node": ">=18.16.0",
+ "npm": "please-use-npm"
}
},
"node_modules/@adobe/css-tools": {
@@ -1810,6 +1816,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz",
"integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw=="
},
+ "node_modules/@types/nodemailer": {
+ "version": "6.4.8",
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.8.tgz",
+ "integrity": "sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/prettier": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
@@ -7413,6 +7428,14 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.11.tgz",
"integrity": "sha512-+M0PwXeU80kRohZ3aT4J/OnR+l9/KD2nVLNNoRgFtnf+umQVFdGBAO2N8+nCnEi0xlh/Wk3zOGC+vNNx+uM79Q=="
},
+ "node_modules/nodemailer": {
+ "version": "6.9.3",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz",
+ "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
diff --git a/package.json b/package.json
index 3e29ac2..96dbc13 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"leaflet-defaulticon-compatibility": "^0.1.1",
"mongodb": "^5.5.0",
"next": "^13.4.5",
+ "nodemailer": "^6.9.3",
"postcss": "8.4.23",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -41,6 +42,7 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/leaflet": "^1.9.3",
+ "@types/nodemailer": "^6.4.8",
"cypress": "^12.13.0",
"eslint-plugin-cypress": "^2.13.3",
"jest": "^29.5.0",