payment not found

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-03-10 17:23:20 +05:00
parent f157c56b93
commit 79436a9b9d
7 changed files with 567 additions and 88 deletions

View File

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

View File

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

View File

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

View File

@@ -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);
};
@@ -55,7 +60,7 @@ export default function LanguageSelectRadix() {
<button
onClick={() => setIsOpen(!isOpen)}
disabled={isPending}
className="inline-flex items-center justify-between gap-2 px-2 py-1 border border-gray-300 hover:border-red-600
className="inline-flex items-center justify-between gap-2 px-2 py-1 border border-gray-300 hover:border-red-600
rounded-lg text-white text-sm font-medium shadow-sm hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
aria-label="Tilni tanlash"
aria-expanded={isOpen}

View File

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

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

View File

@@ -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
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-locale", localeFromPath);
requestHeaders.set("x-pathname", pathname);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
// Normal flow - just pass locale in headers
const response = NextResponse.next();
// 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",
});
}
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|.*\\..*).*)"],
};