diff options
Diffstat (limited to 'src/app/get-started')
-rw-r--r-- | src/app/get-started/actions.ts | 76 | ||||
-rw-r--r-- | src/app/get-started/page.tsx | 206 |
2 files changed, 282 insertions, 0 deletions
diff --git a/src/app/get-started/actions.ts b/src/app/get-started/actions.ts new file mode 100644 index 0000000..1e5f439 --- /dev/null +++ b/src/app/get-started/actions.ts @@ -0,0 +1,76 @@ +"use server"; + +import { createTransport } from "nodemailer"; + +const noSend = true; // for testing + +if (noSend) { + console.log("Emails are disabled - no emails will be sent"); +} + +export default async function logFormData( + prevState: { message: string; error: boolean }, + formData: FormData +) { + // wait 15 sec + await new Promise<void>((resolve) => setTimeout(resolve, 2000)); + + // Create a test account or replace with real credentials. + const transporter = createTransport({ + host: "mail.mfoi.dev", + port: 465, + secure: true, // true for 465, false for other ports + auth: { + user: "test@mfoi.dev", + pass: "Fakrum-5hapzo-fivkeb", // TODO: put in env variable + }, + }); + + const email_content = ` +<h2>New Consulting Request</h2> +<p><strong>First Name: </strong>${formData.get("firstname")}</p> +<p><strong>Last Name: </strong>${formData.get("lastname")}</p> +<p><strong>Email: </strong>${formData.get("email")}</p>${ + formData.get("phonenumber") && + `<p><strong>Phone Number: </strong>${formData.get("phonenumber")}</p>` + } +<p><strong>Message: </strong><br />${formData.get("message")}</p> +<hr /> +<p><strong>Submitted at:</strong> ${new Date().toLocaleString()}</p> + `; + + const full_name = `${formData.get("firstname")} ${formData.get("lastname")}`; + + if (noSend) { + console.log("Email sending is disabled. Email content:"); + console.log(email_content); + return { + message: + "Successfully submitted your consultation request - email sending is disabled for testing purposes.", + error: false, + }; + } + + try { + const info = await transporter.sendMail({ + from: '"sensiblescholars.com" <test@mfoi.dev>', + to: "test@mfoi.dev", + subject: `New Consultation Request from ${full_name}!`, + html: email_content, + }); + console.log("Message sent:", info.messageId); + } catch (error) { + console.error("Error sending email:", error); // Handle errors + return { + message: + "Failed to send email. This has been reported. Please try again later, and sorry for any inconvenience.", + error: true, + }; + } + + return { + message: + "Successfully submitted your consultation request - expect to hear back soon via email!", + error: false, + }; +} diff --git a/src/app/get-started/page.tsx b/src/app/get-started/page.tsx new file mode 100644 index 0000000..75f04b0 --- /dev/null +++ b/src/app/get-started/page.tsx @@ -0,0 +1,206 @@ +"use client"; + +import Spinner from "@/components/Spinner"; +import { motion } from "motion/react"; +import Form from "next/form"; +import { useActionState } from "react"; +import logFormData from "./actions"; + +// Debug states for testing UI without sending email +const debugSuccess = { + message: + "Sucessfully submitted your consultation request - expect to hear back soon via email!", + error: false, +}; + +const debugError = { + message: + "Failed to send email. This has been reported. Please try again later, and sorry for any inconvenience.", + error: true, +}; + +const initialState = { + message: "", + error: false, +}; + +export default function Home() { + // TODO: look up webdevsimplified video on forms in nextjs + const [state, formAction, pending] = useActionState( + logFormData, + initialState + ); + + return ( + <div className="container mx-auto isolate px-4 py-6 sm:py-12 xl:py-16"> + <div + aria-hidden="true" + className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" + > + <div + style={{ + clipPath: + "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)", + }} + className="relative left-1/2 -z-10 aspect-1155/678 w-144.5 max-w-none -translate-x-1/2 rotate-30 bg-linear-to-br from-indigo-600 to-red-300 opacity-50 sm:left-[calc(50%-40rem)] sm:w-288.75" + /> + </div> + <div className="mx-auto max-w-3xl outline outline-gray-700/20 rounded-xl p-6 shadow-lg shadow-gray-700/10 backdrop-blur-lg sm:p-10 bg-white/30"> + <motion.div + className="mx-auto max-w-2xl text-center" + initial={{ opacity: 0, y: -20 }} + animate={{ opacity: 1, y: 0 }} + transition={{ duration: 0.5 }} + > + <h2 className="text-4xl font-semibold tracking-tight text-balance text-gray-900 sm:text-5xl"> + Schedule a <span className="text-gradient">Free Consultation</span> + </h2> + <p className="mt-6 text-lg text-gray-600"> + We want to have a 20 minute conversation with you to discuss your + needs and talk pricing - completely free and with no obligation. + </p> + </motion.div> + + <Form action={formAction} className="mx-auto mt-8"> + <motion.div + className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2" + initial={{ opacity: 0, y: 20 }} + animate={{ opacity: 1, y: 0 }} + transition={{ duration: 0.5, delay: 0.1 }} + > + <div> + <label + htmlFor="firstname" + className="block text-sm/6 font-semibold text-gray-900" + > + First name + </label> + <div className="mt-2.5"> + <input + id="firstname" + name="firstname" + type="text" + auto-complete="given-name" + className="block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600" + placeholder="First Name" + required + /> + </div> + </div> + <div> + <label + htmlFor="lastname" + className="block text-sm/6 font-semibold text-gray-900" + > + Last Name + </label> + <div className="mt-2.5"> + <input + id="lastname" + name="lastname" + type="text" + auto-complete="familyname" + className="block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600" + placeholder="Last Name" + required + /> + </div> + </div> + <div> + <label + htmlFor="email" + className="block text-sm/6 font-semibold text-gray-900" + > + Email + </label> + <div className="mt-2.5"> + <input + id="email" + name="email" + type="email" + auto-complete="email" + className="block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600" + placeholder="email@domain.com" + required + /> + </div> + </div> + <div> + <label + htmlFor="phonenumber" + className="block text-sm/6 font-semibold text-gray-900" + > + Phone Number (optional) + </label> + <div className="mt-2.5"> + <input + id="phonenumber" + name="phonenumber" + type="tel" + auto-complete="tel" + className="block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600" + placeholder="(216) 555-1234" + /> + </div> + </div> + <div className="sm:col-span-2"> + <label + htmlFor="message" + className="block text-sm/6 font-semibold text-gray-900" + > + Message + </label> + <div className="mt-2.5"> + <textarea + id="message" + name="message" + rows={4} + className="block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600" + placeholder="Please include a brief message about your (or your child's) needs - including subjects, grade levels, and any specific requests." + defaultValue={""} + ></textarea> + </div> + </div> + <div className="sm:col-span-2"> + <motion.button + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.9 }} + transition={{ type: "spring", stiffness: 400, damping: 17 }} + disabled={pending} + type="submit" + className="block w-full + rounded-md button-gradient + px-3.5 py-2.5 text-center text-md font-semibold text-white shadow-xs + hover:shadow-sm + disabled:bg-gray-600 + hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + {pending ? ( + <div className="flex items-center justify-center"> + <Spinner /> <span>Submitting...</span> + </div> + ) : ( + "Submit" + )} + </motion.button> + </div> + <div className="sm:col-span-2" hidden={!state.message}> + <div className="text-center block w-full"> + {state.error ? ( + <p className="font-semibold bg-red-50 rounded-md text-md outline-1 -outline-offset-1 outline-red-700 px-3 py-2 text-red-800"> + <span className="font-bold">Error(!): </span> + {state.message} + </p> + ) : ( + <p className="font-semibold bg-green-50 rounded-md text-md outline-1 -outline-offset-1 outline-green-700 px-3 py-2 text-green-800"> + {state.message} + </p> + )} + </div> + </div> + </motion.div> + </Form> + </div> + </div> + ); +} |