From 3203c8af58437f8805730383f8d6903dd69d581e Mon Sep 17 00:00:00 2001 From: Timothy Armes Date: Tue, 10 Oct 2023 11:25:00 +0200 Subject: [PATCH] Use tRPC for contact form --- app/[locale]/contactez-nous/ContactForm.tsx | 141 +++++++++----------- app/api/send-mail/route.ts | 37 ----- app/server/appRouter.ts | 2 + app/server/procedures/contactUs.ts | 38 ++++++ handlers/formHandlers.ts | 42 ------ hooks/useContactForm.ts | 28 ---- schemas.ts | 6 + 7 files changed, 111 insertions(+), 183 deletions(-) delete mode 100644 app/api/send-mail/route.ts create mode 100644 app/server/procedures/contactUs.ts delete mode 100644 handlers/formHandlers.ts delete mode 100644 hooks/useContactForm.ts diff --git a/app/[locale]/contactez-nous/ContactForm.tsx b/app/[locale]/contactez-nous/ContactForm.tsx index f2386ae..e931caf 100644 --- a/app/[locale]/contactez-nous/ContactForm.tsx +++ b/app/[locale]/contactez-nous/ContactForm.tsx @@ -2,101 +2,90 @@ import { useState } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslations } from "next-intl"; +import { FormProvider, useForm } from "react-hook-form"; +import { z } from "zod"; import InfoMessage from "@/components/InfoMessage"; -import { handleEmailSubmit } from "@/handlers/formHandlers"; -import useContactForm from "@/hooks/useContactForm"; +import { clearMessage } from "@/components/InfoMessage"; +import { TextAreaField } from "@/components/form/TextAreaField"; +import { TextField } from "@/components/form/TextField"; +import { contactUsSchema } from "@/schemas"; +import { trpc } from "@/utils/trpc"; + +type TournamentFormValues = z.infer; const ContactForm = () => { const t = useTranslations("Contact"); - const { values, handleChange, resetForm } = useContactForm(); - const [isSending, setIsSending] = useState(false); + + const contactUs = trpc.contactUs.useMutation(); const [responseMessage, setResponseMessage] = useState({ isSuccessful: false, message: "", }); + const form = useForm({ + resolver: zodResolver(contactUsSchema), + }); + + const onSubmit = async (data: TournamentFormValues) => { + try { + await contactUs.mutateAsync(data); + setResponseMessage({ + isSuccessful: true, + message: t("success"), + }); + + clearMessage(setResponseMessage); + form.reset(); + } catch (err: unknown) { + console.log(err); + setResponseMessage({ + isSuccessful: true, + message: t("failure"), + }); + + clearMessage(setResponseMessage); + } + }; + return ( - <> -
- handleEmailSubmit( - e, - t, - setIsSending, - setResponseMessage, - values, - resetForm, - ) - } - className="space-y-8" - > -
- - -
-
- - -
-
- - -
+ + + + + + + + + - +
); }; diff --git a/app/api/send-mail/route.ts b/app/api/send-mail/route.ts deleted file mode 100644 index ca8822b..0000000 --- a/app/api/send-mail/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NextResponse } from "next/server"; -import NodeMailer from "nodemailer"; - -import { errorLog, infoLog } from "@/utils/logger"; - -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} from ${email}`, - text: message, - html: `

${message}

`, - }; - - const transporter = NodeMailer.createTransport({ - service: "gmail", - auth: { - user: process.env.GMAIL_USER, - pass: process.env.GMAIL_PASS, - }, - }); - - const mailInfo = await transporter.sendMail(mailContent); - infoLog(mailInfo); - - return NextResponse.json( - { success: `Message delivered to ${mailInfo.accepted}` }, - { status: 250 }, - ); - } catch (error) { - errorLog(error); - return NextResponse.json({ error: `Connection refused` }, { status: 404 }); - } -} diff --git a/app/server/appRouter.ts b/app/server/appRouter.ts index 10dd13e..2bbb061 100644 --- a/app/server/appRouter.ts +++ b/app/server/appRouter.ts @@ -1,8 +1,10 @@ import { addTournament } from "./procedures/addTournament"; +import { contactUs } from "./procedures/contactUs"; import { fetchTournamentResults } from "./procedures/fetchTournamentResults"; import { router } from "./trpc"; export const appRouter = router({ + contactUs, addTournament, fetchTournamentResults, }); diff --git a/app/server/procedures/contactUs.ts b/app/server/procedures/contactUs.ts new file mode 100644 index 0000000..0404597 --- /dev/null +++ b/app/server/procedures/contactUs.ts @@ -0,0 +1,38 @@ +import NodeMailer from "nodemailer"; + +import { contactUsSchema } from "@/schemas"; +import { errorLog, infoLog } from "@/utils/logger"; + +import { publicProcedure } from "../trpc"; + +export const contactUs = publicProcedure + .input(contactUsSchema) + .mutation(async ({ input }) => { + try { + const { email, subject, message } = input; + + const mailContent = { + from: email, + to: process.env.GMAIL_USER, + subject: `${subject} from ${email}`, + text: message, + html: `

${message}

`, + }; + + const transporter = NodeMailer.createTransport({ + service: "gmail", + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_PASS, + }, + }); + + const mailInfo = await transporter.sendMail(mailContent); + infoLog(mailInfo); + + return true; + } catch (error) { + errorLog(error); + throw error; + } + }); diff --git a/handlers/formHandlers.ts b/handlers/formHandlers.ts deleted file mode 100644 index f8e4b11..0000000 --- a/handlers/formHandlers.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Dispatch, FormEvent, SetStateAction } from "react"; - -import { useTranslations } from "next-intl"; - -import { clearMessage } from "@/components/InfoMessage"; -import sendMail from "@/lib/sendMail"; -import { ResponseMessage } from "@/types"; -import { errorLog } from "@/utils/logger"; - -export const handleEmailSubmit = async ( - e: FormEvent, - t: ReturnType, - setIsSending: Dispatch>, - setResponseMessage: Dispatch>, - values: Record, - resetForm: () => void, -) => { - e.preventDefault(); - setIsSending(true); - - try { - const response = await sendMail(values); - if (response.status === 250) { - setResponseMessage({ - isSuccessful: true, - message: t("success"), - }); - - resetForm(); - clearMessage(setResponseMessage); - setIsSending(false); - } - } catch (error) { - errorLog(error); - setResponseMessage({ - isSuccessful: false, - message: t("failure"), - }); - clearMessage(setResponseMessage); - setIsSending(false); - } -}; diff --git a/hooks/useContactForm.ts b/hooks/useContactForm.ts deleted file mode 100644 index c6f3842..0000000 --- a/hooks/useContactForm.ts +++ /dev/null @@ -1,28 +0,0 @@ -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, - }; - }); - }; - - const resetForm = () => { - setValues({ email: "", subject: "", message: "" }); - }; - - return { values, handleChange, resetForm }; -}; - -export default useContactForm; diff --git a/schemas.ts b/schemas.ts index bce080b..6ed7fee 100644 --- a/schemas.ts +++ b/schemas.ts @@ -2,6 +2,12 @@ import { z } from "zod"; import { TimeControl } from "@/types"; +export const contactUsSchema = z.object({ + email: z.string().email(), + subject: z.string().min(1, { message: "FormValidation.required" }), + message: z.string().min(1, { message: "FormValidation.required" }), +}); + export const addTournamentSchema = z.object({ name: z.string().min(1, { message: "FormValidation.required" }), email: z.string().email(),