last push

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-08 20:15:28 +05:00
parent f18d35c29b
commit dfb8d3bdbc
26 changed files with 634 additions and 281 deletions

View File

@@ -1,18 +1,9 @@
'use client';
import React from 'react';
import { Menu } from 'lucide-react';
import { useTranslations } from 'next-intl';
import type { CabinetSection } from '../lib/types';
// ─── Labels ────────────────────────────────────────────────────────────────────
const SECTION_LABELS: Record<CabinetSection, string> = {
dashboard: 'Dashboard',
plagiat: 'Plagiat tekshiruvlar',
si: 'SI detektor',
payments: "To'lovlar tarixi",
profile: 'Profil',
};
// ─── Props ─────────────────────────────────────────────────────────────────────
interface CabinetNavProps {
@@ -25,19 +16,31 @@ interface CabinetNavProps {
export const CabinetNav: React.FC<CabinetNavProps> = ({
activeSection,
onMenuClick,
}) => (
<header className="h-14 px-4 md:px-6 flex lg:hidden items-center justify-between border-b border-slate-100 bg-white/80 backdrop-blur-sm sticky top-0 z-20">
<div className="flex items-center gap-3">
<button
onClick={onMenuClick}
className="lg:hidden p-2 rounded-lg text-slate-500 hover:bg-slate-100 transition-colors"
aria-label="Menu"
>
<Menu size={18} />
</button>
<h1 className="text-sm font-semibold text-slate-800">
{SECTION_LABELS[activeSection]}
</h1>
</div>
</header>
);
}) => {
const t = useTranslations('Cabinet');
const SECTION_LABELS: Record<CabinetSection, string> = {
dashboard: t('dashboard'),
plagiat: t('plagiatChecks'),
si: t('siNav'),
payments: t('payments'),
profile: t('profile'),
};
return (
<header className="h-14 px-4 md:px-6 flex lg:hidden items-center justify-between border-b border-slate-100 bg-white/80 backdrop-blur-sm sticky top-0 z-20">
<div className="flex items-center gap-3">
<button
onClick={onMenuClick}
className="lg:hidden p-2 rounded-lg text-slate-500 hover:bg-slate-100 transition-colors"
aria-label="Menu"
>
<Menu size={18} />
</button>
<h1 className="text-sm font-semibold text-slate-800">
{SECTION_LABELS[activeSection]}
</h1>
</div>
</header>
);
};

View File

@@ -10,23 +10,9 @@ import {
X,
} from 'lucide-react';
import { Link } from '@/shared/config/i18n/navigation';
import { useTranslations } from 'next-intl';
import type { CabinetSection } from '../lib/types';
// ─── Nav items ─────────────────────────────────────────────────────────────────
type NavItemDef =
| { id: CabinetSection; label: string; icon: React.ElementType; href?: never }
| { id: 'home'; label: string; icon: React.ElementType; href: string };
const NAV_ITEMS: NavItemDef[] = [
{ id: 'home', label: 'Bosh sahifa', icon: Home, href: '/' },
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ id: 'plagiat', label: 'Plagiat', icon: FileSearch },
{ id: 'si', label: 'SI detektor', icon: BrainCircuit },
{ id: 'payments', label: "To'lovlar tarixi", icon: CreditCard },
{ id: 'profile', label: 'Profil', icon: User },
];
// ─── Props ─────────────────────────────────────────────────────────────────────
interface SidebarProps {
@@ -45,95 +31,118 @@ export const Sidebar: React.FC<SidebarProps> = ({
isOpen,
onClose,
userName,
}) => (
<>
{/* Mobile backdrop */}
{isOpen && (
<div
className="fixed inset-0 z-30 bg-black/30 backdrop-blur-sm lg:hidden"
onClick={onClose}
/>
)}
}) => {
const t = useTranslations('Cabinet');
<aside
className={`
fixed top-0 left-0 z-40 h-full w-60 bg-white border-r border-slate-100
flex flex-col shadow-xl
transition-transform duration-300 ease-out
lg:sticky lg:top-0 lg:h-screen lg:translate-x-0 lg:shadow-none lg:z-auto
${isOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
{/* Brand */}
<div className="lg:hidden flex items-center justify-end px-5 py-4 border-b border-slate-100">
<button
const NAV_ITEMS = [
{ id: 'home' as const, label: t('home'), icon: Home, href: '/' },
{
id: 'dashboard' as CabinetSection,
label: t('dashboard'),
icon: LayoutDashboard,
},
{ id: 'plagiat' as CabinetSection, label: t('plagiat'), icon: FileSearch },
{ id: 'si' as CabinetSection, label: t('siNav'), icon: BrainCircuit },
{
id: 'payments' as CabinetSection,
label: t('payments'),
icon: CreditCard,
},
{ id: 'profile' as CabinetSection, label: t('profile'), icon: User },
];
return (
<>
{/* Mobile backdrop */}
{isOpen && (
<div
className="fixed inset-0 z-30 bg-black/30 backdrop-blur-sm lg:hidden"
onClick={onClose}
className=" p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
aria-label="Yopish"
>
<X size={15} />
</button>
</div>
/>
)}
{/* User pill */}
<div className="px-3 pt-4 pb-2">
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl bg-slate-50 border border-slate-100">
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
<span className="text-blue-600 text-xs font-semibold">
{userName.charAt(0).toUpperCase()}
</span>
</div>
<div className="min-w-0">
<p className="text-sm font-medium text-slate-800 truncate">
{userName}
</p>
<p className="text-[11px] text-slate-400">Shaxsiy kabinet</p>
<aside
className={`
fixed top-0 left-0 z-40 h-full w-60 bg-white border-r border-slate-100
flex flex-col shadow-xl
transition-transform duration-300 ease-out
lg:sticky lg:top-0 lg:h-screen lg:translate-x-0 lg:shadow-none lg:z-auto
${isOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
{/* Brand */}
<div className="lg:hidden flex items-center justify-end px-5 py-4 border-b border-slate-100">
<button
onClick={onClose}
className=" p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
aria-label={t('close')}
>
<X size={15} />
</button>
</div>
{/* User pill */}
<div className="px-3 pt-4 pb-2">
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl bg-slate-50 border border-slate-100">
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
<span className="text-blue-600 text-xs font-semibold">
{userName.charAt(0).toUpperCase()}
</span>
</div>
<div className="min-w-0">
<p className="text-sm font-medium text-slate-800 truncate">
{userName}
</p>
<p className="text-[11px] text-slate-400">
{t('personalCabinet')}
</p>
</div>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 px-3 py-2 space-y-0.5 overflow-y-auto">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
{/* Navigation */}
<nav className="flex-1 px-3 py-2 space-y-0.5 overflow-y-auto">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
if (item.id === 'home') {
if (item.id === 'home') {
return (
<Link
key="home"
href={item.href as string}
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 transition-all duration-150"
>
<Icon size={17} />
<span>{item.label}</span>
</Link>
);
}
const isActive = item.id === active;
return (
<Link
key="home"
href={item.href}
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 transition-all duration-150"
<button
key={item.id}
onClick={() => onNavigate(item.id as CabinetSection)}
className={`
w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium
transition-all duration-150
${
isActive
? 'bg-blue-50 text-blue-600'
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
}
`}
>
<Icon size={17} />
<span>{item.label}</span>
</Link>
{isActive && (
<span className="ml-auto w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0" />
)}
</button>
);
}
const isActive = item.id === active;
return (
<button
key={item.id}
onClick={() => onNavigate(item.id as CabinetSection)}
className={`
w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium
transition-all duration-150
${
isActive
? 'bg-blue-50 text-blue-600'
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
}
`}
>
<Icon size={17} />
<span>{item.label}</span>
{isActive && (
<span className="ml-auto w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0" />
)}
</button>
);
})}
</nav>
</aside>
</>
);
})}
</nav>
</aside>
</>
);
};

View File

@@ -1,33 +1,38 @@
'use client';
import React from 'react';
import { FileSearch, ArrowRight } from 'lucide-react';
import Link from 'next/link';
import { SiCTACard } from '@/features/modals/siModal/page';
import { useTranslations } from 'next-intl';
export const CtaCards = () => (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Plagiat */}
<Link
href={'/plagat'}
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-blue-500 to-blue-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
>
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
<FileSearch size={72} className="text-white" />
</div>
<FileSearch size={26} className="text-white mb-4" />
<h3 className="text-white font-semibold text-base mb-1">
Plagiat tekshiruvi
</h3>
<p className="text-blue-100 text-sm mb-4 leading-relaxed">
Hujjatingizni originallik uchun tekshiring
</p>
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
Yuborish <ArrowRight size={12} />
</span>
</Link>
export const CtaCards = () => {
const t = useTranslations('Cabinet');
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Plagiat */}
<Link
href={'/plagat'}
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-blue-500 to-blue-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
>
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
<FileSearch size={72} className="text-white" />
</div>
<FileSearch size={26} className="text-white mb-4" />
<h3 className="text-white font-semibold text-base mb-1">
{t('plagiatCheck')}
</h3>
<p className="text-blue-100 text-sm mb-4 leading-relaxed">
{t('checkDesc')}
</p>
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
{t('submit')} <ArrowRight size={12} />
</span>
</Link>
{/* SI */}
<SiCTACard />
</div>
</>
);
{/* SI */}
<SiCTACard />
</div>
</>
);
};

View File

@@ -96,7 +96,7 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({
const pay = useMutation({
mutationKey: ['si-payment', item.id],
mutationFn: () =>
apiRequest<{ payment_link: string }>('POST', links.si_payment(item.id)),
apiRequest<{ payment_link: string }>('POST', links.demo_pay(item.id)),
onSuccess: (res) => {
window.open(res.data.payment_link, '_self');
},

View File

@@ -3,16 +3,35 @@
import React, { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useParams } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { apiRequest } from '@/shared/request/apiRequest';
import { links } from '@/shared/request/links';
import { Download, CloudDownload } from 'lucide-react';
import { Download } from 'lucide-react';
// ── Types ────────────────────────────────────────────────────────────────────
type SiResult = {
original: number;
ai_possible: number;
type SiResultRes = {
ai: number;
hash: string;
text: string;
citation: number;
plagiarism: number;
originality: number;
};
type SiResultData = {
ok: boolean;
res: SiResultRes;
error: string;
success: string;
text_res: string;
analyze_text?: Record<string, unknown>;
};
type SiResult = {
id: number;
document: number;
result: SiResultData;
};
type SiDetail = {
@@ -134,6 +153,7 @@ function LoadingSkeleton() {
// ── Main Page ─────────────────────────────────────────────────────────────────
export default function SiDetailPage({ id }: { id: number }) {
const t = useTranslations('SiDetail');
const { locale } = useParams() as { locale: string };
useEffect(() => {
console.log(locale);
@@ -172,9 +192,10 @@ export default function SiDetailPage({ id }: { id: number }) {
}
// Derive SI percentages
const original = doc.result?.original ?? 100 - (doc.si_percantage ?? 0);
const aiPossible = doc.result?.ai_possible ?? 0;
const ai = doc.result?.ai ?? doc.si_percantage ?? 0;
const res = doc.result?.result?.res;
const original = res?.originality ?? 100 - (doc.si_percantage ?? 0);
const aiPossible = res?.plagiarism ?? 0;
const ai = res?.ai ?? doc.si_percantage ?? 0;
return (
<div className="min-h-screen bg-slate-50 font-sans">
@@ -200,7 +221,7 @@ export default function SiDetailPage({ id }: { id: number }) {
</svg>
</button>
<h1 className="text-base font-bold text-slate-800 truncate">
{doc.title || 'SI tekshiruv'}
{doc.title || t('siCheck')}
</h1>
</div>
</header>
@@ -211,7 +232,7 @@ export default function SiDetailPage({ id }: { id: number }) {
{/* Section header */}
<div className="bg-slate-50 border-b border-slate-100 px-6 py-4 text-center">
<h2 className="text-sm font-bold uppercase tracking-widest text-blue-600">
Asosiy ma&apos;lumotlar
{t('basicInfo')}
</h2>
<div className="w-10 h-0.5 bg-blue-600 mx-auto mt-1.5" />
</div>
@@ -219,48 +240,42 @@ export default function SiDetailPage({ id }: { id: number }) {
<div className="px-6">
{/* Sub-header */}
<p className="text-[11px] font-bold uppercase tracking-widest text-slate-400 py-4 border-b border-slate-100">
Hujjat haqida ma&apos;lumotlar
{t('documentInfo')}
</p>
<InfoRow
label="Hujjat nomi"
label={t('documentName')}
value={doc.title || fileName(doc.file)}
/>
{doc.user && (
<InfoRow
label="Tekshiruvchi"
label={t('checker')}
value={`${doc.user.name} ${doc.user.surname}`}
/>
)}
<InfoRow
label="Hujjat yuklangan vaqti"
label={t('uploadedAt')}
value={formatDate(doc.created_at)}
/>
<InfoRow label={t('originalFileName')} value={fileName(doc.file)} />
<InfoRow
label="Hujjat faylining original nomi"
value={fileName(doc.file)}
/>
<InfoRow
label="So'zlar soni"
label={t('wordCount')}
value={
doc.total_words > 0
? doc.total_words.toLocaleString('uz-UZ')
: '—'
}
/>
<InfoRow
label="Hujjat fayli kenggaytmasi"
value={fileExtension(doc.file)}
/>
<InfoRow label={t('fileExt')} value={fileExtension(doc.file)} />
{doc.file_size !== undefined && (
<InfoRow
label="Hujjat fayli o'lchami"
label={t('fileSize')}
value={fileSizeMb(doc.file_size)}
/>
)}
{doc.total_price !== undefined && (
<InfoRow
label="Yechilgan summa"
label={t('amountCharged')}
value={`${Number(doc.total_price).toLocaleString('uz-UZ')} so'm`}
/>
)}
@@ -275,7 +290,7 @@ export default function SiDetailPage({ id }: { id: number }) {
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-cyan-500 text-cyan-600 text-sm font-semibold hover:bg-cyan-50 transition-colors"
>
<Download size={15} />
Original hujjatni yuklab olish
{t('downloadOriginal')}
</a>
</div>
)}
@@ -308,21 +323,15 @@ export default function SiDetailPage({ id }: { id: number }) {
</div>
<div>
<h2 className="text-xl font-black text-slate-800 leading-snug">
Hujjatning SI detektori natijalari
{t('siResultsTitle')}
</h2>
<p className="text-sm text-slate-500 mt-1.5 leading-relaxed max-w-lg">
Ushbu oynada foydalanuvchi tomonidan yuklangan matn
sun&apos;iy intellekt (SI) yordamida yozilgan bo&apos;lish
ehtimoli bo&apos;yicha tahlil natijalari aks etirilgan.
Detektor matnning stilistik, grammatik va semantik
xususiyatlarini baholab, uning qanchalik darajada sun&apos;iy
intellekt tomonidan generatsiya qilingan bo&apos;lishi
mumkinligini foizlik ko&apos;rinishida ko&apos;rsatadi.
{t('siResultsDesc')}
</p>
</div>
</div>
{doc.file && (
{/* {doc.file && (
<a
href={doc.file}
target="_blank"
@@ -330,24 +339,24 @@ export default function SiDetailPage({ id }: { id: number }) {
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-slate-800 text-white text-sm font-semibold hover:bg-slate-700 transition-colors shrink-0"
>
<CloudDownload size={16} />
Yuklab olish
{t('download')}
</a>
)}
)} */}
</div>
{/* Bars */}
<div className="px-6 py-6 space-y-5">
<SiBar
label="Original matn"
label={t('originalText')}
value={original}
color="bg-emerald-400"
/>
<SiBar
label="Ehtimoliy Sun'iy Intellekt"
label={t('possibleAi')}
value={aiPossible}
color="bg-amber-400"
/>
<SiBar label="Sun'iy Intellekt" value={ai} color="bg-red-500" />
<SiBar label={t('aiContent')} value={ai} color="bg-red-500" />
</div>
</div>
</main>

View File

@@ -10,7 +10,7 @@ import PaymentStatus from './paidStatus';
import Sertifikat from '@/features/modals/sertificateModal/sertifikat';
// ── Types ────────────────────────────────────────────────────────────────────
const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1';
interface AnalyzeText {
[key: string]: number | string;
}
@@ -397,10 +397,10 @@ export default function DocumentDetailPage({ id }: { id: number }) {
<div className="flex flex-wrap items-center gap-3">
<PaymentStatus status={doc.state} />
{doc.certificate && <Sertifikat document_id={Number(id)} />}
<Sertifikat document_id={Number(id)} />
{doc.file && (
<a
href={doc.file}
href={`${baseUrl}${doc.file}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-slate-700 transition-colors"
@@ -486,21 +486,26 @@ export default function DocumentDetailPage({ id }: { id: number }) {
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
<div className="flex flex-wrap justify-around gap-8">
<ScoreRing
value={res.originality}
label="Originality"
value={res.ai}
label={t('scoreOriginality')}
color="#10b981"
/>
<ScoreRing
value={res.plagiarism}
label="Plagiarism"
label={t('scorePlagiarism')}
color="#f59e0b"
/>
<ScoreRing
value={res.citation}
label="Citation"
label={t('scoreCitation')}
color="#6366f1"
/>
<ScoreRing value={res.ai} label="AI Content" color="#ef4444" />
<ScoreRing
value={res.originality}
label={t('scoreAiContent')}
color="#ef4444"
/>
</div>
<div

View File

@@ -1,3 +1,5 @@
'use client';
import { useTranslations } from 'next-intl';
import { blue } from '../../lib/constant';
import { PlagiatData } from '../../lib/types';
import BarRow from './BarRow';
@@ -14,27 +16,28 @@ export default function GaugeWithBars({
originalityPercent,
citationPercent,
}: Props) {
const t = useTranslations('PlagiatResult');
return (
<div className="flex items-center gap-5 mb-5">
<CircleGauge value={plagiarismPercent || 0} />
<div className="flex-1">
<BarRow
label="Plagiat"
label={t('plagiat')}
value={plagiarismPercent || 0}
color={blue[900]}
/>
<BarRow
label="AI generatsiya"
label={t('aiGeneration')}
value={aiPercent || 0}
color={blue[600]}
/>
<BarRow
label="Original"
label={t('original')}
value={originalityPercent || 0}
color={blue[400]}
/>
<BarRow
label="Iqtibos"
label={t('citation')}
value={citationPercent || 0}
color={blue[200]}
/>

View File

@@ -1,3 +1,5 @@
'use client';
import { useTranslations } from 'next-intl';
import { blue } from '../../lib/constant';
import { PlagiatData } from '../../lib/types';
@@ -21,6 +23,7 @@ export default function Header({
location,
checkedAt,
}: Props) {
const t = useTranslations('PlagiatResult');
return (
<div className="flex items-center gap-3 mb-5">
<div
@@ -41,7 +44,7 @@ export default function Header({
className="text-xs px-2.5 py-0.5 rounded-md font-medium"
style={{ background: blue[100], color: blue[800] }}
>
{plagiarismPercent}% plagiat
{plagiarismPercent}% {t('plagiat').toLowerCase()}
</span>
</div>
<p className="text-[13px] mt-0.5 truncate" style={{ color: blue[400] }}>
@@ -50,7 +53,7 @@ export default function Header({
</div>
<div className="text-right shrink-0">
<p className="text-[11px]" style={{ color: blue[400] }}>
Tekshirilgan
{t('checked')}
</p>
<p className="text-[12px] mt-0.5" style={{ color: blue[600] }}>
{checkedAt}

View File

@@ -1,3 +1,5 @@
'use client';
import { useTranslations } from 'next-intl';
import { PlagiatData } from '../../lib/types';
import MetricCard from './Metriccard';
@@ -12,12 +14,16 @@ export default function TopMetrics({
originalityPercent,
citationPercent,
}: Props) {
const t = useTranslations('PlagiatResult');
return (
<div className="grid grid-cols-4 gap-2.5 mb-5">
<MetricCard label="Plagiat darajasi" value={`${plagiarismPercent}%`} />
<MetricCard label="AI yozgan" value={`${aiPercent}%`} />
<MetricCard label="Originallik" value={`${originalityPercent}%`} />
<MetricCard label="Iqtibos" value={`${citationPercent}%`} />
<MetricCard
label={t('plagiarismLevel')}
value={`${plagiarismPercent}%`}
/>
<MetricCard label={t('aiWritten')} value={`${aiPercent}%`} />
<MetricCard label={t('originality')} value={`${originalityPercent}%`} />
<MetricCard label={t('citation')} value={`${citationPercent}%`} />
</div>
);
}

View File

@@ -145,26 +145,6 @@ function Skeleton() {
);
}
// ─── Error state ──────────────────────────────────────────────────────────────
function ErrorState() {
return (
<div className="min-h-screen flex items-center justify-center p-6 bg-slate-50">
<div
className="rounded-xl p-8 text-center"
style={{ background: '#fff', border: `0.5px solid ${blue[100]}` }}
>
<p className="text-sm font-medium mb-1" style={{ color: blue[900] }}>
Ma&apos;lumot topilmadi
</p>
<p className="text-xs" style={{ color: blue[400] }}>
Ushbu tekshiruv mavjud emas yoki o&apos;chirilgan
</p>
</div>
</div>
);
}
// ─── Component ────────────────────────────────────────────────────────────────
export default function PlagiatResult({ id }: { id: number }) {
@@ -186,7 +166,22 @@ export default function PlagiatResult({ id }: { id: number }) {
});
if (isLoading) return <Skeleton />;
if (isError || !rawData) return <ErrorState />;
if (isError || !rawData)
return (
<div className="min-h-screen flex items-center justify-center p-6 bg-slate-50">
<div
className="rounded-xl p-8 text-center"
style={{ background: '#fff', border: `0.5px solid ${blue[100]}` }}
>
<p className="text-sm font-medium mb-1" style={{ color: blue[900] }}>
Ma&apos;lumot topilmadi
</p>
<p className="text-xs" style={{ color: blue[400] }}>
Ushbu tekshiruv mavjud emas yoki o&apos;chirilgan
</p>
</div>
</div>
);
const data: PlagiatData = transformResponse(rawData);

View File

@@ -30,7 +30,7 @@ const PageHeader: React.FC = () => {
>
<Plus size={15} className="text-white" />
<h3 className="text-white font-semibold text-base">
Plagiat tekshiruvi
{t('plagiatCheck')}
</h3>
</Link>
</div>

View File

@@ -1,10 +1,20 @@
'use client';
import { useEffect } from 'react';
import Hero from './components/Hero';
import InfoSection from './components/InfoSection';
import StepsSection from './components/StepsSection';
import Ticker from './components/Ticker';
import { useRouter } from '@/shared/config/i18n/navigation';
const PlagiarismLanding = () => {
const route = useRouter();
useEffect(() => {
const data = localStorage.getItem('user');
if (data) {
route.push('/plagat');
}
}, []);
return (
<>
<Hero />

View File

@@ -35,7 +35,7 @@ export interface PlagiarismFormState {
file: File | null;
certificate: boolean;
text?: string;
type: string;
type: number;
}
export type PlagiarismFormErrors = Partial<

View File

@@ -21,7 +21,7 @@ const INITIAL_FORM: PlagiarismFormState = {
file: null,
certificate: true,
text: '',
type: 'boshqa',
type: 0,
};
const PRICE: PriceCalculate = {
@@ -71,6 +71,7 @@ export function usePlagiarismForm() {
const priceInfo: PriceCalculate = {
total_price: resdata?.total_price || 0,
discount: resdata?.discount || 0,
certificate: resdata?.certificate || 0,
service_fee: resdata?.service_fee || 0,
};
setPrices(priceInfo);
@@ -90,7 +91,7 @@ export function usePlagiarismForm() {
const payment = useMutation({
mutationKey: ['payload'],
mutationFn: ({ order_id }: { order_id: number }) =>
apiRequest<{ payment_link: string }>('POST', links.payment(order_id)),
apiRequest<{ payment_link: string }>('POST', links.demo_pay(order_id)),
onSuccess: (res) => {
console.log('payment res: ', res);
window.open(res.data.payment_link, '_self');
@@ -116,9 +117,9 @@ export function usePlagiarismForm() {
setErrors((prev) => ({ ...prev, file: undefined }));
}, []);
const setOption = useCallback((option: string) => {
setForm((prev) => ({ ...prev, document_type: option }));
setErrors((prev) => ({ ...prev, document_type: undefined }));
const setOption = useCallback((option: number) => {
setForm((prev) => ({ ...prev, type: option }));
setErrors((prev) => ({ ...prev, type: undefined }));
}, []);
const toggleCertificate = useCallback(() => {
@@ -150,7 +151,7 @@ export function usePlagiarismForm() {
fd.append('text', form.text || '');
fd.append('file', form.file!);
fd.append('certificate', String(form.certificate));
fd.append('type', form.type);
fd.append('type', String(form.type));
console.log('sended data: ', fd);
checkdocumentRequest.mutate(fd);
},

View File

@@ -1,5 +1,6 @@
'use client';
import React from 'react';
import { useTranslations } from 'next-intl';
import { FieldWrapper } from './Plagiraismui';
import { useQuery } from '@tanstack/react-query';
import { apiRequest } from '@/shared/request/apiRequest';
@@ -12,8 +13,8 @@ type DocumentType = {
};
interface DocumentsTypesProps {
value: string;
onChange: (value: string) => void;
value: number;
onChange: (value: number) => void;
disabled?: boolean;
}
@@ -22,6 +23,7 @@ export default function DocumentsTypes({
onChange,
disabled,
}: DocumentsTypesProps) {
const t = useTranslations('DocumentTypes');
const { data, isLoading } = useQuery({
queryKey: ['document_types'],
queryFn: (): Promise<DocumentType[]> =>
@@ -30,20 +32,24 @@ export default function DocumentsTypes({
),
});
const selected = data?.find((item) => item.id === value);
return (
<FieldWrapper htmlFor="document_type" label="Hujjat turi">
<FieldWrapper htmlFor="document_type" label={t('label')}>
<select
id="document_type"
value={value}
onChange={(e) => onChange(e.target.value)}
value={selected?.name}
onChange={(e) => {
onChange(Number(e.target.value));
}}
disabled={isLoading || disabled}
className={`${inputCls} cursor-pointer`}
>
<option value="" disabled>
{isLoading ? 'Yuklanmoqda...' : 'Hujjat turini tanlang...'}
{isLoading ? t('loading') : t('placeholder')}
</option>
{data?.map((type) => (
<option key={type.id} value={String(type.id)}>
<option key={type.id} value={type.id}>
{type.name}
</option>
))}