connetcted to backend: form request

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-02-05 11:02:57 +05:00
parent ca3e28779e
commit 3c862ea104
13 changed files with 458 additions and 90 deletions

View File

@@ -10,6 +10,9 @@ import BackAnimatsiya from "@/components/backAnimatsiya/backAnimatsiya";
import { PriceModal } from "@/components/priceContact";
import PageTransition from "@/components/pageTransition/pageTransition";
import { InitialLoading } from "@/components/initialLoading/initialLoading";
import { Providers } from "@/components/provider";
("info@ignum-tech.com");
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -61,12 +64,7 @@ export default async function RootLayout({
>
<InitialLoading />
<NextIntlClientProvider messages={messages} locale={locale}>
<BackAnimatsiya />
<Navbar />
{children}
<Footer />
<PriceModal />
<Analytics />
<Providers>{children}</Providers>
</NextIntlClientProvider>
</body>
</html>

View File

@@ -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

View File

@@ -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 */}

View File

@@ -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

View 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>
);
}

View File

@@ -228,7 +228,9 @@
"contactTitle": "Send us your phone number",
"contactSubTitle": "Our staff will contact you",
"enterPhone": "Enter your phone number",
"send": "Send",
"send": "Sent",
"error":"Error!",
"succes":"sent!",
"priceModal": {
"title": "Get Price",
"product": {

View File

@@ -229,6 +229,8 @@
"contactSubTitle": "Наши сотрудники свяжутся с вами",
"enterPhone": "Введите ваш номер телефона",
"send": "Отправить",
"error": "Ошибка!",
"succes": "Отправлено!",
"priceModal": {
"title": "Узнать цену",
"product": {

View File

@@ -229,7 +229,8 @@
"contactSubTitle": "Xodimlarimiz siz bilan bog'lanishadi",
"enterPhone": "Telefon raqamingiz kiriting",
"send": "Yuborish",
"error":"Xatolik!",
"succes":"Yuborildi!",
"priceModal": {
"title": "Narxni bilish",
"product": {

View File

@@ -38,8 +38,10 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
"@tanstack/react-query": "^5.90.20",
"@vercel/analytics": "1.3.1",
"autoprefixer": "^10.4.20",
"axios": "^1.13.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
@@ -58,6 +60,7 @@
"react-fast-marquee": "^1.6.5",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"react-toastify": "^11.0.5",
"recharts": "2.15.4",
"sonner": "^1.7.4",
"swiper": "^12.0.3",

223
pnpm-lock.yaml generated
View File

@@ -95,12 +95,18 @@ importers:
'@radix-ui/react-tooltip':
specifier: 1.1.6
version: 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@tanstack/react-query':
specifier: ^5.90.20
version: 5.90.20(react@19.2.0)
'@vercel/analytics':
specifier: 1.3.1
version: 1.3.1(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)
autoprefixer:
specifier: ^10.4.20
version: 10.4.23(postcss@8.5.6)
axios:
specifier: ^1.13.4
version: 1.13.4
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -155,6 +161,9 @@ importers:
react-resizable-panels:
specifier: ^2.1.7
version: 2.1.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-toastify:
specifier: ^11.0.5
version: 11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
recharts:
specifier: 2.15.4
version: 2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -1379,6 +1388,14 @@ packages:
'@tailwindcss/postcss@4.1.18':
resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
'@tanstack/query-core@5.90.20':
resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==}
'@tanstack/react-query@5.90.20':
resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==}
peerDependencies:
react: ^18 || ^19
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@@ -1435,6 +1452,9 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
autoprefixer@10.4.23:
resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
engines: {node: ^10 || ^12 || >=14}
@@ -1442,6 +1462,9 @@ packages:
peerDependencies:
postcss: ^8.1.0
axios@1.13.4:
resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==}
baseline-browser-mapping@2.9.17:
resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==}
hasBin: true
@@ -1451,6 +1474,10 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
caniuse-lite@1.0.30001765:
resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==}
@@ -1470,6 +1497,10 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -1529,6 +1560,10 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -1539,6 +1574,10 @@ packages:
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
electron-to-chromium@1.5.267:
resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
@@ -1559,6 +1598,22 @@ packages:
resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
engines: {node: '>=10.13.0'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -1570,6 +1625,19 @@ packages:
resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==}
engines: {node: '>=6.0.0'}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
form-data@4.0.5:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
@@ -1587,13 +1655,40 @@ packages:
react-dom:
optional: true
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
input-otp@1.4.1:
resolution: {integrity: sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==}
peerDependencies:
@@ -1707,6 +1802,18 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
motion-dom@12.29.2:
resolution: {integrity: sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==}
@@ -1796,6 +1903,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
react-day-picker@9.8.0:
resolution: {integrity: sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ==}
engines: {node: '>=18'}
@@ -1867,6 +1977,12 @@ packages:
'@types/react':
optional: true
react-toastify@11.0.5:
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
peerDependencies:
react: ^18 || ^19
react-dom: ^18 || ^19
react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
@@ -3121,6 +3237,13 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.18
'@tanstack/query-core@5.90.20': {}
'@tanstack/react-query@5.90.20(react@19.2.0)':
dependencies:
'@tanstack/query-core': 5.90.20
react: 19.2.0
'@types/d3-array@3.2.2': {}
'@types/d3-color@3.1.3': {}
@@ -3170,6 +3293,8 @@ snapshots:
dependencies:
tslib: 2.8.1
asynckit@0.4.0: {}
autoprefixer@10.4.23(postcss@8.5.6):
dependencies:
browserslist: 4.28.1
@@ -3179,6 +3304,14 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
axios@1.13.4:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
baseline-browser-mapping@2.9.17: {}
browserslist@4.28.1:
@@ -3189,6 +3322,11 @@ snapshots:
node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1)
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
caniuse-lite@1.0.30001765: {}
class-variance-authority@0.7.1:
@@ -3211,6 +3349,10 @@ snapshots:
- '@types/react'
- '@types/react-dom'
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
csstype@3.2.3: {}
d3-array@3.2.4:
@@ -3259,6 +3401,8 @@ snapshots:
decimal.js@10.6.0: {}
delayed-stream@1.0.0: {}
detect-libc@2.1.2: {}
detect-node-es@1.1.0: {}
@@ -3268,6 +3412,12 @@ snapshots:
'@babel/runtime': 7.28.6
csstype: 3.2.3
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
electron-to-chromium@1.5.267: {}
embla-carousel-react@8.5.1(react@19.2.0):
@@ -3287,12 +3437,37 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.0
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
escalade@3.2.0: {}
eventemitter3@4.0.7: {}
fast-equals@5.4.0: {}
follow-redirects@1.15.11: {}
form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
mime-types: 2.1.35
fraction.js@5.3.4: {}
framer-motion@12.29.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
@@ -3304,10 +3479,42 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
function-bind@1.1.2: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-nonce@1.0.1: {}
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
input-otp@1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
react: 19.2.0
@@ -3395,6 +3602,14 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
math-intrinsics@1.1.0: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
motion-dom@12.29.2:
dependencies:
motion-utils: 12.29.2
@@ -3483,6 +3698,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
proxy-from-env@1.1.0: {}
react-day-picker@9.8.0(react@19.2.0):
dependencies:
'@date-fns/tz': 1.2.0
@@ -3548,6 +3765,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.9
react-toastify@11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
clsx: 2.1.1
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@babel/runtime': 7.28.6

35
request/api.ts Normal file
View File

@@ -0,0 +1,35 @@
import axios, { InternalAxiosRequestConfig } from "axios";
import { getRouteLang } from "./getLang";
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
const httpClient = axios.create({
baseURL: baseUrl,
headers: {
'Content-Type': 'application/json',
}
});
// Request interceptor
httpClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const language = getRouteLang();
config.headers["Accept-Language"] = language;
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor (xatoliklarni boshqarish uchun)
httpClient.interceptors.response.use(
(response) => response,
(error) => {
// Xatolikni formatlash
const message = error.response?.data?.message || error.message;
return Promise.reject(message);
}
);
export default httpClient;

18
request/getLang.ts Normal file
View File

@@ -0,0 +1,18 @@
// utils/getRouteLang.ts
import { locales, defaultLocale } from "@/i18n/config";
export function getRouteLang(): string {
if (typeof window === "undefined") return defaultLocale;
// 1)Fall back to the first non-empty pathname segment
const rawSegments = window.location.pathname.split("/"); // e.g. ['', 'uz', 'path', ...]
const segments = rawSegments.filter(Boolean); // removes empty strings
if (segments.length > 0) {
const candidate = segments[0]; // first segment after root
if (locales.includes(candidate as (typeof locales)[number]))
return candidate;
}
// 2) final fallback
return defaultLocale;
}

23
request/links.ts Normal file
View File

@@ -0,0 +1,23 @@
export const endPoints = {
category: {
all: "category/",
},
subCategory: {
byId: (id: number) => `subCategory/?category=${id}`,
},
product: {
byCategory: (categoryId: number) => `product/?category=${categoryId}`,
bySubCategory: (subCategoryId: number) =>
`product/?subCategory=${subCategoryId}`,
detail: (id: number) => `product/${id}/`,
},
faq: "faq/",
gallery: "gallery/",
contact: "contact/",
statistics: "statistics/",
post: {
sendNumber: "callBack/",
productContact: "customer/",
contact: "question/",
},
};