connetcted to backend: form request
This commit is contained in:
@@ -7,6 +7,11 @@ import { Mail, Phone, MapPin } from "lucide-react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "react-toastify";
|
||||
import axios from "axios";
|
||||
import httpClient from "@/request/api";
|
||||
import { endPoints } from "@/request/links";
|
||||
|
||||
export function Footer() {
|
||||
const locale = useLocale();
|
||||
@@ -14,12 +19,25 @@ export function Footer() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
|
||||
const handleSubscribe = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (email) {
|
||||
const formRequest = useMutation({
|
||||
mutationKey: [],
|
||||
mutationFn: (data: any) => httpClient.post(endPoints.post.sendNumber, data),
|
||||
onSuccess: () => {
|
||||
toast.success(t("succes"));
|
||||
setSubscribed(true);
|
||||
setEmail("");
|
||||
setTimeout(() => setSubscribed(false), 3000);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log("error: ", error);
|
||||
toast.error(t("error"));
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubscribe = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (email) {
|
||||
formRequest.mutate({ number: email });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,9 +67,11 @@ export function Footer() {
|
||||
className="flex sm:flex-row flex-col w-full gap-2 md:w-auto"
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
type="text"
|
||||
placeholder={t("enterPhone")}
|
||||
value={email}
|
||||
minLength={9}
|
||||
maxLength={13}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="font-almarai flex-1 rounded-full bg-white px-6 py-3 text-gray-800 placeholder-gray-400 focus:outline-none md:w-64"
|
||||
required
|
||||
@@ -179,9 +199,7 @@ export function Footer() {
|
||||
<div className="border-t border-gray-800 px-4 py-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="font-almarai flex flex-col justify-between gap-4 text-sm text-gray-400 md:flex-row md:items-center">
|
||||
<div>
|
||||
Copyright © 2025 Ignum Company.
|
||||
</div>
|
||||
<div>Copyright © 2025 Ignum Company.</div>
|
||||
<div className="flex gap-6">
|
||||
<a href="#terms" className="hover:text-white">
|
||||
Terms & Conditions
|
||||
|
||||
@@ -3,21 +3,26 @@
|
||||
import { Check } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { toast } from "react-toastify";
|
||||
import httpClient from "@/request/api";
|
||||
import { endPoints } from "@/request/links";
|
||||
|
||||
interface FormData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
address: string;
|
||||
theme: string;
|
||||
message: string;
|
||||
agreeToPolicy: boolean;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
subject?: string;
|
||||
name?: string;
|
||||
surname?: string;
|
||||
address?: string;
|
||||
theme?: string;
|
||||
message?: string;
|
||||
agreeToPolicy?: string;
|
||||
}
|
||||
@@ -25,10 +30,10 @@ interface FormErrors {
|
||||
export default function Form() {
|
||||
const t = useTranslations();
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
name: "",
|
||||
surname: "",
|
||||
address: "",
|
||||
theme: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
@@ -38,22 +43,48 @@ export default function Form() {
|
||||
"idle" | "success" | "error"
|
||||
>("idle");
|
||||
|
||||
const formRequest = useMutation({
|
||||
mutationKey: [],
|
||||
mutationFn: (data: FormData) => httpClient.post(endPoints.post.contact, data),
|
||||
onSuccess: () => {
|
||||
setSubmitStatus("success");
|
||||
setFormData({
|
||||
name: "",
|
||||
surname: "",
|
||||
address: "",
|
||||
theme: "",
|
||||
message: "",
|
||||
agreeToPolicy: false,
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
toast.success(t("succes"));
|
||||
setTimeout(() => setSubmitStatus("idle"), 3000);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log("error: ", error);
|
||||
setIsSubmitting(false);
|
||||
setSubmitStatus("error");
|
||||
toast.error(t("error"));
|
||||
setTimeout(() => setSubmitStatus("idle"), 3000);
|
||||
},
|
||||
});
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
if (!formData.firstName.trim()) {
|
||||
newErrors.firstName = "First name is required";
|
||||
if (!formData.name.trim()) {
|
||||
newErrors.name = "First name is required";
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
newErrors.lastName = "Last name is required";
|
||||
if (!formData.surname.trim()) {
|
||||
newErrors.surname = "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.address.trim()) {
|
||||
newErrors.address = "address is required";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.address)) {
|
||||
newErrors.address = "Please enter a valid address";
|
||||
}
|
||||
if (!formData.subject.trim()) {
|
||||
newErrors.subject = "Subject is required";
|
||||
if (!formData.theme.trim()) {
|
||||
newErrors.theme = "theme is required";
|
||||
}
|
||||
if (!formData.message.trim()) {
|
||||
newErrors.message = "Message is required";
|
||||
@@ -90,32 +121,7 @@ export default function Form() {
|
||||
|
||||
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);
|
||||
}
|
||||
formRequest.mutate(formData);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -125,66 +131,74 @@ export default function Form() {
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
name="name"
|
||||
placeholder={t("contact.form.placeholders.firstName")}
|
||||
value={formData.firstName}
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className={`font-almarai 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.name ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">{errors.firstName}</p>
|
||||
{errors.name && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">
|
||||
{errors.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
name="surname"
|
||||
placeholder={t("contact.form.placeholders.lastName")}
|
||||
value={formData.lastName}
|
||||
value={formData.surname}
|
||||
onChange={handleChange}
|
||||
className={`font-almarai 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.surname ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">{errors.lastName}</p>
|
||||
{errors.surname && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">
|
||||
{errors.surname}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row - Email & Subject */}
|
||||
{/* Second Row - address & theme */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
type="address"
|
||||
name="address"
|
||||
placeholder={t("contact.form.placeholders.email")}
|
||||
value={formData.email}
|
||||
value={formData.address}
|
||||
onChange={handleChange}
|
||||
className={`font-almarai 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.address ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">{errors.email}</p>
|
||||
{errors.address && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">
|
||||
{errors.address}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="subject"
|
||||
name="theme"
|
||||
placeholder={t("contact.form.placeholders.subject")}
|
||||
value={formData.subject}
|
||||
value={formData.theme}
|
||||
onChange={handleChange}
|
||||
className={`font-almarai 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.theme ? "border-red-500" : "border-transparent"
|
||||
}`}
|
||||
/>
|
||||
{errors.subject && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">{errors.subject}</p>
|
||||
{errors.theme && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">
|
||||
{errors.theme}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,7 +216,9 @@ export default function Form() {
|
||||
}`}
|
||||
/>
|
||||
{errors.message && (
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">{errors.message}</p>
|
||||
<p className="font-almarai mt-1 text-xs text-red-500">
|
||||
{errors.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -236,7 +252,9 @@ export default function Form() {
|
||||
</button>
|
||||
</div>
|
||||
{errors.agreeToPolicy && (
|
||||
<p className="font-almarai text-xs text-red-500">{errors.agreeToPolicy}</p>
|
||||
<p className="font-almarai text-xs text-red-500">
|
||||
{errors.agreeToPolicy}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Status Messages */}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Image from "next/image";
|
||||
import { Mail, MapPin, Phone, Check } from "lucide-react";
|
||||
import { Mail, MapPin, Phone } from "lucide-react";
|
||||
import ContactHeader from "./contactHeader";
|
||||
import Form from "./form";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -25,7 +25,7 @@ export function Contact() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative min-h-175 w-full py-16 md:py-40">
|
||||
<section className="relative min-h-175 w-full py-30 md:py-40">
|
||||
{/* Background Image */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
|
||||
27
components/provider/index.tsx
Normal file
27
components/provider/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import BackAnimatsiya from "../backAnimatsiya/backAnimatsiya";
|
||||
import { Footer, Navbar } from "../layout";
|
||||
import { PriceModal } from "../priceContact";
|
||||
import { Analytics } from "@vercel/analytics/next";
|
||||
import { ReactNode } from "react";
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BackAnimatsiya />
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
<PriceModal />
|
||||
<Analytics />
|
||||
<ToastContainer/>
|
||||
</QueryClientProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user