contact part design updated
This commit is contained in:
252
components/pages/contact/form.tsx
Normal file
252
components/pages/contact/form.tsx
Normal file
@@ -0,0 +1,252 @@
|
||||
"use client";
|
||||
|
||||
import { Check } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface FormData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
agreeToPolicy: boolean;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
subject?: string;
|
||||
message?: string;
|
||||
agreeToPolicy?: string;
|
||||
}
|
||||
|
||||
export default function Form() {
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitStatus, setSubmitStatus] = useState<
|
||||
"idle" | "success" | "error"
|
||||
>("idle");
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
if (!formData.firstName.trim()) {
|
||||
newErrors.firstName = "First name is required";
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
newErrors.lastName = "Last name is required";
|
||||
}
|
||||
if (!formData.email.trim()) {
|
||||
newErrors.email = "Email is required";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
newErrors.email = "Please enter a valid email";
|
||||
}
|
||||
if (!formData.subject.trim()) {
|
||||
newErrors.subject = "Subject is required";
|
||||
}
|
||||
if (!formData.message.trim()) {
|
||||
newErrors.message = "Message is required";
|
||||
}
|
||||
if (!formData.agreeToPolicy) {
|
||||
newErrors.agreeToPolicy = "You must agree to the privacy policy";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
if (errors[name as keyof FormErrors]) {
|
||||
setErrors((prev) => ({ ...prev, [name]: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
setFormData((prev) => ({ ...prev, agreeToPolicy: !prev.agreeToPolicy }));
|
||||
if (errors.agreeToPolicy) {
|
||||
setErrors((prev) => ({ ...prev, agreeToPolicy: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
setSubmitStatus("idle");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setSubmitStatus("success");
|
||||
setFormData({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
} else {
|
||||
setSubmitStatus("error");
|
||||
}
|
||||
} catch {
|
||||
setSubmitStatus("error");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* First Row - Names */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
placeholder="First Name"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.firstName ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.firstName}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
placeholder="Last Name"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.lastName ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.lastName}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row - Email & Subject */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Your Email Address"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.email ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="subject"
|
||||
placeholder="Subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.subject ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.subject && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.subject}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message Textarea */}
|
||||
<div>
|
||||
<textarea
|
||||
name="message"
|
||||
placeholder="Leave us a message"
|
||||
rows={8}
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
className={`w-full resize-none rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.message ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.message && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Checkbox and Submit */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCheckboxChange}
|
||||
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition ${
|
||||
formData.agreeToPolicy
|
||||
? "border-red-600 bg-red-600"
|
||||
: "border-gray-400 bg-transparent"
|
||||
}`}
|
||||
aria-label="Agree to privacy policy"
|
||||
>
|
||||
{formData.agreeToPolicy && (
|
||||
<Check className="h-3 w-3 text-white" strokeWidth={3} />
|
||||
)}
|
||||
</button>
|
||||
<span className="text-sm text-gray-300">
|
||||
You agree to our friendly privacy policy
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="rounded-full bg-red-600 px-8 py-3 text-sm font-semibold uppercase tracking-wider text-white transition hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? "Sending..." : "Send Message"}
|
||||
</button>
|
||||
</div>
|
||||
{errors.agreeToPolicy && (
|
||||
<p className="text-xs text-red-500">{errors.agreeToPolicy}</p>
|
||||
)}
|
||||
|
||||
{/* Status Messages */}
|
||||
{submitStatus === "success" && (
|
||||
<p className="text-center text-sm text-green-400">
|
||||
Message sent successfully!
|
||||
</p>
|
||||
)}
|
||||
{submitStatus === "error" && (
|
||||
<p className="text-center text-sm text-red-400">
|
||||
Failed to send message. Please try again.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,125 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Mail, MapPin, Phone, Check } from "lucide-react";
|
||||
import ContactHeader from "./contactHeader";
|
||||
|
||||
interface FormData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
agreeToPolicy: boolean;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
subject?: string;
|
||||
message?: string;
|
||||
agreeToPolicy?: string;
|
||||
}
|
||||
import Form from "./form";
|
||||
|
||||
export function Contact() {
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitStatus, setSubmitStatus] = useState<
|
||||
"idle" | "success" | "error"
|
||||
>("idle");
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
if (!formData.firstName.trim()) {
|
||||
newErrors.firstName = "First name is required";
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
newErrors.lastName = "Last name is required";
|
||||
}
|
||||
if (!formData.email.trim()) {
|
||||
newErrors.email = "Email is required";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
newErrors.email = "Please enter a valid email";
|
||||
}
|
||||
if (!formData.subject.trim()) {
|
||||
newErrors.subject = "Subject is required";
|
||||
}
|
||||
if (!formData.message.trim()) {
|
||||
newErrors.message = "Message is required";
|
||||
}
|
||||
if (!formData.agreeToPolicy) {
|
||||
newErrors.agreeToPolicy = "You must agree to the privacy policy";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
if (errors[name as keyof FormErrors]) {
|
||||
setErrors((prev) => ({ ...prev, [name]: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
setFormData((prev) => ({ ...prev, agreeToPolicy: !prev.agreeToPolicy }));
|
||||
if (errors.agreeToPolicy) {
|
||||
setErrors((prev) => ({ ...prev, agreeToPolicy: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
setSubmitStatus("idle");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setSubmitStatus("success");
|
||||
setFormData({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
} else {
|
||||
setSubmitStatus("error");
|
||||
}
|
||||
} catch {
|
||||
setSubmitStatus("error");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const contactInfo = [
|
||||
{
|
||||
icon: Mail,
|
||||
@@ -163,138 +47,7 @@ export function Contact() {
|
||||
<div className="relative z-10 mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<ContactHeader />
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* First Row - Names */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
placeholder="First Name"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.firstName ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.firstName}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
placeholder="Last Name"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.lastName ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.lastName}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row - Email & Subject */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Your Email Address"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.email ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="subject"
|
||||
placeholder="Subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
className={`w-full rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.subject ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.subject && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.subject}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message Textarea */}
|
||||
<div>
|
||||
<textarea
|
||||
name="message"
|
||||
placeholder="Leave us a message"
|
||||
rows={5}
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
className={`w-full resize-none rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
|
||||
errors.message ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.message && (
|
||||
<p className="mt-1 text-xs text-red-500">{errors.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Checkbox and Submit */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCheckboxChange}
|
||||
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition ${
|
||||
formData.agreeToPolicy
|
||||
? "border-red-600 bg-red-600"
|
||||
: "border-gray-400 bg-transparent"
|
||||
}`}
|
||||
aria-label="Agree to privacy policy"
|
||||
>
|
||||
{formData.agreeToPolicy && (
|
||||
<Check className="h-3 w-3 text-white" strokeWidth={3} />
|
||||
)}
|
||||
</button>
|
||||
<span className="text-sm text-gray-300">
|
||||
You agree to our friendly privacy policy
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="rounded-full bg-red-600 px-8 py-3 text-sm font-semibold uppercase tracking-wider text-white transition hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? "Sending..." : "Send Message"}
|
||||
</button>
|
||||
</div>
|
||||
{errors.agreeToPolicy && (
|
||||
<p className="text-xs text-red-500">{errors.agreeToPolicy}</p>
|
||||
)}
|
||||
|
||||
{/* Status Messages */}
|
||||
{submitStatus === "success" && (
|
||||
<p className="text-center text-sm text-green-400">
|
||||
Message sent successfully!
|
||||
</p>
|
||||
)}
|
||||
{submitStatus === "error" && (
|
||||
<p className="text-center text-sm text-red-400">
|
||||
Failed to send message. Please try again.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
<Form />
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="mt-12 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
@@ -303,7 +56,7 @@ export function Contact() {
|
||||
key={info.title}
|
||||
className="flex flex-col items-center text-center"
|
||||
>
|
||||
<div className="mb-3 flex h-14 w-14 items-center justify-center rounded-full border-2 border-red-600">
|
||||
<div className="mb-3 flex h-14 w-14 items-center justify-center rounded-xl bg-[#2c2b2a]">
|
||||
<info.icon className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<h3 className="text-sm font-bold tracking-wider text-white">
|
||||
|
||||
@@ -2,7 +2,7 @@ import DotAnimatsiya from "../../dot/DotAnimatsiya";
|
||||
|
||||
export function Banner() {
|
||||
return (
|
||||
<section className="relative w-full lg:h-[86vh] h-screen min-h-[600px] overflow-hidden pt-20">
|
||||
<section className="relative w-full lg:h-[86vh] h-screen min-h-150 overflow-hidden pt-20">
|
||||
{/* Background Image */}
|
||||
<div
|
||||
className="absolute inset-0 z-0"
|
||||
@@ -23,7 +23,7 @@ export function Banner() {
|
||||
|
||||
{/* Content Container */}
|
||||
<div className="relative z-20 h-full flex items-end lg:mt-0 md:mt-[15vh] sm:mt-[5vh] mt-0">
|
||||
<div className="max-w-[1500px] mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||||
<div className="max-w-375 mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center h-full">
|
||||
{/* Right side - Text Content */}
|
||||
<div className="lg:hidden inline-block space-y-6 text-white">
|
||||
@@ -36,8 +36,10 @@ export function Banner() {
|
||||
</div>
|
||||
|
||||
{/* Main Heading */}
|
||||
<h1 className="bg-linear-to-br from-white via-white to-black
|
||||
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty">
|
||||
<h1
|
||||
className="bg-linear-to-br from-white via-white to-black
|
||||
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty"
|
||||
>
|
||||
THE FIRE GUARDIAN
|
||||
</h1>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user