diff --git a/jalwiki_ui/app/about/page.tsx b/jalwiki_ui/app/about/page.tsx index 0530745..593563e 100644 --- a/jalwiki_ui/app/about/page.tsx +++ b/jalwiki_ui/app/about/page.tsx @@ -7,6 +7,7 @@ import Link from "next/link" import { Button } from "@/components/ui/button" import { useTheme } from "@/context/theme-context" // For theme toggling import { cn } from "@/lib/utils" // For conditional class names +import ContactUSForm from "../../components/ContactUsForm.tsx" export default function AboutPage() { const { darkMode } = useTheme(); // Get current theme state @@ -301,6 +302,7 @@ export default function AboutPage() { + ) } \ No newline at end of file diff --git a/jalwiki_ui/components/ContactUsForm.tsx b/jalwiki_ui/components/ContactUsForm.tsx new file mode 100644 index 0000000..fd1bf7c --- /dev/null +++ b/jalwiki_ui/components/ContactUsForm.tsx @@ -0,0 +1,360 @@ +"use client" + +import React, { useState } from "react" +import { useTheme } from "@/context/theme-context" +import { cn } from "@/lib/utils" + +export default function ContactUsForm() { + const { darkMode } = useTheme() + + type FormState = { + name: string + email: string + phone?: string + subject: string + message: string + company?: string + } + + const [form, setForm] = useState({ + name: "", + email: "", + phone: "", + subject: "", + message: "", + company: "", + }) + + const [errors, setErrors] = useState>>({}) + const [submitting, setSubmitting] = useState(false) + const [status, setStatus] = useState<"idle" | "success" | "error">("idle") + + function validate(values: FormState) { + const newErrors: Partial> = {} + + if (!values.name.trim()) newErrors.name = "Please enter your name." + + if (!values.email.trim()) { + newErrors.email = "Please enter your email." + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(values.email)) { + newErrors.email = "Enter a valid email address." + } + + if (!values.subject.trim()) newErrors.subject = "Please add a subject." + + if (!values.message.trim() || values.message.trim().length < 10) { + newErrors.message = "Message should be at least 10 characters." + } + + if (values.phone && values.phone.trim() && !/^[+\d\s()\-]{7,20}$/.test(values.phone)) { + newErrors.phone = "Enter a valid phone number (optional)." + } + + if (values.company && values.company.trim().length > 0) { + newErrors.company = "" + } + + return newErrors + } + + async function onSubmit(e: React.FormEvent) { + e.preventDefault() + setStatus("idle") + + const v = validate(form) + setErrors(v) + if (Object.keys(v).length > 0) return + + setSubmitting(true) + + try { + await new Promise((res) => setTimeout(res, 900)) + setStatus("success") + setForm({ name: "", email: "", phone: "", subject: "", message: "", company: "" }) + setErrors({}) + } catch (err) { + console.error(err) + setStatus("error") + } finally { + setSubmitting(false) + } + } + + function handleChange(key: T, value: FormState[T]) { + setForm((prev) => ({ ...prev, [key]: value })) + if (errors[key]) setErrors((e) => ({ ...e, [key]: undefined })) + } + + // Theme-based styles + const containerBg = darkMode + ? "bg-gradient-to-br from-gray-900 to-gray-800" + : "bg-gradient-to-br from-slate-50 to-slate-100" + + const cardBg = darkMode + ? "bg-gray-800/90 backdrop-blur ring-gray-700" + : "bg-white/90 backdrop-blur ring-slate-200" + + const textColor = darkMode ? "text-gray-100" : "text-slate-800" + const mutedTextColor = darkMode ? "text-gray-300" : "text-slate-600" + const labelColor = darkMode ? "text-gray-200" : "text-slate-700" + const placeholderColor = darkMode ? "placeholder-gray-500" : "placeholder-slate-400" + + const inputBg = darkMode ? "bg-gray-700" : "bg-white" + const inputBorder = darkMode ? "border-gray-600" : "border-slate-300" + const inputFocusBorder = darkMode ? "focus:border-purple-400" : "focus:border-slate-400" + const inputFocusRing = darkMode + ? "focus:ring-purple-500/30" + : "focus:ring-slate-200" + + const successBg = darkMode + ? "border-green-800 bg-green-900/50 text-green-200" + : "border-green-200 bg-green-50 text-green-800" + + const errorBg = darkMode + ? "border-red-800 bg-red-900/50 text-red-200" + : "border-red-200 bg-red-50 text-red-800" + + const buttonBg = darkMode + ? "bg-purple-600 hover:bg-purple-700 focus:ring-purple-500/30" + : "bg-slate-900 hover:bg-slate-800 focus:ring-slate-200" + + const linkColor = darkMode + ? "text-purple-300 underline decoration-purple-500 hover:decoration-purple-300" + : "text-slate-900 underline decoration-slate-300 hover:decoration-slate-800" + + return ( +
+
+
+

+ Contact Us +

+
+ +
+ {/* Success / Error banners */} + {status === "success" && ( +
+ ✅ Your message has been sent. We'll get back to you soon! +
+ )} + {status === "error" && ( +
+ ❌ Something went wrong. Please try again. +
+ )} + +
+
+ {/* Name */} +
+ + handleChange("name", e.target.value)} + aria-invalid={!!errors.name} + aria-describedby={errors.name ? "name-error" : undefined} + className={cn( + "mt-1 w-full rounded-xl px-3 py-2 shadow-sm outline-none transition focus:ring-4", + inputBg, + inputBorder, + inputFocusBorder, + inputFocusRing, + placeholderColor, + darkMode ? "text-gray-100" : "text-slate-900" + )} + placeholder="Your Name" + required + /> + {errors.name && ( +

{errors.name}

+ )} +
+ + {/* Email */} +
+ + handleChange("email", e.target.value)} + aria-invalid={!!errors.email} + aria-describedby={errors.email ? "email-error" : undefined} + className={cn( + "mt-1 w-full rounded-xl px-3 py-2 shadow-sm outline-none transition focus:ring-4", + inputBg, + inputBorder, + inputFocusBorder, + inputFocusRing, + placeholderColor, + darkMode ? "text-gray-100" : "text-slate-900" + )} + placeholder="jane@example.com" + required + /> + {errors.email && ( +

{errors.email}

+ )} +
+
+ + {/* Phone (optional) */} +
+
+ + handleChange("phone", e.target.value)} + aria-invalid={!!errors.phone} + aria-describedby={errors.phone ? "phone-error" : undefined} + className={cn( + "mt-1 w-full rounded-xl px-3 py-2 shadow-sm outline-none transition focus:ring-4", + inputBg, + inputBorder, + inputFocusBorder, + inputFocusRing, + placeholderColor, + darkMode ? "text-gray-100" : "text-slate-900" + )} + placeholder="+91 98765 43210" + /> + {errors.phone && ( +

{errors.phone}

+ )} +
+ + {/* Subject */} +
+ + handleChange("subject", e.target.value)} + aria-invalid={!!errors.subject} + aria-describedby={errors.subject ? "subject-error" : undefined} + className={cn( + "mt-1 w-full rounded-xl px-3 py-2 shadow-sm outline-none transition focus:ring-4", + inputBg, + inputBorder, + inputFocusBorder, + inputFocusRing, + placeholderColor, + darkMode ? "text-gray-100" : "text-slate-900" + )} + placeholder="How can we help?" + required + /> + {errors.subject && ( +

{errors.subject}

+ )} +
+
+ + {/* Message */} +
+ +