From 3bbd546da2c45684ea13fe8e2bc6d896d17709b8 Mon Sep 17 00:00:00 2001 From: KrutagyaKaneria Date: Thu, 28 May 2026 18:06:08 +0530 Subject: [PATCH 1/6] feat: add contact page and footer link --- src/app/contact/page.tsx | 61 ++++++++ src/components/ContactForm.tsx | 214 +++++++++++++++++++++++++++ src/components/Footer.tsx | 3 + src/components/LanguageBreakdown.tsx | 9 +- src/lib/supabase.ts | 1 - 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/app/contact/page.tsx create mode 100644 src/components/ContactForm.tsx diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 00000000..7ed023bb --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,61 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import ContactForm from "@/components/ContactForm"; + +export const metadata: Metadata = { + title: "Contact | DevTrack", + description: "Send feedback, questions, or support requests to the DevTrack team.", +}; + +export default function ContactPage() { + return ( +
+
+ +
+
+

+ Contact DevTrack +

+

+ Reach out with feedback, questions, or support needs. +

+

+ Use this form for product feedback, bug reports, or anything else you want to share with the DevTrack team. +

+ +
+
+

+ Response focus +

+

+ Messages are reviewed with product and support in mind, so you can keep everything in one place. +

+
+
+

+ Prefer an issue? +

+

+ You can also open an issue on GitHub if the request is better suited to public tracking. +

+ + View GitHub issues + +
+
+
+ +
+ +
+
+
+ ); +} diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx new file mode 100644 index 00000000..3c9ffaef --- /dev/null +++ b/src/components/ContactForm.tsx @@ -0,0 +1,214 @@ +"use client"; + +import { useId, useState } from "react"; + +type FormValues = { + name: string; + email: string; + message: string; +}; + +type FieldErrors = Partial>; + +type SubmissionState = "idle" | "submitting" | "success" | "error"; + +const initialValues: FormValues = { + name: "", + email: "", + message: "", +}; + +function validate(values: FormValues): FieldErrors { + const nextErrors: FieldErrors = {}; + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!values.name.trim()) { + nextErrors.name = "Name is required."; + } + + if (!values.email.trim()) { + nextErrors.email = "Email is required."; + } else if (!emailPattern.test(values.email.trim())) { + nextErrors.email = "Enter a valid email address."; + } + + if (!values.message.trim()) { + nextErrors.message = "Message is required."; + } + + return nextErrors; +} + +export default function ContactForm() { + const nameId = useId(); + const emailId = useId(); + const messageId = useId(); + const statusId = useId(); + + const [values, setValues] = useState(initialValues); + const [errors, setErrors] = useState({}); + const [status, setStatus] = useState("idle"); + const [feedback, setFeedback] = useState(""); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + const nextErrors = validate(values); + if (Object.keys(nextErrors).length > 0) { + setErrors(nextErrors); + setStatus("error"); + setFeedback("Please fix the highlighted fields and try again."); + return; + } + + setErrors({}); + setStatus("submitting"); + setFeedback(""); + + try { + await new Promise((resolve) => window.setTimeout(resolve, 700)); + setStatus("success"); + setFeedback("Thanks. Your message has been received."); + setValues(initialValues); + } catch { + setStatus("error"); + setFeedback("We could not submit your message right now. Please try again."); + } + } + + function handleChange(field: keyof FormValues, value: string) { + setValues((current) => ({ ...current, [field]: value })); + + if (errors[field]) { + setErrors((current) => { + const nextErrors = { ...current }; + delete nextErrors[field]; + return nextErrors; + }); + } + + if (status !== "idle") { + setStatus("idle"); + setFeedback(""); + } + } + + const isSubmitting = status === "submitting"; + const statusTone = status === "error" ? "destructive" : status === "success" ? "success" : ""; + + return ( +
+
+

+ Contact form +

+

+ Send a message +

+

+ Share feedback, ask a question, or report something that needs attention. +

+
+ +
+ {feedback || "All fields are required before sending."} +
+ +
+
+ + handleChange("name", event.target.value)} + aria-invalid={Boolean(errors.name)} + aria-describedby={errors.name ? `${nameId}-error` : undefined} + className="w-full rounded-2xl border border-white/10 bg-[#0f172a] px-4 py-3 text-[#f8fafc] transition-colors placeholder:text-[#64748b] hover:border-[#2563eb]/50 focus:border-[#60a5fa] focus:bg-[#111b2f] focus:outline-none" + placeholder="Your name" + /> + {errors.name ? ( +

+ {errors.name} +

+ ) : null} +
+ +
+ + handleChange("email", event.target.value)} + aria-invalid={Boolean(errors.email)} + aria-describedby={errors.email ? `${emailId}-error` : undefined} + className="w-full rounded-2xl border border-white/10 bg-[#0f172a] px-4 py-3 text-[#f8fafc] transition-colors placeholder:text-[#64748b] hover:border-[#2563eb]/50 focus:border-[#60a5fa] focus:bg-[#111b2f] focus:outline-none" + placeholder="you@example.com" + /> + {errors.email ? ( +

+ {errors.email} +

+ ) : null} +
+ +
+ +