This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-06 15:43:51 +05:00
parent 89c5552c4e
commit 27b1510842
23 changed files with 1871 additions and 26 deletions

178
public/modules.html Normal file
View File

@@ -0,0 +1,178 @@
<style>
.stat-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:2rem}
.stat{background:var(--color-background-secondary);border-radius:var(--border-radius-md);padding:14px 16px}
.stat-n{font-size:22px;font-weight:500;color:var(--color-text-primary);line-height:1}
.stat-l{font-size:12px;color:var(--color-text-secondary);margin-top:4px}
.cat-header{display:flex;align-items:center;gap:10px;margin:1.75rem 0 0.85rem}
.cat-line{flex:1;height:0.5px;background:var(--color-border-tertiary)}
.cat-label{font-size:11px;font-weight:500;letter-spacing:.07em;color:var(--color-text-secondary);white-space:nowrap}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px}
.card{background:var(--color-background-primary);border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);padding:12px 14px;display:flex;align-items:flex-start;gap:10px}
.icon{width:34px;height:34px;border-radius:var(--border-radius-md);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.body{flex:1;min-width:0}
.name{font-size:13px;font-weight:500;color:var(--color-text-primary);margin-bottom:3px;line-height:1.4}
.desc{font-size:12px;color:var(--color-text-secondary);line-height:1.5;margin-bottom:6px}
.tags{display:flex;flex-wrap:wrap;gap:5px}
.tag{font-size:10px;font-weight:500;padding:2px 8px;border-radius:20px}
.t-sci{background:#E6F1FB;color:#0C447C}
.t-ai{background:#EEEDFE;color:#3C3489}
.t-legal{background:#FAEEDA;color:#633806}
.t-med{background:#FBEAF0;color:#72243E}
.t-int{background:#E1F5EE;color:#085041}
.t-free{background:#EAF3DE;color:#27500A}
.t-oav{background:#F1EFE8;color:#444441}
.t-inner{background:#F1EFE8;color:#444441}
.num{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:var(--color-background-secondary);font-size:10px;font-weight:500;color:var(--color-text-secondary);flex-shrink:0;margin-top:1px}
</style>
<div style="padding:1.5rem 0 2rem">
<div class="stat-row">
<div class="stat"><div class="stat-n">30</div><div class="stat-l">Jami modullar</div></div>
<div class="stat"><div class="stat-n">10</div><div class="stat-l">Bepul internet manbalari</div></div>
<div class="stat"><div class="stat-n">5</div><div class="stat-l">AI tahlil modullari</div></div>
<div class="stat"><div class="stat-n">4</div><div class="stat-l">Kategoriya</div></div>
</div>
<div class="cat-header"><div class="cat-line"></div><span class="cat-label">Ilmiy va ta'lim bazalari</span><div class="cat-line"></div></div>
<div class="grid">
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></div>
<div class="body"><div class="name">eLIBRARY.RU</div><div class="desc">Rus va xorijiy tillardagi ilmiy maqolalarning to'liq matnlari bazasi</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#EEEDFE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#534AB7" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5M2 12l10 5 10-5"/></svg></div>
<div class="body"><div class="name">Публикации eLIBRARY (tarjima va qayta bayon)</div><div class="desc">Tarjima va parafraz qilingan maqolalarni aniqlash</div><div class="tags"><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></div>
<div class="body"><div class="name">RDK to'plami</div><div class="desc">Rossiya Davlat kutubxonasidan dissertatsiya va avtoreferatlar</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg></div>
<div class="body"><div class="name">BMK dissertatsiyalari</div><div class="desc">Belarus milliy kutubxonasi dissertatsiyalari va avtoreferatlari</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#EAF3DE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#3B6D11" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8m-4-4v4"/></svg></div>
<div class="body"><div class="name">IEEE</div><div class="desc">Xalqaro elektrotexnika va elektronika muhandislari instituti bazasi</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#EEEDFE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#534AB7" stroke-width="2"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></div>
<div class="body"><div class="name">IEEE parafraz moduli</div><div class="desc">IEEE maqolalarining qayta bayon qilingan variantlarini aniqlash</div><div class="tags"><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#F1EFE8"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#5F5E5A" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg></div>
<div class="body"><div class="name">Elektron-kutubxona tizimlari</div><div class="desc">Book.ru, Юрайт, Лань, Айбукс va boshqa ELS bazalari</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></div>
<div class="body"><div class="name">OTMlar halqasi</div><div class="desc">O'zbekiston oliy ta'lim muassasalari birgalikdagi bazasi</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></div>
<div class="body"><div class="name">Коллекция НБУ</div><div class="desc">O'zbekiston milliy kutubxonasi to'plami</div><div class="tags"><span class="tag t-sci">Ilmiy</span></div></div>
</div>
</div>
<div class="cat-header"><div class="cat-line"></div><span class="cat-label">Huquqiy va normativ bazalar</span><div class="cat-line"></div></div>
<div class="grid">
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div>
<div class="body"><div class="name">Patentlar</div><div class="desc">SSSR, O'zbekiston, Rossiya va MDH davlatlari patentlari bazasi</div><div class="tags"><span class="tag t-legal">Huquqiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M9 13h6M9 17h6M9 9h1"/></svg></div>
<div class="body"><div class="name">ИПС Адилет</div><div class="desc">O'zbekiston qonunchilik bazasi hujjatlari</div><div class="tags"><span class="tag t-legal">Huquqiy</span></div></div>
</div>
</div>
<div class="cat-header"><div class="cat-line"></div><span class="cat-label">Internet tekshiruv modullari</span><div class="cat-line"></div></div>
<div class="grid">
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div>
<div class="body"><div class="name">Internet PLUS moduli</div><div class="desc">Internet bo'ylab kengaytirilgan chuqur skanerlash</div><div class="tags"><span class="tag t-int">Internet</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg></div>
<div class="body"><div class="name">Internet RU — parafraz</div><div class="desc">Rus internet segmentidagi qayta bayon qilingan qarzlar</div><div class="tags"><span class="tag t-int">Internet</span><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg></div>
<div class="body"><div class="name">Internet EN — parafraz</div><div class="desc">Ingliz internet segmentidagi qayta bayon qilingan qarzlar</div><div class="tags"><span class="tag t-int">Internet</span><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M5 12h14M12 5l7 7-7 7"/></svg></div>
<div class="body"><div class="name">Internet RU — tarjima</div><div class="desc">Rus internet segmentidagi tarjima qilingan qarzlar</div><div class="tags"><span class="tag t-int">Internet</span><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M5 12h14M12 5l7 7-7 7"/></svg></div>
<div class="body"><div class="name">Internet EN — tarjima</div><div class="desc">Ingliz internet segmentidagi tarjima qilingan qarzlar</div><div class="tags"><span class="tag t-int">Internet</span><span class="tag t-ai">AI tahlil</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#F1EFE8"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#5F5E5A" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div>
<div class="body"><div class="name">СМИ России и СНГ</div><div class="desc">Rossiya va MDH ommaviy axborot vositalari maqolalari</div><div class="tags"><span class="tag t-oav">OAV</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#F1EFE8"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#5F5E5A" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div>
<div class="body"><div class="name">Собственная коллекция компании</div><div class="desc">Antiplag.uz ichki hujjatlar to'plami</div><div class="tags"><span class="tag t-inner">Ichki baza</span></div></div>
</div>
</div>
<div class="cat-header"><div class="cat-line"></div><span class="cat-label">Yangi bepul internet manbalari</span><div class="cat-line"></div></div>
<div class="grid">
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M9 13h6M9 17h6"/></svg></div>
<div class="body"><div class="name">consultant.ru</div><div class="desc">Rossiya qonunchiligining elektron bazasi</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-legal">Huquqiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div>
<div class="body"><div class="name">kremlin.ru</div><div class="desc">Rossiya prezidenti farmonlari va rasmiy qonunlar</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-legal">Huquqiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div>
<div class="body"><div class="name">pravo.gov.ru</div><div class="desc">Rossiya rasmiy huquqiy hujjatlar nashriyoti portali</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-legal">Huquqiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#FAEEDA"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#854F0B" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M9 13h6M9 17h6"/></svg></div>
<div class="body"><div class="name">docs.cntd.ru</div><div class="desc">Texnik normalar va standartlar hujjatlari bazasi</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-legal">Standartlar</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg></div>
<div class="body"><div class="name">rumc.mininuniver.ru</div><div class="desc">Minin universiteti adaptiv ta'lim dasturlari resursi</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-sci">Ta'lim</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg></div>
<div class="body"><div class="name">moodle.kstu.ru</div><div class="desc">KSTU universiteti Moodle platformasi o'quv resurslari</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-sci">Ta'lim</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E6F1FB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#185FA5" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><line x1="16" y1="13" x2="8" y2="13"/></svg></div>
<div class="body"><div class="name">freereferats.ru</div><div class="desc">Dissertatsiya avtoreferatlari ochiq to'plami (PDF)</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-sci">Ilmiy</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div>
<div class="body"><div class="name">ktzszmoik.gov.by</div><div class="desc">Belarus nogironlarni ijtimoiy himoya qilish davlat portali</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-int">Internet</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#F1EFE8"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#5F5E5A" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 3H8M12 3v4"/></svg></div>
<div class="body"><div class="name">bizlog.ru</div><div class="desc">Iqtisodiy-boshqaruv terminologiyasi va izohli lug'at</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-int">Internet</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#E1F5EE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0F6E56" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div>
<div class="body"><div class="name">disabilityartsinternational.org</div><div class="desc">Nogironlik va madaniyat bo'yicha xalqaro resurs</div><div class="tags"><span class="tag t-free">Bepul</span><span class="tag t-int">Xalqaro</span></div></div>
</div>
</div>
<div class="cat-header"><div class="cat-line"></div><span class="cat-label">Yordamchi modullar</span><div class="cat-line"></div></div>
<div class="grid">
<div class="card">
<div class="icon" style="background:#EEEDFE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#534AB7" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div>
<div class="body"><div class="name">Shablon iboralar</div><div class="desc">Standart kirish so'zlari, universitet nomlari va klişe iboralarni aniqlash</div><div class="tags"><span class="tag t-ai">Avtomatik</span></div></div>
</div>
<div class="card">
<div class="icon" style="background:#EEEDFE"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#534AB7" stroke-width="2"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg></div>
<div class="body"><div class="name">Iqtibos keltirish moduli</div><div class="desc">Hujjatda to'g'ri rasmiylashtirilgan iqtiboslarni avtomatik aniqlash</div><div class="tags"><span class="tag t-ai">Avtomatik</span></div></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,5 @@
import { CabinetLayout } from '@/widgets/cabinet/ui';
export default function CabinetPage() {
return <CabinetLayout />;
}

View File

@@ -75,7 +75,7 @@ function NavigationMenuTrigger({
>
{children}{' '}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
className="relative top-px ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>

View File

@@ -0,0 +1,18 @@
'use client';
import { useState } from 'react';
import type { CabinetSection } from '../types';
export const useCabinet = () => {
const [activeSection, setActiveSection] =
useState<CabinetSection>('dashboard');
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const navigate = (section: CabinetSection) => {
setActiveSection(section);
setIsSidebarOpen(false);
};
const toggleSidebar = () => setIsSidebarOpen((prev) => !prev);
return { activeSection, navigate, isSidebarOpen, toggleSidebar };
};

View File

@@ -0,0 +1,35 @@
'use client';
import { useState } from 'react';
import type { UserProfile } from '../types';
interface ProfileForm extends UserProfile {
currentPassword: string;
newPassword: string;
confirmPassword: string;
}
export const useProfile = (initial: UserProfile) => {
const [form, setForm] = useState<ProfileForm>({
...initial,
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
const [isSaving, setIsSaving] = useState(false);
const [saved, setSaved] = useState(false);
const handleChange = (field: keyof ProfileForm, value: string) => {
setForm((prev) => ({ ...prev, [field]: value }));
setSaved(false);
};
const handleSave = async () => {
setIsSaving(true);
// TODO: replace with real API call
await new Promise((res) => setTimeout(res, 800));
setIsSaving(false);
setSaved(true);
};
return { form, isSaving, saved, handleChange, handleSave };
};

View File

@@ -0,0 +1,151 @@
import type {
CabinetStats,
Payment,
PlagiatCheck,
SiCheck,
UserProfile,
} from './types';
export const MOCK_USER: UserProfile = {
name: 'Ali',
surname: 'Karimov',
email: 'ali.karimov@gmail.com',
phone: '+998 90 123 45 67',
};
export const MOCK_STATS: CabinetStats = {
total: 24,
thisMonth: 7,
discountUsed: 7,
discountTotal: 10,
balance: 0,
currency: 'UZS',
};
export const MOCK_PLAGIAT: PlagiatCheck[] = [
{
id: 1,
file: 'diplom_ishi.docx',
type: 'Diplom',
percent: 12,
date: '2026-04-01',
status: 'completed',
downloadUrl: '#',
},
{
id: 2,
file: 'kurs_ishi_2.pdf',
type: 'Kurs ishi',
percent: 8,
date: '2026-03-28',
status: 'completed',
downloadUrl: '#',
},
{
id: 3,
file: 'referat_fizika.docx',
type: 'Referat',
percent: 23,
date: '2026-03-20',
status: 'completed',
downloadUrl: '#',
},
{
id: 4,
file: 'magistr_disser.pdf',
type: 'Magistrlik',
percent: 5,
date: '2026-03-15',
status: 'completed',
downloadUrl: '#',
},
{
id: 5,
file: 'tahlil_hisobot.docx',
type: 'Hisobot',
percent: 0,
date: '2026-04-05',
status: 'pending',
},
];
export const MOCK_SI: SiCheck[] = [
{
id: 1,
file: 'kurs_ishi_1.docx',
words: 4200,
siPercent: 18,
date: '2026-04-02',
status: 'completed',
reportUrl: '#',
},
{
id: 2,
file: 'maqola_2026.pdf',
words: 1800,
siPercent: 42,
date: '2026-03-29',
status: 'completed',
reportUrl: '#',
},
{
id: 3,
file: 'tezis_draft.docx',
words: 950,
siPercent: 7,
date: '2026-03-22',
status: 'completed',
reportUrl: '#',
},
{
id: 4,
file: 'annotatsiya.txt',
words: 320,
siPercent: 0,
date: '2026-04-05',
status: 'pending',
},
];
export const MOCK_PAYMENTS: Payment[] = [
{
id: 1,
service: 'Plagiat tekshiruvi',
amount: 41200,
discount: 5200,
date: '2026-04-01',
status: 'paid',
},
{
id: 2,
service: 'SI detektor',
amount: 30000,
discount: 0,
date: '2026-03-28',
status: 'paid',
},
{
id: 3,
service: 'Plagiat tekshiruvi',
amount: 41200,
discount: 5200,
date: '2026-03-20',
status: 'paid',
},
{
id: 4,
service: 'Plagiat tekshiruvi',
amount: 41200,
discount: 0,
date: '2026-03-15',
status: 'paid',
},
{
id: 5,
service: 'SI detektor',
amount: 30000,
discount: 0,
date: '2026-04-05',
status: 'pending',
},
];

View File

@@ -0,0 +1,230 @@
// ─── Tag types ─────────────────────────────────────────────────────────────────
export type ModuleTag =
| 'Ilmiy'
| 'AI tahlil'
| 'Huquqiy'
| 'Internet'
| 'Bepul'
| 'OAV'
| 'Ichki baza'
| 'Standartlar'
| "Ta'lim"
| 'Xalqaro'
| 'Avtomatik';
export interface Module {
name: string;
desc: string;
tags: ModuleTag[];
}
export interface ModuleCategory {
label: string;
modules: Module[];
}
// ─── Tag style map ─────────────────────────────────────────────────────────────
export const TAG_STYLES: Record<ModuleTag, string> = {
Ilmiy: 'bg-blue-50 text-blue-700',
'AI tahlil': 'bg-violet-50 text-violet-700',
Huquqiy: 'bg-amber-50 text-amber-700',
Internet: 'bg-emerald-50 text-emerald-700',
Bepul: 'bg-lime-50 text-lime-700',
OAV: 'bg-stone-100 text-stone-600',
'Ichki baza': 'bg-stone-100 text-stone-600',
Standartlar: 'bg-amber-50 text-amber-700',
"Ta'lim": 'bg-blue-50 text-blue-700',
Xalqaro: 'bg-emerald-50 text-emerald-700',
Avtomatik: 'bg-violet-50 text-violet-700',
};
// ─── Module stats ──────────────────────────────────────────────────────────────
export const MODULE_STATS = {
total: 30,
freeInternet: 10,
aiModules: 5,
categories: 4,
} as const;
// ─── Module data ───────────────────────────────────────────────────────────────
export const MODULE_CATEGORIES: ModuleCategory[] = [
{
label: "Ilmiy va ta'lim bazalari",
modules: [
{
name: 'eLIBRARY.RU',
desc: "Rus va xorijiy tillardagi ilmiy maqolalarning to'liq matnlari bazasi",
tags: ['Ilmiy'],
},
{
name: 'eLIBRARY nashrlari (tarjima va qayta bayon)',
desc: 'Tarjima va parafraz qilingan maqolalarni aniqlash',
tags: ['AI tahlil'],
},
{
name: "RDK to'plami",
desc: 'Rossiya Davlat kutubxonasidan dissertatsiya va avtoreferatlar',
tags: ['Ilmiy'],
},
{
name: 'BMK dissertatsiyalari',
desc: 'Belarus milliy kutubxonasi dissertatsiyalari va avtoreferatlari',
tags: ['Ilmiy'],
},
{
name: 'IEEE',
desc: 'Xalqaro elektrotexnika va elektronika muhandislari instituti bazasi',
tags: ['Ilmiy'],
},
{
name: 'IEEE parafraz moduli',
desc: 'IEEE maqolalarining qayta bayon qilingan variantlarini aniqlash',
tags: ['AI tahlil'],
},
{
name: 'Elektron-kutubxona tizimlari',
desc: 'Book.ru, Yurait, Lans, Aybuks va boshqa ELS bazalari',
tags: ['Ilmiy'],
},
{
name: 'OTMlar halqasi',
desc: "O'zbekiston oliy ta'lim muassasalari birgalikdagi bazasi",
tags: ['Ilmiy'],
},
{
name: 'Kolleksiya NMU',
desc: "O'zbekiston milliy kutubxonasi to'plami",
tags: ['Ilmiy'],
},
],
},
{
label: 'Huquqiy va normativ bazalar',
modules: [
{
name: 'Patentlar',
desc: "SSSR, O'zbekiston, Rossiya va MDH davlatlari patentlari bazasi",
tags: ['Huquqiy'],
},
{
name: 'NBS Adilex',
desc: "O'zbekiston qonunchilik bazasi hujjatlari",
tags: ['Huquqiy'],
},
],
},
{
label: 'Internet tekshiruv modullari',
modules: [
{
name: 'Internet PLUS moduli',
desc: "Internet bo'ylab kengaytirilgan chuqur skanerlash",
tags: ['Internet'],
},
{
name: 'Internet RU parafraz',
desc: 'Rus internet segmentidagi qayta bayon qilingan qarzlar',
tags: ['Internet', 'AI tahlil'],
},
{
name: 'Internet EN parafraz',
desc: 'Ingliz internet segmentidagi qayta bayon qilingan qarzlar',
tags: ['Internet', 'AI tahlil'],
},
{
name: 'Internet RU tarjima',
desc: 'Rus internet segmentidagi tarjima qilingan qarzlar',
tags: ['Internet', 'AI tahlil'],
},
{
name: 'Internet EN tarjima',
desc: 'Ingliz internet segmentidagi tarjima qilingan qarzlar',
tags: ['Internet', 'AI tahlil'],
},
{
name: 'SMI Rossii va MDH',
desc: 'Rossiya va MDH ommaviy axborot vositalari maqolalari',
tags: ['OAV'],
},
{
name: "Kompaniyaning ichki to'plami",
desc: "Antiplag.uz ichki hujjatlar to'plami",
tags: ['Ichki baza'],
},
],
},
{
label: 'Yangi bepul internet manbalari',
modules: [
{
name: 'consultant.ru',
desc: 'Rossiya qonunchiligining elektron bazasi',
tags: ['Bepul', 'Huquqiy'],
},
{
name: 'kremlin.ru',
desc: 'Rossiya prezidenti farmonlari va rasmiy qonunlar',
tags: ['Bepul', 'Huquqiy'],
},
{
name: 'pravo.gov.ru',
desc: 'Rossiya rasmiy huquqiy hujjatlar nashriyoti portali',
tags: ['Bepul', 'Huquqiy'],
},
{
name: 'docs.cntd.ru',
desc: 'Texnik normalar va standartlar hujjatlari bazasi',
tags: ['Bepul', 'Standartlar'],
},
{
name: 'rumc.mininuniver.ru',
desc: "Minin universiteti adaptiv ta'lim dasturlari resursi",
tags: ['Bepul', "Ta'lim"],
},
{
name: 'moodle.kstu.ru',
desc: "KSTU universiteti Moodle platformasi o'quv resurslari",
tags: ['Bepul', "Ta'lim"],
},
{
name: 'freereferats.ru',
desc: "Dissertatsiya avtoreferatlari ochiq to'plami (PDF)",
tags: ['Bepul', 'Ilmiy'],
},
{
name: 'ktzszmoik.gov.by',
desc: 'Belarus nogironlarni ijtimoiy himoya qilish davlat portali',
tags: ['Bepul', 'Internet'],
},
{
name: 'bizlog.ru',
desc: "Iqtisodiy-boshqaruv terminologiyasi va izohli lug'at",
tags: ['Bepul', 'Internet'],
},
{
name: 'disabilityartsinternational.org',
desc: "Nogironlik va madaniyat bo'yicha xalqaro resurs",
tags: ['Bepul', 'Xalqaro'],
},
],
},
{
label: 'Yordamchi modullar',
modules: [
{
name: 'Shablon iboralar',
desc: "Standart kirish so'zlari, universitet nomlari va klishe iboralarni aniqlash",
tags: ['Avtomatik'],
},
{
name: 'Iqtibos keltirish moduli',
desc: "Hujjatda to'g'ri rasmiylashtirilgan iqtiboslarni avtomatik aniqlash",
tags: ['Avtomatik'],
},
],
},
];

View File

@@ -0,0 +1,55 @@
// ─── Navigation ────────────────────────────────────────────────────────────────
export type CabinetSection =
| 'dashboard'
| 'plagiat'
| 'si'
| 'payments'
| 'profile';
// ─── Domain ────────────────────────────────────────────────────────────────────
export interface UserProfile {
name: string;
surname: string;
email: string;
phone: string;
}
export interface PlagiatCheck {
id: number;
file: string;
type: string;
percent: number;
date: string;
status: 'completed' | 'pending' | 'failed';
downloadUrl?: string;
}
export interface SiCheck {
id: number;
file: string;
words: number;
siPercent: number;
date: string;
status: 'completed' | 'pending' | 'failed';
reportUrl?: string;
}
export interface Payment {
id: number;
service: string;
amount: number;
discount: number;
date: string;
status: 'paid' | 'pending' | 'failed';
}
export interface CabinetStats {
total: number;
thisMonth: number;
discountUsed: number;
discountTotal: number;
balance: number;
currency: string;
}

View File

@@ -0,0 +1,145 @@
'use client';
import React from 'react';
import {
LayoutDashboard,
FileSearch,
BrainCircuit,
CreditCard,
User,
Home,
X,
} from 'lucide-react';
import { Link } from '@/shared/config/i18n/navigation';
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 {
active: CabinetSection;
onNavigate: (section: CabinetSection) => void;
isOpen: boolean;
onClose: () => void;
userName: string;
}
// ─── Component ─────────────────────────────────────────────────────────────────
export const Sidebar: React.FC<SidebarProps> = ({
active,
onNavigate,
isOpen,
onClose,
userName,
}) => (
<>
{/* Mobile backdrop */}
{isOpen && (
<div
className="fixed inset-0 z-30 bg-black/30 backdrop-blur-sm lg:hidden"
onClick={onClose}
/>
)}
<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="flex items-center justify-between px-5 py-4 border-b border-slate-100">
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-lg bg-blue-500 flex items-center justify-center">
<span className="text-white text-xs font-bold">P</span>
</div>
<span className="font-semibold text-slate-800 text-sm">Plagat</span>
</div>
<button
onClick={onClose}
className="lg:hidden 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>
</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;
if (item.id === 'home') {
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"
>
<Icon size={17} />
<span>{item.label}</span>
</Link>
);
}
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>
</>
);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { FileSearch, BrainCircuit, ArrowRight } from 'lucide-react';
import type { CabinetSection } from '../../lib/types';
interface CtaCardsProps {
onNavigate: (section: CabinetSection) => void;
}
export const CtaCards: React.FC<CtaCardsProps> = ({ onNavigate }) => (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Plagiat */}
<button
onClick={() => onNavigate('plagiat')}
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>
</button>
{/* SI */}
<button
onClick={() => onNavigate('si')}
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-violet-500 to-violet-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">
<BrainCircuit size={72} className="text-white" />
</div>
<BrainCircuit size={26} className="text-white mb-4" />
<h3 className="text-white font-semibold text-base mb-1">SI detektor</h3>
<p className="text-violet-100 text-sm mb-4 leading-relaxed">
Matnni sun&apos;iy intellekt 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>
</button>
</div>
);

View File

@@ -0,0 +1,128 @@
import React from 'react';
import {
MODULE_CATEGORIES,
MODULE_STATS,
TAG_STYLES,
type ModuleTag,
} from '../../lib/modules';
// ─── Module stats mini-cards ───────────────────────────────────────────────────
const ModuleStats: React.FC = () => (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{[
{ value: MODULE_STATS.total, label: 'Jami modullar' },
{ value: MODULE_STATS.freeInternet, label: 'Bepul internet manbalari' },
{ value: MODULE_STATS.aiModules, label: 'AI tahlil modullari' },
{ value: MODULE_STATS.categories, label: 'Kategoriya' },
].map(({ value, label }) => (
<div
key={label}
className="bg-slate-50 border border-slate-100 rounded-xl px-4 py-3"
>
<p className="text-2xl font-bold text-slate-900 tabular-nums leading-none">
{value}
</p>
<p className="text-xs text-slate-500 mt-1">{label}</p>
</div>
))}
</div>
);
// ─── Tag badge ─────────────────────────────────────────────────────────────────
const Tag: React.FC<{ tag: ModuleTag }> = ({ tag }) => (
<span
className={`inline-block text-[10px] font-semibold px-2 py-0.5 rounded-full ${TAG_STYLES[tag]}`}
>
{tag}
</span>
);
// ─── Category divider ──────────────────────────────────────────────────────────
const CategoryHeader: React.FC<{ label: string }> = ({ label }) => (
<div className="flex items-center gap-3 my-5">
<div className="flex-1 h-px bg-slate-100" />
<span className="text-[11px] font-semibold tracking-widest text-slate-400 uppercase whitespace-nowrap">
{label}
</span>
<div className="flex-1 h-px bg-slate-100" />
</div>
);
// ─── Single module card ────────────────────────────────────────────────────────
interface ModuleCardProps {
name: string;
desc: string;
tags: ModuleTag[];
index: number;
}
const ModuleCard: React.FC<ModuleCardProps> = ({ name, desc, tags, index }) => (
<div className="bg-white border border-slate-100 rounded-xl p-4 flex items-start gap-3 hover:border-slate-200 hover:shadow-sm transition-all duration-150">
{/* Index number */}
<span className="w-6 h-6 rounded-full bg-slate-100 flex items-center justify-center text-[10px] font-semibold text-slate-500 shrink-0 mt-0.5">
{index}
</span>
<div className="min-w-0 flex-1">
<p className="text-sm font-semibold text-slate-800 leading-snug mb-1 truncate">
{name}
</p>
<p className="text-xs text-slate-500 leading-relaxed mb-2.5">{desc}</p>
<div className="flex flex-wrap gap-1.5">
{tags.map((tag) => (
<Tag key={tag} tag={tag} />
))}
</div>
</div>
</div>
);
// ─── Modules section ───────────────────────────────────────────────────────────
export const ModulesSection: React.FC = () => {
// running counter across all categories
let counter = 0;
return (
<div>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-sm font-semibold text-slate-800">
Tekshiruv modullari
</h3>
<p className="text-xs text-slate-400 mt-0.5">
Plagiat aniqlashda foydalaniladigan barcha manbalar
</p>
</div>
<span className="text-xs text-slate-400 bg-slate-100 px-2.5 py-1 rounded-lg font-medium">
{MODULE_STATS.total} ta modul
</span>
</div>
<ModuleStats />
{MODULE_CATEGORIES.map((cat) => (
<div key={cat.label}>
<CategoryHeader label={cat.label} />
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
{cat.modules.map((mod) => {
counter += 1;
return (
<ModuleCard
key={mod.name}
name={mod.name}
desc={mod.desc}
tags={mod.tags}
index={counter}
/>
);
})}
</div>
</div>
))}
</div>
);
};

View File

@@ -0,0 +1,81 @@
import React from 'react';
import { TrendingUp, Calendar, Tag, Wallet } from 'lucide-react';
import type { CabinetStats } from '../../lib/types';
// ─── Single stat card ──────────────────────────────────────────────────────────
interface StatCardProps {
icon: React.ElementType;
label: string;
value: string;
sub?: string;
iconColor: string;
iconBg: string;
}
const StatCard: React.FC<StatCardProps> = ({
icon: Icon,
label,
value,
sub,
iconColor,
iconBg,
}) => (
<div className="bg-white rounded-2xl border border-slate-100 p-5 shadow-sm hover:shadow-md transition-shadow duration-200">
<div
className={`w-10 h-10 rounded-xl flex items-center justify-center mb-4 ${iconBg}`}
>
<Icon size={18} className={iconColor} />
</div>
<p className="text-2xl font-bold text-slate-900 tabular-nums">{value}</p>
<p className="text-xs text-slate-500 mt-0.5">{label}</p>
{sub && <p className="text-[11px] text-slate-400 mt-1">{sub}</p>}
</div>
);
// ─── Grid ──────────────────────────────────────────────────────────────────────
interface StatsCardsProps {
stats: CabinetStats;
}
export const StatsCards: React.FC<StatsCardsProps> = ({ stats }) => {
const discountPct = Math.round(
(stats.discountUsed / stats.discountTotal) * 100,
);
return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
icon={TrendingUp}
label="Jami tekshiruvlar"
value={String(stats.total)}
iconColor="text-blue-600"
iconBg="bg-blue-50"
/>
<StatCard
icon={Calendar}
label="Bu oy"
value={String(stats.thisMonth)}
sub={`${stats.discountUsed}/${stats.discountTotal} ta hujjat`}
iconColor="text-emerald-600"
iconBg="bg-emerald-50"
/>
<StatCard
icon={Tag}
label="Chegirma holati"
value={`${discountPct}%`}
sub={`${stats.discountUsed}/${stats.discountTotal} ta ishlatilgan`}
iconColor="text-amber-600"
iconBg="bg-amber-50"
/>
<StatCard
icon={Wallet}
label="Balans"
value={`${stats.balance.toLocaleString()} ${stats.currency}`}
iconColor="text-violet-600"
iconBg="bg-violet-50"
/>
</div>
);
};

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { CtaCards } from './CtaCards';
import { StatsCards } from './StatsCards';
import { ModulesSection } from './ModulesSection';
import type { CabinetSection, CabinetStats } from '../../lib/types';
interface DashboardProps {
stats: CabinetStats;
onNavigate: (section: CabinetSection) => void;
userName: string;
}
export const Dashboard: React.FC<DashboardProps> = ({
stats,
onNavigate,
userName,
}) => (
<div className="space-y-6">
<div>
<h2 className="text-xl font-bold text-slate-900">
Xush kelibsiz, {userName} 👋
</h2>
<p className="text-sm text-slate-500 mt-0.5">
Shaxsiy kabinetingizga xush kelibsiz
</p>
</div>
<StatsCards stats={stats} />
<div>
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-widest mb-3">
Tezkor harakatlar
</h3>
<CtaCards onNavigate={onNavigate} />
</div>
<div className="border-t border-slate-100 pt-6">
<ModulesSection />
</div>
</div>
);

View File

@@ -0,0 +1,114 @@
'use client';
import React from 'react';
import dynamic from 'next/dynamic';
import { AnimatePresence, motion } from 'framer-motion';
import { Sidebar } from './Sidebar';
import { Dashboard } from './dashboard';
import { useCabinet } from '../lib/hooks/useCabinet';
import {
MOCK_USER,
MOCK_STATS,
MOCK_PLAGIAT,
MOCK_SI,
MOCK_PAYMENTS,
} from '../lib/mock';
import type { CabinetSection } from '../lib/types';
// ─── Lazy sections (separate JS chunks) ───────────────────────────────────────
const Skeleton = () => (
<div className="space-y-3">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-12 bg-slate-100 rounded-xl animate-pulse" />
))}
</div>
);
const PlagiatTable = dynamic(
() =>
import('./tables/PlagiatTable').then((m) => ({ default: m.PlagiatTable })),
{ loading: Skeleton },
);
const SiTable = dynamic(
() => import('./tables/SiTable').then((m) => ({ default: m.SiTable })),
{ loading: Skeleton },
);
const PaymentsTable = dynamic(
() =>
import('./tables/PaymentsTable').then((m) => ({
default: m.PaymentsTable,
})),
{ loading: Skeleton },
);
const ProfileSection = dynamic(
() => import('./profile').then((m) => ({ default: m.ProfileSection })),
{ loading: Skeleton },
);
// ─── Section switcher ──────────────────────────────────────────────────────────
function SectionContent({
section,
onNavigate,
}: {
section: CabinetSection;
onNavigate: (s: CabinetSection) => void;
}) {
switch (section) {
case 'dashboard':
return (
<Dashboard
stats={MOCK_STATS}
onNavigate={onNavigate}
userName={MOCK_USER.name}
/>
);
case 'plagiat':
return <PlagiatTable data={MOCK_PLAGIAT} />;
case 'si':
return <SiTable data={MOCK_SI} />;
case 'payments':
return <PaymentsTable data={MOCK_PAYMENTS} />;
case 'profile':
return <ProfileSection user={MOCK_USER} stats={MOCK_STATS} />;
}
}
// ─── Animation ────────────────────────────────────────────────────────────────
const FADE = {
initial: { opacity: 0, y: 10 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -6 },
transition: { duration: 0.18, ease: 'easeOut' },
} as const;
// ─── CabinetLayout ────────────────────────────────────────────────────────────
export const CabinetLayout: React.FC = () => {
const { activeSection, navigate, isSidebarOpen, toggleSidebar } =
useCabinet();
const fullName = `${MOCK_USER.name} ${MOCK_USER.surname}`;
return (
<div className="flex border-t min-h-screen">
<Sidebar
active={activeSection}
onNavigate={navigate}
isOpen={isSidebarOpen}
onClose={toggleSidebar}
userName={fullName}
/>
<div className="flex-1 flex flex-col min-w-0">
<main className="flex-1 p-4 md:p-6 lg:p-8 max-w-6xl mx-auto w-full">
<AnimatePresence mode="wait">
<motion.div key={activeSection} {...FADE}>
<SectionContent section={activeSection} onNavigate={navigate} />
</motion.div>
</AnimatePresence>
</main>
</div>
</div>
);
};

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { Tag } from 'lucide-react';
import type { CabinetStats } from '../../lib/types';
interface DiscountProgressProps {
stats: CabinetStats;
}
export const DiscountProgress: React.FC<DiscountProgressProps> = ({
stats,
}) => {
const pct = Math.round((stats.discountUsed / stats.discountTotal) * 100);
const remaining = stats.discountTotal - stats.discountUsed;
return (
<div className="bg-gradient-to-br from-amber-50 to-orange-50 border border-amber-100 rounded-2xl p-5">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-xl bg-amber-100 flex items-center justify-center">
<Tag size={15} className="text-amber-600" />
</div>
<div>
<h3 className="text-sm font-semibold text-amber-800">
Bu oyda chegirma
</h3>
<p className="text-xs text-amber-600 mt-0.5">
{remaining > 0
? `${remaining} ta hujjatdan keyin chegirma tugaydi`
: 'Bu oyda barcha chegirmalar ishlatildi'}
</p>
</div>
</div>
<span className="text-2xl font-bold text-amber-700 tabular-nums">
{stats.discountUsed}/{stats.discountTotal}
</span>
</div>
{/* Bar */}
<div className="h-2 bg-amber-100 rounded-full overflow-hidden mb-2.5">
<div
className="h-full bg-amber-400 rounded-full transition-[width] duration-700 ease-out"
style={{ width: `${pct}%` }}
/>
</div>
<div className="flex justify-between text-[11px] text-amber-600">
<span>{stats.discountUsed} ta ishlatildi</span>
<span>{pct}%</span>
</div>
</div>
);
};

View File

@@ -0,0 +1,170 @@
'use client';
import React from 'react';
import { User, Mail, Phone, Lock, Save, CheckCircle } from 'lucide-react';
import { useProfile } from '../../lib/hooks/useProfile';
import type { UserProfile } from '../../lib/types';
// ─── Input field ───────────────────────────────────────────────────────────────
interface InputFieldProps {
label: string;
value: string;
onChange: (val: string) => void;
type?: string;
icon: React.ElementType;
placeholder?: string;
}
const InputField: React.FC<InputFieldProps> = ({
label,
value,
onChange,
type = 'text',
icon: Icon,
placeholder,
}) => (
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1.5">
{label}
</label>
<div className="relative">
<Icon
size={14}
className="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"
/>
<input
type={type}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="
w-full pl-9 pr-4 py-2.5 text-sm rounded-xl
border border-slate-200 bg-white
text-slate-800 placeholder:text-slate-300
focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400
transition-all duration-150
"
/>
</div>
</div>
);
// ─── Form ──────────────────────────────────────────────────────────────────────
interface ProfileFormProps {
initial: UserProfile;
}
export const ProfileForm: React.FC<ProfileFormProps> = ({ initial }) => {
const { form, isSaving, saved, handleChange, handleSave } =
useProfile(initial);
return (
<div className="space-y-5">
{/* Personal info */}
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6">
<h3 className="text-sm font-semibold text-slate-800 mb-4">
Shaxsiy ma&apos;lumotlar
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<InputField
label="Ism"
value={form.name}
onChange={(v) => handleChange('name', v)}
icon={User}
placeholder="Ali"
/>
<InputField
label="Familiya"
value={form.surname}
onChange={(v) => handleChange('surname', v)}
icon={User}
placeholder="Karimov"
/>
<InputField
label="Email"
value={form.email}
onChange={(v) => handleChange('email', v)}
type="email"
icon={Mail}
placeholder="ali@example.com"
/>
<InputField
label="Telefon"
value={form.phone}
onChange={(v) => handleChange('phone', v)}
icon={Phone}
placeholder="+998 90 123 45 67"
/>
</div>
</div>
{/* Password */}
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6">
<h3 className="text-sm font-semibold text-slate-800 mb-4">
Parol o&apos;zgartirish
</h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<InputField
label="Joriy parol"
value={form.currentPassword}
onChange={(v) => handleChange('currentPassword', v)}
type="password"
icon={Lock}
placeholder="••••••••"
/>
<InputField
label="Yangi parol"
value={form.newPassword}
onChange={(v) => handleChange('newPassword', v)}
type="password"
icon={Lock}
placeholder="••••••••"
/>
<InputField
label="Tasdiqlash"
value={form.confirmPassword}
onChange={(v) => handleChange('confirmPassword', v)}
type="password"
icon={Lock}
placeholder="••••••••"
/>
</div>
</div>
{/* Save */}
<div className="flex justify-end">
<button
onClick={handleSave}
disabled={isSaving}
className={`
flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-semibold text-white
transition-all duration-200
${
isSaving
? 'bg-blue-300 cursor-not-allowed'
: saved
? 'bg-emerald-500 hover:bg-emerald-600'
: 'bg-blue-500 hover:bg-blue-600 active:scale-95 shadow-md hover:shadow-lg'
}
`}
>
{saved ? (
<>
<CheckCircle size={15} /> Saqlandi
</>
) : isSaving ? (
<>
<span className="w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" />
Saqlanmoqda
</>
) : (
<>
<Save size={15} /> Saqlash
</>
)}
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { DiscountProgress } from './DiscountProgress';
import { ProfileForm } from './ProfileForm';
import type { CabinetStats, UserProfile } from '../../lib/types';
interface ProfileSectionProps {
user: UserProfile;
stats: CabinetStats;
}
export const ProfileSection: React.FC<ProfileSectionProps> = ({
user,
stats,
}) => (
<div className="space-y-6">
<div>
<h2 className="text-xl font-bold text-slate-900">Profil</h2>
<p className="text-sm text-slate-500 mt-0.5">
Ma&apos;lumotlaringizni boshqaring
</p>
</div>
<DiscountProgress stats={stats} />
<ProfileForm initial={user} />
</div>
);

View File

@@ -0,0 +1,101 @@
import React from 'react';
import { CheckCircle, Clock, XCircle } from 'lucide-react';
import type { Payment } from '../../lib/types';
// ─── Helpers ───────────────────────────────────────────────────────────────────
const STATUS_MAP = {
paid: {
label: "To'landi",
icon: CheckCircle,
cls: 'text-emerald-600 bg-emerald-50',
},
pending: {
label: 'Kutilmoqda',
icon: Clock,
cls: 'text-amber-600 bg-amber-50',
},
failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' },
} as const;
// ─── Component ─────────────────────────────────────────────────────────────────
interface PaymentsTableProps {
data: Payment[];
}
export const PaymentsTable: React.FC<PaymentsTableProps> = ({ data }) => (
<div className="space-y-4">
<div>
<h2 className="text-xl font-bold text-slate-900">
To&apos;lovlar tarixi
</h2>
<p className="text-sm text-slate-500 mt-0.5">
{data.length} ta to&apos;lov
</p>
</div>
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-slate-50 border-b border-slate-100">
{['#', 'Xizmat', 'Summa', 'Chegirma', 'Sana', 'Holat'].map(
(h) => (
<th
key={h}
className="text-left px-5 py-3 text-[11px] font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap"
>
{h}
</th>
),
)}
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{data.map((row) => {
const s = STATUS_MAP[row.status];
const Icon = s.icon;
return (
<tr
key={row.id}
className="hover:bg-slate-50/60 transition-colors"
>
<td className="px-5 py-3.5 text-slate-400 font-mono text-xs">
{String(row.id).padStart(2, '0')}
</td>
<td className="px-5 py-3.5 text-slate-800 font-medium whitespace-nowrap">
{row.service}
</td>
<td className="px-5 py-3.5 text-slate-800 font-semibold tabular-nums whitespace-nowrap">
{row.amount.toLocaleString()} UZS
</td>
<td className="px-5 py-3.5 tabular-nums whitespace-nowrap">
{row.discount > 0 ? (
<span className="text-emerald-600 font-medium">
-{row.discount.toLocaleString()} UZS
</span>
) : (
<span className="text-slate-300"></span>
)}
</td>
<td className="px-5 py-3.5 text-slate-500 whitespace-nowrap">
{row.date}
</td>
<td className="px-5 py-3.5">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium ${s.cls}`}
>
<Icon size={12} />
{s.label}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,128 @@
import React from 'react';
import { Download, Clock, CheckCircle, XCircle } from 'lucide-react';
import type { PlagiatCheck } from '../../lib/types';
// ─── Helpers ───────────────────────────────────────────────────────────────────
const STATUS_MAP = {
completed: {
label: 'Yakunlandi',
icon: CheckCircle,
cls: 'text-emerald-600 bg-emerald-50',
},
pending: {
label: 'Kutilmoqda',
icon: Clock,
cls: 'text-amber-600 bg-amber-50',
},
failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' },
} as const;
const PercentBadge: React.FC<{ value: number }> = ({ value }) => {
const cls =
value < 15
? 'text-emerald-700 bg-emerald-50'
: value < 30
? 'text-amber-700 bg-amber-50'
: 'text-red-700 bg-red-50';
return (
<span
className={`inline-block px-2 py-0.5 rounded-md text-xs font-semibold ${cls}`}
>
{value}%
</span>
);
};
// ─── Component ─────────────────────────────────────────────────────────────────
interface PlagiatTableProps {
data: PlagiatCheck[];
}
export const PlagiatTable: React.FC<PlagiatTableProps> = ({ data }) => (
<div className="space-y-4">
<div>
<h2 className="text-xl font-bold text-slate-900">Plagiat tekshiruvlar</h2>
<p className="text-sm text-slate-500 mt-0.5">
{data.length} ta tekshiruv
</p>
</div>
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-slate-50 border-b border-slate-100">
{['#', 'Fayl', 'Turi', '%', 'Sana', 'Holat', 'Yuklab olish'].map(
(h) => (
<th
key={h}
className="text-left px-5 py-3 text-[11px] font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap last:text-center"
>
{h}
</th>
),
)}
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{data.map((row) => {
const s = STATUS_MAP[row.status];
const Icon = s.icon;
return (
<tr
key={row.id}
className="hover:bg-slate-50/60 transition-colors"
>
<td className="px-5 py-3.5 text-slate-400 font-mono text-xs">
{String(row.id).padStart(2, '0')}
</td>
<td className="px-5 py-3.5">
<span className="text-slate-800 font-medium max-w-[150px] truncate block">
{row.file}
</span>
</td>
<td className="px-5 py-3.5 text-slate-500 whitespace-nowrap">
{row.type}
</td>
<td className="px-5 py-3.5">
{row.status === 'pending' ? (
<span className="text-slate-300 text-xs"></span>
) : (
<PercentBadge value={row.percent} />
)}
</td>
<td className="px-5 py-3.5 text-slate-500 whitespace-nowrap">
{row.date}
</td>
<td className="px-5 py-3.5">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium ${s.cls}`}
>
<Icon size={12} />
{s.label}
</span>
</td>
<td className="px-5 py-3.5 text-center">
{row.downloadUrl ? (
<a
href={row.downloadUrl}
className="inline-flex items-center justify-center w-8 h-8 rounded-lg text-slate-400 hover:text-blue-600 hover:bg-blue-50 transition-colors"
aria-label="Yuklab olish"
>
<Download size={15} />
</a>
) : (
<span className="text-slate-200 text-xs"></span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,128 @@
import React from 'react';
import { FileText, Clock, CheckCircle, XCircle } from 'lucide-react';
import type { SiCheck } from '../../lib/types';
// ─── Helpers ───────────────────────────────────────────────────────────────────
const STATUS_MAP = {
completed: {
label: 'Yakunlandi',
icon: CheckCircle,
cls: 'text-emerald-600 bg-emerald-50',
},
pending: {
label: 'Kutilmoqda',
icon: Clock,
cls: 'text-amber-600 bg-amber-50',
},
failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' },
} as const;
const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => {
const cls =
value < 20
? 'text-emerald-700 bg-emerald-50'
: value < 40
? 'text-amber-700 bg-amber-50'
: 'text-red-700 bg-red-50';
return (
<span
className={`inline-block px-2 py-0.5 rounded-md text-xs font-semibold ${cls}`}
>
{value}%
</span>
);
};
// ─── Component ─────────────────────────────────────────────────────────────────
interface SiTableProps {
data: SiCheck[];
}
export const SiTable: React.FC<SiTableProps> = ({ data }) => (
<div className="space-y-4">
<div>
<h2 className="text-xl font-bold text-slate-900">SI detektor</h2>
<p className="text-sm text-slate-500 mt-0.5">
{data.length} ta tekshiruv
</p>
</div>
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-slate-50 border-b border-slate-100">
{['#', 'Fayl', "So'z", 'SI%', 'Sana', 'Holat', 'Hisobot'].map(
(h) => (
<th
key={h}
className="text-left px-5 py-3 text-[11px] font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap last:text-center"
>
{h}
</th>
),
)}
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{data.map((row) => {
const s = STATUS_MAP[row.status];
const Icon = s.icon;
return (
<tr
key={row.id}
className="hover:bg-slate-50/60 transition-colors"
>
<td className="px-5 py-3.5 text-slate-400 font-mono text-xs">
{String(row.id).padStart(2, '0')}
</td>
<td className="px-5 py-3.5">
<span className="text-slate-800 font-medium max-w-[150px] truncate block">
{row.file}
</span>
</td>
<td className="px-5 py-3.5 text-slate-500 tabular-nums">
{row.words.toLocaleString()}
</td>
<td className="px-5 py-3.5">
{row.status === 'pending' ? (
<span className="text-slate-300 text-xs"></span>
) : (
<SiPercentBadge value={row.siPercent} />
)}
</td>
<td className="px-5 py-3.5 text-slate-500 whitespace-nowrap">
{row.date}
</td>
<td className="px-5 py-3.5">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium ${s.cls}`}
>
<Icon size={12} />
{s.label}
</span>
</td>
<td className="px-5 py-3.5 text-center">
{row.reportUrl ? (
<a
href={row.reportUrl}
className="inline-flex items-center justify-center w-8 h-8 rounded-lg text-slate-400 hover:text-violet-600 hover:bg-violet-50 transition-colors"
aria-label="Hisobot"
>
<FileText size={15} />
</a>
) : (
<span className="text-slate-200 text-xs"></span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);

View File

@@ -8,9 +8,9 @@ const Footer = () => {
// { name: 'Contact', href: '/contact' },
// ];
return (
<section className="py-10">
<div className="custom-container">
<div className=" flex flex-col justify-between gap-4 border-t pt-8 text-center text-sm font-medium text-muted-foreground lg:flex-row lg:items-center lg:text-left">
<section className="py-5">
<div className="max-w-6xl w-full mx-auto">
<div className=" flex flex-col justify-between gap-4 text-center text-sm font-medium text-muted-foreground lg:flex-row lg:items-center lg:text-left">
<p>{t('copyright', { year: new Date().getFullYear() })}</p>
<ul className="flex justify-center gap-4 lg:justify-start">
<li className="hover:text-primary">

View File

@@ -6,6 +6,7 @@ import {
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from '@/shared/ui/navigation-menu';
import SubMenuLink from './SubMenuLink';
@@ -30,7 +31,7 @@ function AuthButtons() {
};
const userItem = [
{ title: t('profile'), url: '/profile', icon: User },
{ title: t('profile'), url: '/cabinet', icon: User },
{ title: t('logout'), url: '/', icon: LogOut },
];
@@ -64,10 +65,11 @@ function AuthButtons() {
<div className="sm:flex hidden">
<ChangeLang />
</div>
<NavigationMenu>
<NavigationMenu viewport={true}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-xl">
{localUser.name} {localUser.surname}
<NavigationMenuTrigger className="text-lg">
{localUser.name}
</NavigationMenuTrigger>
<NavigationMenuContent className="bg-popover text-popover-foreground">
{userItem.map((subItem) => (
@@ -76,11 +78,19 @@ function AuthButtons() {
key={subItem.title}
className="w-80"
>
<SubMenuLink logOut={clearTokens} item={subItem} />
<SubMenuLink
logOut={() => {
if (subItem.url !== '/cabinet') {
clearTokens();
}
}}
item={subItem}
/>
</NavigationMenuLink>
))}
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
);

View File

@@ -22,7 +22,7 @@ const Navbar = () => {
const menu = getMenu(t);
return (
<section className="py-4 flex items-center justify-center w-full ">
<section className="py-1 flex items-center justify-center w-full ">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
{/* Desktop Menu */}
<nav className="justify-between items-center flex max-sm:flex-col gap-5">
@@ -34,10 +34,10 @@ const Navbar = () => {
>
<Image
src={Logo_image}
className="min-h-4"
className="min-h-2"
alt="Anti-Plagiat.uz"
width={200}
height={50}
width={140}
height={10}
/>
</Link>
<div className="flex sm:hidden items-center justify-center">