diff --git a/postcss.config.js b/postcss.config.js index c2ddf74..61e3684 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,7 @@ -export default { +const config = { plugins: { "@tailwindcss/postcss": {}, }, }; + +export default config; diff --git a/prettier.config.js b/prettier.config.js index da332bd..b2d59b4 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,4 +1,6 @@ /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ -export default { +const config = { plugins: ["prettier-plugin-tailwindcss"], }; + +export default config; diff --git a/public/IMG_3500.jpeg b/public/IMG_3500.jpeg deleted file mode 100644 index 11aa590..0000000 Binary files a/public/IMG_3500.jpeg and /dev/null differ diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 5593fe8..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/src/app/_components/contact-form.tsx b/src/app/_components/contact-form.tsx index e1ed455..8a9d594 100644 --- a/src/app/_components/contact-form.tsx +++ b/src/app/_components/contact-form.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { z } from "zod"; const empty = { name: "", email: "", company: "", message: "", website: "" }; @@ -18,6 +18,7 @@ const contactSchema = z.object({ }); export function ContactForm() { + const formRef = useRef(null); const [form, setForm] = useState(empty); const [isPending, setIsPending] = useState(false); const [isSuccess, setIsSuccess] = useState(false); @@ -31,6 +32,21 @@ export function ContactForm() { setForm((f) => ({ ...f, [field]: e.target.value })); } + function focusFirstError(errors: typeof fieldErrors) { + const fields: Array = [ + "name", + "email", + "company", + "message", + ]; + const firstInvalid = fields.find((field) => errors[field]); + const control = firstInvalid + ? formRef.current?.elements.namedItem(firstInvalid) + : null; + + if (control instanceof HTMLElement) control.focus(); + } + async function onSubmit(e: React.FormEvent) { e.preventDefault(); setServerError(null); @@ -38,13 +54,15 @@ export function ContactForm() { const parsed = contactSchema.safeParse(form); if (!parsed.success) { const flat = parsed.error.flatten().fieldErrors; - setFieldErrors({ + const errors = { name: flat.name?.[0], email: flat.email?.[0], company: flat.company?.[0], message: flat.message?.[0], website: flat.website?.[0], - }); + }; + setFieldErrors(errors); + focusFirstError(errors); return; } @@ -68,13 +86,15 @@ export function ContactForm() { if (!res.ok || !data.ok) { if ("fieldErrors" in data && data.fieldErrors) { - setFieldErrors({ + const errors = { name: data.fieldErrors.name?.[0], email: data.fieldErrors.email?.[0], company: data.fieldErrors.company?.[0], message: data.fieldErrors.message?.[0], website: data.fieldErrors.website?.[0], - }); + }; + setFieldErrors(errors); + focusFirstError(errors); } setServerError( ("message" in data ? data.message : undefined) ?? @@ -94,7 +114,11 @@ export function ContactForm() { if (isSuccess) { return ( -
+

- Thank you for reaching out. I will get back to you shortly. This - message went straight to Hadlock. No third-party form service handled - it. + Thank you for reaching out. Your message went directly to Hadlock, and + I will get back to you shortly.