payment not found
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { redirect } from 'next/navigation'
|
||||
import React from 'react'
|
||||
|
||||
import PaymentFailed from "@/components/pages/payment";
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react";
|
||||
|
||||
export default function Page() {
|
||||
return redirect('/home')
|
||||
// return redirect('/home')
|
||||
return <PaymentFailed />;
|
||||
}
|
||||
|
||||
@@ -155,10 +155,11 @@ export default async function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<InitialLoading />
|
||||
{/* <InitialLoading />
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
<Providers>{children}</Providers>
|
||||
</NextIntlClientProvider>
|
||||
</NextIntlClientProvider> */}
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
|
||||
import PaymentFailed from "@/components/pages/payment";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Home() {
|
||||
return redirect('/uz/home')
|
||||
// return redirect('/uz/home')
|
||||
return <PaymentFailed />;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import { useRouter, usePathname } from "next/navigation";
|
||||
import { Check, ChevronDown, Globe } from "lucide-react";
|
||||
import { locales, localeFlags, localeNames, type Locale } from "@/i18n/config";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export default function LanguageSelectRadix() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const queryClient = useQueryClient();
|
||||
const currentLocale = useLocale() as Locale;
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -46,6 +48,9 @@ export default function LanguageSelectRadix() {
|
||||
}, 100); // Small delay ensures navigation completes
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
queryClient.invalidateQueries({ queryKey: [newLocale] });
|
||||
}, 200);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Navbar() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
const { data: navbarItems } = useQuery({
|
||||
queryKey: ["navbaritem"],
|
||||
queryKey: ["navbaritem",locale],
|
||||
queryFn: () => httpClient(endPoints.navbar),
|
||||
select: (data: any) => ({
|
||||
results: data?.data?.data?.results,
|
||||
|
||||
527
components/pages/payment/index.tsx
Normal file
527
components/pages/payment/index.tsx
Normal file
@@ -0,0 +1,527 @@
|
||||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const translations = {
|
||||
uz: {
|
||||
badge: "To'lov amalga oshmadi",
|
||||
title: "To'lov\nQabul\nQilinmagani uchun Sayt O'chirildi",
|
||||
subtitle: "Afsuski, to'lovingizni qayta ishlashda xatolik yuz berdi.",
|
||||
reasons_title: "Mumkin bo'lgan sabablar:",
|
||||
reasons: [
|
||||
"Karta mablag'i yetarli emas",
|
||||
"Bank tomonidan to'lov rad etildi",
|
||||
"Karta ma'lumotlari noto'g'ri",
|
||||
"Tarmoq ulanish muammosi",
|
||||
],
|
||||
retry: "Qayta urinish",
|
||||
support: "Qo'llab-quvvatlash",
|
||||
back: "Orqaga qaytish",
|
||||
code: "Xato kodi",
|
||||
time: "Vaqt",
|
||||
},
|
||||
ru: {
|
||||
badge: "Платёж не выполнен",
|
||||
title: "Из-за\nнеполучения\nплатежа сайт отключен",
|
||||
subtitle: "К сожалению, при обработке вашего платежа произошла ошибка.",
|
||||
reasons_title: "Возможные причины:",
|
||||
reasons: [
|
||||
"Недостаточно средств на карте",
|
||||
"Платёж отклонён банком",
|
||||
"Неверные данные карты",
|
||||
"Проблема с сетевым подключением",
|
||||
],
|
||||
retry: "Повторить",
|
||||
support: "Поддержка",
|
||||
back: "Назад",
|
||||
code: "Код ошибки",
|
||||
time: "Время",
|
||||
},
|
||||
en: {
|
||||
badge: "Payment Failed",
|
||||
title: "The site\nwas disabled\ndue to non-payment",
|
||||
subtitle: "Unfortunately, an error occurred while processing your payment.",
|
||||
reasons_title: "Possible reasons:",
|
||||
reasons: [
|
||||
"Insufficient funds on card",
|
||||
"Payment declined by bank",
|
||||
"Incorrect card details",
|
||||
"Network connection issue",
|
||||
],
|
||||
retry: "Try Again",
|
||||
support: "Support",
|
||||
back: "Go Back",
|
||||
code: "Error Code",
|
||||
time: "Time",
|
||||
},
|
||||
};
|
||||
|
||||
const ERROR_CODE = "ERR-4082";
|
||||
|
||||
export default function PaymentFailed() {
|
||||
const [lang, setLang] = useState("en");
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [shake, setShake] = useState(false);
|
||||
const [time] = useState(() =>
|
||||
new Date().toLocaleTimeString("en-GB", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
);
|
||||
|
||||
const t = translations[lang as "uz" | "ru" | "en"];
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setVisible(true), 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleRetry = () => {
|
||||
setShake(true);
|
||||
setTimeout(() => setShake(false), 600);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap');
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--red: #E83A3A;
|
||||
--red-dark: #C02828;
|
||||
--red-glow: rgba(232,58,58,0.18);
|
||||
--bg: #0D0D0D;
|
||||
--surface: #141414;
|
||||
--surface2: #1C1C1C;
|
||||
--border: rgba(255,255,255,0.07);
|
||||
--text: #F0EDE8;
|
||||
--muted: rgba(240,237,232,0.45);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Noise overlay */
|
||||
.page::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Left panel */
|
||||
.left {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 2.5rem;
|
||||
border-right: 1px solid var(--border);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
transition: opacity 0.7s ease, transform 0.7s ease;
|
||||
}
|
||||
.left.visible { opacity: 1; transform: translateX(0); }
|
||||
|
||||
.lang-switcher {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.lang-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border-radius: 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.lang-btn:hover { color: var(--text); border-color: rgba(255,255,255,0.2); }
|
||||
.lang-btn.active {
|
||||
background: var(--red);
|
||||
border-color: var(--red);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.title-block { flex: 1; display: flex; align-items: center; }
|
||||
|
||||
.main-title {
|
||||
font-family: 'Bebas Neue', sans-serif;
|
||||
font-size: clamp(4.5rem, 10vw, 9rem);
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--text);
|
||||
white-space: pre-line;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title-accent {
|
||||
display: block;
|
||||
color: var(--red);
|
||||
position: relative;
|
||||
}
|
||||
.title-accent::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: var(--red);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
animation: underline-grow 0.5s 0.9s ease forwards;
|
||||
}
|
||||
@keyframes underline-grow { to { transform: scaleX(1); } }
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
.meta-item { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.meta-label {
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.meta-value {
|
||||
font-family: 'Bebas Neue', sans-serif;
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
/* Right panel */
|
||||
.right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 2.5rem 3rem;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
transition: opacity 0.7s 0.2s ease, transform 0.7s 0.2s ease;
|
||||
}
|
||||
.right.visible { opacity: 1; transform: translateX(0); }
|
||||
|
||||
/* Glowing orb background */
|
||||
.orb {
|
||||
position: absolute;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(232,58,58,0.12) 0%, transparent 70%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
animation: pulse-orb 4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-orb {
|
||||
0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.6; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: var(--red-glow);
|
||||
border: 1px solid rgba(232,58,58,0.3);
|
||||
border-radius: 2rem;
|
||||
padding: 0.4rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--red);
|
||||
margin-bottom: 2rem;
|
||||
width: fit-content;
|
||||
}
|
||||
.badge-dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--red);
|
||||
animation: blink 1.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
.icon-wrap {
|
||||
width: 80px; height: 80px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface2);
|
||||
border: 1px solid rgba(232,58,58,0.25);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
animation: shake-anim 0s ease;
|
||||
}
|
||||
.icon-wrap.shake { animation: shake-anim 0.5s ease; }
|
||||
@keyframes shake-anim {
|
||||
0%, 100% { transform: translateX(0) rotate(0); }
|
||||
15% { transform: translateX(-6px) rotate(-3deg); }
|
||||
30% { transform: translateX(6px) rotate(3deg); }
|
||||
45% { transform: translateX(-4px) rotate(-2deg); }
|
||||
60% { transform: translateX(4px) rotate(2deg); }
|
||||
75% { transform: translateX(-2px) rotate(-1deg); }
|
||||
}
|
||||
.icon-ring {
|
||||
position: absolute;
|
||||
inset: -8px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(232,58,58,0.15);
|
||||
animation: ring-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes ring-pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.5; }
|
||||
50% { transform: scale(1.08); opacity: 1; }
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.reasons-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
.reasons-title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.reason-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 300;
|
||||
color: var(--text);
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||
}
|
||||
.reason-item.visible { opacity: 1; transform: translateX(0); }
|
||||
.reason-item:last-child { border-bottom: none; }
|
||||
.reason-dot {
|
||||
width: 4px; height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--red);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--red);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 1rem 1.5rem;
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, transform 0.1s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn-primary::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: white;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.btn-primary:hover { background: var(--red-dark); }
|
||||
.btn-primary:hover::after { opacity: 0.05; }
|
||||
.btn-primary:active { transform: scale(0.98); }
|
||||
|
||||
.btn-row { display: flex; gap: 0.75rem; }
|
||||
.btn-secondary {
|
||||
flex: 1;
|
||||
background: var(--surface2);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 0.85rem 1rem;
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: var(--surface);
|
||||
border-color: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
/* Diagonal stripe decoration */
|
||||
.stripe {
|
||||
position: absolute;
|
||||
top: 0; right: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, transparent, var(--red), transparent);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.page { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
|
||||
.left {
|
||||
padding: 1.5rem;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.title-block { align-items: flex-start; }
|
||||
.main-title { font-size: clamp(3.5rem, 14vw, 5.5rem); }
|
||||
.right { padding: 2rem 1.5rem; }
|
||||
.orb { width: 250px; height: 250px; }
|
||||
.meta-row { gap: 1.5rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.left { padding: 1.25rem; }
|
||||
.right { padding: 1.5rem 1.25rem; }
|
||||
.btn-row { flex-direction: column; }
|
||||
.reasons-card { padding: 1.25rem; }
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="page">
|
||||
{/* LEFT */}
|
||||
<div className={`left ${visible ? "visible" : ""}`}>
|
||||
<div className="lang-switcher">
|
||||
{["uz", "ru", "en"].map((l) => (
|
||||
<button
|
||||
key={l}
|
||||
className={`lang-btn ${lang === l ? "active" : ""}`}
|
||||
onClick={() => setLang(l)}
|
||||
>
|
||||
{l.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="title-block">
|
||||
<h1 className="main-title">
|
||||
{t.title.split("\n").map((line:any, i:number) =>
|
||||
i === 1 ? (
|
||||
<span key={i} className="title-accent">{line}</span>
|
||||
) : (
|
||||
<span key={i} style={{ display: "block" }}>{line}</span>
|
||||
)
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="meta-row">
|
||||
<div className="meta-item">
|
||||
<span className="meta-label">{t.code}</span>
|
||||
<span className="meta-value">{ERROR_CODE}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<span className="meta-label">{t.time}</span>
|
||||
<span className="meta-value">{time}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT */}
|
||||
<div className={`right ${visible ? "visible" : ""}`}>
|
||||
<div className="orb" />
|
||||
<div className="stripe" />
|
||||
|
||||
<div className="badge">
|
||||
<span className="badge-dot" />
|
||||
{t.badge}
|
||||
</div>
|
||||
|
||||
<div className={`icon-wrap ${shake ? "shake" : ""}`}>
|
||||
<div className="icon-ring" />
|
||||
<svg width="34" height="34" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
|
||||
stroke="#E83A3A"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p className="subtitle">{t.subtitle}</p>
|
||||
|
||||
<div className="reasons-card">
|
||||
<p className="reasons-title">{t.reasons_title}</p>
|
||||
{t.reasons.map((reason, i) => (
|
||||
<div
|
||||
key={`${lang}-${i}`}
|
||||
className={`reason-item ${visible ? "visible" : ""}`}
|
||||
style={{ transitionDelay: `${0.4 + i * 0.1}s` }}
|
||||
>
|
||||
<span className="reason-dot" />
|
||||
{reason}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<button className="btn-primary" onClick={handleRetry}>
|
||||
+998-99-940-00-49
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { match as matchLocale } from "@formatjs/intl-localematcher";
|
||||
import Negotiator from "negotiator";
|
||||
const PUBLIC_PAGES = ["/login", "/register"];
|
||||
|
||||
const LOCALES = ["uz", "ru", "en"];
|
||||
const DEFAULT_LOCALE = "uz";
|
||||
@@ -9,23 +8,18 @@ const DEFAULT_LOCALE = "uz";
|
||||
type Locale = (typeof LOCALES)[number];
|
||||
|
||||
function getLocaleFromPathname(pathname: string): Locale | null {
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
const firstSegment = segments[0];
|
||||
|
||||
const firstSegment = pathname.split("/").filter(Boolean)[0];
|
||||
if (firstSegment && LOCALES.includes(firstSegment as Locale)) {
|
||||
return firstSegment as Locale;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLocaleFromCookie(request: NextRequest): Locale | null {
|
||||
const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
|
||||
|
||||
if (cookieLocale && LOCALES.includes(cookieLocale as Locale)) {
|
||||
return cookieLocale as Locale;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -36,13 +30,8 @@ function getLocaleFromHeaders(request: NextRequest): Locale {
|
||||
});
|
||||
|
||||
const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
|
||||
|
||||
try {
|
||||
return matchLocale(
|
||||
languages,
|
||||
LOCALES as unknown as string[],
|
||||
DEFAULT_LOCALE
|
||||
) as Locale;
|
||||
return matchLocale(languages, LOCALES as string[], DEFAULT_LOCALE) as Locale;
|
||||
} catch {
|
||||
return DEFAULT_LOCALE;
|
||||
}
|
||||
@@ -51,86 +40,37 @@ function getLocaleFromHeaders(request: NextRequest): Locale {
|
||||
export function middleware(request: NextRequest) {
|
||||
const { pathname, search } = request.nextUrl;
|
||||
|
||||
// Skip public files and API routes
|
||||
if (
|
||||
pathname.includes(".") ||
|
||||
pathname.startsWith("/api") ||
|
||||
pathname.startsWith("/_next")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// 1. Check URL locale
|
||||
// 1. If URL has a locale, pass it through with headers (+ sync cookie if needed)
|
||||
const localeFromPath = getLocaleFromPathname(pathname);
|
||||
|
||||
// 2. Check cookie locale
|
||||
const localeFromCookie = getLocaleFromCookie(request);
|
||||
const preferredLocale = localeFromPath ?? localeFromCookie ?? getLocaleFromHeaders(request);
|
||||
|
||||
// 3. Check browser locale
|
||||
const localeFromBrowser = getLocaleFromHeaders(request);
|
||||
|
||||
// Priority: URL > Cookie > Browser
|
||||
const preferredLocale =
|
||||
localeFromPath || localeFromCookie || localeFromBrowser;
|
||||
|
||||
// Faqat kerakli sahifalarni redirect qilamiz
|
||||
const isPublicPage = PUBLIC_PAGES.some((page) => pathname === page);
|
||||
|
||||
if (isPublicPage) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = `/${DEFAULT_LOCALE}/verify-otp`;
|
||||
url.search = search; // ?code=1111&phone=...
|
||||
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// If URL has no locale, redirect with preferred locale
|
||||
// 2. No locale in URL → redirect to preferred locale
|
||||
if (!localeFromPath) {
|
||||
const newUrl = new URL(`/${preferredLocale}${pathname}`, request.url);
|
||||
newUrl.search = search;
|
||||
return NextResponse.redirect(newUrl);
|
||||
}
|
||||
|
||||
// If URL locale differs from cookie, update cookie
|
||||
if (localeFromPath !== localeFromCookie) {
|
||||
const response = NextResponse.next();
|
||||
|
||||
// ✅ Set cookie on server side
|
||||
response.cookies.set("NEXT_LOCALE", localeFromPath, {
|
||||
path: "/",
|
||||
maxAge: 31536000, // 1 year
|
||||
sameSite: "lax",
|
||||
});
|
||||
|
||||
// ✅ Pass locale to request headers for server components
|
||||
// 3. Build response with locale headers for server components
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set("x-locale", localeFromPath);
|
||||
requestHeaders.set("x-pathname", pathname);
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
const response = NextResponse.next({ request: { headers: requestHeaders } });
|
||||
|
||||
// 4. Sync cookie if it differs from URL locale
|
||||
if (localeFromPath !== localeFromCookie) {
|
||||
response.cookies.set("NEXT_LOCALE", localeFromPath, {
|
||||
path: "/",
|
||||
maxAge: 31_536_000, // 1 year
|
||||
sameSite: "lax",
|
||||
});
|
||||
}
|
||||
|
||||
// Normal flow - just pass locale in headers
|
||||
const response = NextResponse.next();
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set("x-locale", localeFromPath);
|
||||
requestHeaders.set("x-pathname", pathname);
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Match all pathnames except for
|
||||
// - … if they start with `/api`, `/_next` or `/_vercel`
|
||||
// - … the ones containing a dot (e.g. `favicon.ico`)
|
||||
'/((?!api|_next|_vercel|.*\\..*).*)',
|
||||
],
|
||||
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||
};
|
||||
Reference in New Issue
Block a user