11.15-bob: Dashboard loyiha — layout, auth
11-QISM — Frontend: React · 15-mavzu
1. Kirish va motivatsiya
Shu paytgacha 11-QISM'da React'ning har bir qismini alohida o'rgandik: komponent, state, hooklar, code splitting, routing, forma, performance, ilg'or naqshlar, TypeScript. Endi ularning hammasini bitta real loyihada birlashtiramiz — professional admin dashboard (boshqaruv paneli). Bu — sizning portfoliongizning markaziy loyihasi bo'lishi mumkin, chunki dashboard — eng ko'p talab qilinadigan frontend ish turlaridan biri: har SaaS, e-commerce, CRM, ichki tizimda admin paneli bor. Va u shu paytgacha o'rgangan barcha ko'nikmani bir joyda sinaydi.
Dashboard — alohida xususiyatlar to'plami emas, balki arxitektura masalasi: qismlar bir-biriga qanday ulanadi? Layout (sidebar, header) qanday tashkil qilinadi? Login'dan keyin foydalanuvchi qanday "eslab qolinadi"? Himoyalangan sahifalarga ruxsatsiz kirishni qanday to'xtatamiz? Server bilan qanday gaplashamiz (token, xato, qayta yuklash)? CRUD (yaratish-o'qish-yangilash-o'chirish) sahifalar qanday bir xil naqshda quriladi? Bu bob aynan shu arxitektura qarorlarini o'rgatadi — ya'ni "junior" (ishlaydigan, lekin tartibsiz) va "senior" (tartibli, kengaytiriladigan, ishonchli) dashboard o'rtasidagi farqni.
Bu bob: dashboard arxitekturasi (layout, routing tuzilishi), layout pattern (sidebar + header + Outlet — 11.9), responsive (mobil sidebar), autentifikatsiya oqimi (login token protected), auth state (Context + persist), protected routes (+ rol — 11.9), API qatlami (axios instance + interceptors — token, refresh), ma'lumot olish naqshi (loading/error/empty), CRUD sahifa naqshi (ro'yxat/yaratish/tahrir/o'chirish), bildirishnomalar (toast), mavzu (dark/light), va production tayyorlik (a11y, error boundary, code splitting). Bundan tashqari: token saqlash xavfsizligi (localStorage vs httpOnly cookie — 2.11), access/refresh token va refresh interceptor (queue — 2.12), role-based UI (menu/tugma rol bo'yicha — 2.13), server state (TanStack Query — kesh/invalidate/optimistic — 2.14), ma'lumot jadvali (pagination/sort/filter/search — 2.15), hamda breadcrumb, chart, i18n va UI kutubxona tanlovi 2.16-bob. Bu mavzular to'liq, integratsion va amaliy holatda ochiladi.
O'xshatish: Dashboard qurish — bu ofis binosini loyihalashtirish. Shu paytgacha biz alohida materiallar (g'isht — komponent, sim — hook, eshik — routing) bilan ishladik. Endi ulardan butun bino quramiz. Layout — bu binoning doimiy karkasi (asosiy devorlar, koridor, lift — sidebar/header) — har qavatda (sahifada) bir xil; faqat xona ichi (Outlet) o'zgaradi. Auth — bu binoning xavfsizlik tizimi: kirishda propusk (login token), har eshikda tekshiruv (protected route), va propuskni yo'qotmaslik (persist — sahifani yangilaganda eslab qolish). API qatlami — bu binoning markaziy aloqa punkti (har so'rovga avtomatik propusk qo'shadi — interceptor). Yaxshi loyihalangan bino — yangi xona qo'shish oson (CRUD naqshi), bir devor buzilsa butun bino qulamaydi (error boundary), va har kim (jumladan nogironlar — a11y) erkin yura oladi.
Nega muhim?
- Eng ko'p ish — admin dashboard har turdagi ilovada bor (SaaS, e-commerce, CRM); frontend ish bozorida eng keng.
- Arxitektura sinovi — barcha ko'nikma (routing, auth, forma, state) bir loyihada birlashadi.
- Portfolio — to'liq, real dashboard — eng kuchli portfolio loyihasi (intervyuda ko'rsatasiz).
- Naqshlar — layout, auth, CRUD — bir marta o'rganib, har loyihada qayta ishlatasiz.
2. Nazariya — chuqur tushuntirish
2.1. Dashboard arxitekturasi va loyiha tuzilishi
DASHBOARD ARXITEKTURASI (qatlamlar):
┌─────────────────────────────────────────────────────────┐
│ ROUTING 11.9-bob — public / protected / admin sahifalar │
│ │
│ LAYOUT — sidebar + header + Outlet (umumiy karkas — 2.3) │
│ │
│ SAHIFALAR — Dashboard, Users, Products, Settings... (CRUD)│
│ │
│ STATE — auth (Context), server (Query — 12.4), UI (local) │
│ │
│ API QATLAMI — axios instance + interceptors (token — 2.8) │
└─────────────────────────────────────────────────────────┘
LOYIHA TUZILISHI (feature-based — 11.3: 2.9):
src/
├── app/ App, router, providers (global setup)
├── components/ umumiy UI (Layout, Sidebar, Button, Table, Modal)
├── features/ auth/, users/, products/ (har feature: components+hooks+api)
├── lib/ api (axios), utils
├── hooks/ umumiy hooklar (useToggle, useMediaQuery)
└── types/ umumiy turlar
Dashboard — qatlamlar (routinglayoutsahifastateAPI); har biri aniq vazifali
Feature-based tuzilish (katta loyiha — bog'liq narsalar birga — 11.3: 2.9, 9.3)Dashboard arxitekturasi — qatlamlarning aniq ajratilishi. Yuqoridan pastga: routing (public/protected/admin sahifalar — 11.9) layout (sidebar + header + Outlet — umumiy karkas — 2.3) sahifalar (Dashboard, Users, Products — CRUD) state (auth — Context; server ma'lumoti — Query — 12.4; UI holati — local) API qatlami (axios instance + interceptors — token — 2.8). Har qatlam aniq vazifaga ega va boshqalari bilan aniq interfeys orqali gaplashadi. Loyiha tuzilishi (feature-based — 11.3: 2.9, 9.3 Clean Architecture ruhi):
app/(global setup — App, router, providers),components/(umumiy UI — Layout, Sidebar, Button, Table, Modal),features/(auth/,users/,products/— har feature o'z komponentlari, hooklari, API'sini birga saqlaydi),lib/(api — axios, utils),hooks/(umumiy hooklar),types/(umumiy turlar). Ikki tamoyil: (1) dashboard — aniq qatlamlar (routinglayoutsahifastateAPI) — har biri mustaqil, sinaladigan, almashtiriladigan; (2) feature-based tuzilish katta loyihada yaxshi masshtablanadi (bog'liq narsalar birga — bir feature'ni topish/o'zgartirish/o'chipish oson). Bu — junior va senior dashboard'ning asosiy farqi (tartibli arxitektura).
2.2. Layout pattern — sidebar, header, Outlet
LAYOUT — barcha sahifada umumiy karkas (sidebar + header), faqat kontent o'zgaradi (11.9: 2.7):
┌──────────┬──────────────────────────────────────┐
│ │ HEADER (qidiruv, profil, mavzu) │
│ SIDEBAR ├──────────────────────────────────────┤
│ (menyu, │ │
│ navigat-│ <Outlet /> sahifa kontenti │
│ siya) │ (Dashboard / Users / Products...) │
│ │ │
└──────────┴──────────────────────────────────────┘
function DashboardLayout() {
return (
<div className="layout">
<Sidebar /> {/* navigatsiya (NavLink — 11.9: 2.5) */}
<div className="main">
<Header /> {/* qidiruv, profil, mavzu */}
<main className="content">
<Outlet /> {/* sahifa shu yerda (11.9: 2.7) */}
</main>
</div>
</div>
);
}
Layout — bir marta (sidebar/header takrorlanmaydi); Outlet — sahifaga qarab o'zgaradi
Nested route: <Route element={<DashboardLayout/>}> ichida bola sahifalar (11.9: 2.7)Layout pattern — dashboard'ning karkasi. Layout barcha sahifada umumiy qism (sidebar + header)ni beradi, faqat asosiy kontent (Outlet) sahifaga qarab o'zgaradi (11.9: 2.7 — nested routes). Tipik dashboard layout: chap tomonda sidebar (navigatsiya menyusi — NavLink bilan — 11.9: 2.5), tepada header (qidiruv, foydalanuvchi profili, mavzu almashtirgich, bildirishnomalar), va o'rtada
<Outlet />(sahifa kontenti — Dashboard, Users, Products almashadi).DashboardLayoutkomponenti shu strukturani beradi va<Outlet />o'rniga URL'ga mos sahifa render bo'ladi. Routing'da:<Route element={<DashboardLayout/>}>ichida bola sahifalar (<Route path="/dashboard">,<Route path="/users">) — bularning hammasi bir xil layout ichida render bo'ladi. Ikki nuqta: (1) layout — bir marta yoziladi, sidebar/header takrorlanmaydi (har sahifada qayta yozish kerak emas — DRY); sahifalar almashganda faqat Outlet ichi o'zgaradi (sidebar/header qayta render bo'lmaydi — performance — 11.11). (2) Bu nested routes + Outlet naqshining amaliy qo'llanishi (11.9: 2.7). Layout — dashboard'ning vizual va navigatsion poydevori.
2.3. Responsive layout — mobil sidebar
RESPONSIVE — desktop'da sidebar doim ko'rinadi; mobilda yashirin (toggle bilan ochiladi):
DESKTOP (≥768px): MOBIL (<768px):
┌────┬──────────┐ ┌──────────────┐
│side│ kontent │ │ header │ sidebar yashirin
│bar │ │ ├──────────────┤
│ │ │ │ kontent │ bossa sidebar "ustidan" ochiladi
└────┴──────────┘ └──────────────┘
function DashboardLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const isMobile = useMediaQuery("(max-width: 768px)"); // 11.7 custom hook
return (
<div className="layout">
<Sidebar open={!isMobile || sidebarOpen} onClose={() => setSidebarOpen(false)} />
{isMobile && sidebarOpen && <div className="overlay" onClick={() => setSidebarOpen(false)} />}
<div className="main">
<Header onMenuClick={() => setSidebarOpen(true)} showMenu={isMobile} />
<main><Outlet /></main>
</div>
</div>
);
}
useMediaQuery 11.7-bob — mobil/desktop aniqlash; mobilda sidebar overlay (ustidan)
CSS (1.5 responsive) + JS holat (sidebarOpen) — birga (mobil UX)Responsive layout — dashboard mobil va desktopda turlicha ko'rinadi. Desktop (≥768px): sidebar doim ko'rinadi (chap tomonda qotgan). Mobil (<768px): sidebar yashirin, header'dagi "" (hamburger) tugmasi bosilganda kontent ustidan ochiladi (overlay bilan), tashqariga yoki havolaga bossa yopiladi. Buni amalga oshirish:
useMediaQuery("(max-width: 768px)")(11.7 custom hook) bilan mobil/desktop aniqlanadi,sidebarOpenstate bilan mobil sidebar boshqariladi. Mobilda sidebar ochiq bo'lganda — yarim shaffof overlay (orqa fon — bossa yopiladi). Header'da mobilda "" tugmasi ko'rsatiladi (showMenu={isMobile}). Ikki nuqta: (1)useMediaQuery11.7-bob — JavaScript'da responsive shartni tekshirish (CSS bilan birga ishlaydi — 1.5); CSS layout o'lchamini (sidebar kengligi, grid)ni boshqaradi, JS esa xulqni (mobil sidebar ochiq/yopiq); (2) mobil sidebar — overlay ustidan ochiladi (kontentni siljitmasdan), havolaga bossa avtomatik yopiladi (UX). Responsive layout — zamonaviy dashboard'ning majburiy talabi (foydalanuvchilar har xil qurilmada kiradi).
2.4. Autentifikatsiya oqimi — login token protected
AUTH OQIMI (to'liq jarayon — 5.15, 5.16 backend bilan):
1. LOGIN — foydalanuvchi email/parol kiritadi backend'ga so'rov (POST /auth/login)
2. TOKEN — backend access token (+ refresh token) qaytaradi frontend SAQLAYDI
3. SAQLASH — token localStorage / cookie'da (sahifa yangilaganda eslab qolish — 2.6)
4. HAR SO'ROV — token Authorization header'da yuboriladi (interceptor — 2.8)
5. PROTECTED — token bor himoyalangan sahifa; yo'q login'ga 2.7-bob
6. REFRESH — access token muddati tugasa refresh token bilan yangilash 2.8-bob
7. LOGOUT — token o'chiriladi login'ga
┌────────────────────────────────────────────────────────────┐
│ login token (saqla) har so'rovga token protected │
│ token tugasa refresh; logout token o'chir │
└────────────────────────────────────────────────────────────┘
Frontend auth — token boshqaruvi (saqlash, yuborish, yangilash, o'chirish)
HAQIQIY tekshiruv backend'da (frontend faqat token uzatadi + UX — 5.16, 14)Autentifikatsiya oqimi — dashboard'ning xavfsizlik yuragi (5.15, 5.16 backend bilan). To'liq jarayon: (1) login — foydalanuvchi email/parol kiritadi, frontend backend'ga
POST /auth/loginyuboradi; (2) token — backend access token (+ refresh token) qaytaradi; (3) saqlash — frontend tokenni saqlaydi (localStorage yoki cookie — sahifani yangilaganda foydalanuvchi "eslab qolish" uchun — 2.6); (4) har so'rov — token avtomatikAuthorization: Bearer <token>header'da yuboriladi (interceptor — 2.8); (5) protected — token bor bo'lsa himoyalangan sahifaga ruxsat, yo'q bo'lsa login'ga 2.7-bob; (6) refresh — access token muddati tugasa, refresh token bilan yangi access token olinadi (foydalanuvchi qayta login qilmacdan — 2.8); (7) logout — token o'chiriladi, login'ga. Ikki muhim nuqta: (1) frontend auth — token boshqaruvi (saqlash, yuborish, yangilash, o'chirish); (2) haqiqiy autentifikatsiya tekshiruvi backend'da (frontend faqat token uzatadi va UX'ni boshqaradi — har kim frontend kodni ko'ra/o'zgartira oladi, shuning uchun himoyada faqat unga tayanib bo'lmaydi — 5.16, 14-QISM). Bu oqim — har auth'li ilovaning standart skeleti.
2.5. Auth state va persist — Context + saqlash
AUTH STATE — foydalanuvchi holatini butun ilovaga ulashish (Context — 11.5: 2.11):
interface AuthContextType {
user: User | null;
token: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
isLoading: boolean;
}
PERSIST (sahifani yangilaganda eslab qolish — 11.7 useLocalStorage):
- token localStorage'da saqlanadi (sahifa yangilansa — o'qiladi)
- mount'da: token bor foydalanuvchini tiklash (GET /auth/me yoki token'dan)
function AuthProvider({ children }) {
const [token, setToken] = useLocalStorage<string | null>("token", null); // 11.7
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => { // mount'da — token bor bo'lsa user'ni tikla
if (token) fetchMe().then(setUser).finally(() => setIsLoading(false));
else setIsLoading(false);
}, [token]);
// login/logout funksiyalari...
}
Auth state — Context (butun ilova ulashadi); token persist (localStorage — 11.7)
isLoading — boshlang'ich tekshiruv paytida (token bor, lekin user hali yuklanmagan — 2.7)Auth state va persist — foydalanuvchi holatini butun ilovaga ulashish va eslab qolish. Auth state — Context (11.5: 2.11) orqali ulashiladi:
interface AuthContextType { user, token, login, logout, isLoading }— butun ilovauseAuth()bilan foydalanuvchini, login/logout funksiyalarini oladi (11.7, 11.14: 2.13 — type-safe). Persist (sahifani yangilaganda eslab qolish — 11.7useLocalStorage): tokenlocalStorageda saqlanadi, shuning uchun foydalanuvchi sahifani yangilasa yoki qayta ochsa, token o'qiladi va u tizimda qoladi (qayta login qilmacdan). Mount'da: token bor bo'lsa, foydalanuvchi ma'lumotini tiklash (GET /auth/meyoki token'dan) — chunki sahifa yangilanganda React state (user) yo'qoladi, lekin token localStorage'da qoladi undan user qayta olinadi.isLoadingmuhim — boshlang'ich tekshiruv paytida (token bor, lekin user hali yuklanmagan) — protected route bu paytda "yuklanmoqda" ko'rsatishi kerak (login'ga yo'naltirmasdan — 2.7). Ikki nuqta: (1) auth state — Context (butun ilova), token — persist (localStorage — 11.7); (2)isLoading— boshlang'ich auth tekshiruvi paytida (aks holda token bor foydalanuvchi sahifani yangilaganda bir zumga login'ga otilib ketadi — yomon UX). Bu — ishonchli auth state boshqaruvi.
2.6. Protected routes va rol — himoya
PROTECTED ROUTE — token/user bor ruxsat; yo'q login (11.9: 2.12 — auth bilan):
function ProtectedRoute() {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <FullPageSpinner />; // boshlang'ich tekshiruv (2.5 — login'ga otma)
if (!user) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
return <Outlet />; // himoyalangan bola sahifalar (11.9: 2.7)
}
ROL BILAN (RBAC — 5.17 frontend ko'rinishi):
function RoleRoute({ allowedRoles }: { allowedRoles: string[] }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
if (!allowedRoles.includes(user.role)) return <Navigate to="/403" replace />; // ruxsat yo'q
return <Outlet />;
}
// Routing:
<Route element={<ProtectedRoute />}> {/* hammasi himoyalangan */}
<Route element={<DashboardLayout />}> {/* layout */}
<Route path="/dashboard" element={<Dashboard />} />
<Route element={<RoleRoute allowedRoles={["admin"]} />}> {/* faqat admin */}
<Route path="/users" element={<Users />} />
</Route>
</Route>
</Route>
ProtectedRoute — Outlet bilan (guruh himoya); RoleRoute — rol tekshiruvi (RBAC)
isLoading tekshiruvi — boshlang'ich auth paytida login'ga otilib ketmaslik (2.5)Protected routes va rol — himoya qatlami (11.9: 2.12 — auth bilan to'ldirilgan).
ProtectedRoute—useAuth()danuservaisLoadingni oladi: agarisLoading(boshlang'ich tekshiruv davom etmoqda — 2.5) — spinner; agaruseryo'q — login'ga yo'naltiriladi (vafromsaqlanadi — qayta kelish uchun); aks holda<Outlet />(himoyalangan bola sahifalar — 11.9: 2.7). Rol bilan (RBAC — 5.17 frontend ko'rinishi):RoleRoute—allowedRolesprop oladi, foydalanuvchi roli ruxsat etilgan ro'yxatda bo'lmasa/403ga (ruxsat yo'q). Routing strukturasi — ichma-ich:<Route element={<ProtectedRoute/>}>(hammasini himoyalaydi)<Route element={<DashboardLayout/>}>(layout) sahifalar; admin sahifalar yana<Route element={<RoleRoute allowedRoles={["admin"]}/>}>ichida. Ikki nuqta: (1)ProtectedRoute/RoleRoute—Outletbilan (bir guruh route'ni birata himoyalash — DRY); (2)isLoadingtekshiruvi hal qiluvchi — boshlang'ich auth tekshiruvi paytida (token bor, user hali yuklanmoqda) login'ga otilib ketmaslik (aks holda foydalanuvchi har yangilashda bir zumga login ko'radi — yomon UX — 2.5). Bu — to'liq, ishonchli himoya tizimi.
2.7. API qatlami — axios instance va interceptors
API QATLAMI — markazlashgan so'rov boshqaruvi (axios instance — 2.18):
// lib/api.ts — bitta sozlangan instance (butun ilova ishlatadi):
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL, // env (11.3: 2.10)
timeout: 10000,
});
// REQUEST interceptor — HAR so'rovga token avtomatik qo'shadi:
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) config.headers.Authorization = `Bearer ${token}`; // har so'rovga 2.4-bob
return config;
});
// RESPONSE interceptor — 401 (token tugadi) refresh yoki logout:
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// refresh token bilan yangilash (yoki logout login)
// ... yangi token olib, so'rovni qayta yuborish
}
return Promise.reject(error);
}
);
axios instance — bir marta sozlash (baseURL, timeout); interceptor — har so'rovga avtomatik
Request interceptor: token qo'shadi | Response interceptor: 401 (refresh/logout) — 2.4API qatlami — markazlashgan, izchil so'rov boshqaruvi (axios — 2.18). Bitta sozlangan axios instance yaratiladi (
lib/api.ts):axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout })— butun ilova shu instance'ni ishlatadi (har sahifada qayta sozlash kerak emas). Request interceptor — har so'rov yuborishdan oldin avtomatik ishlaydi: localStorage'dan token olib,Authorization: Bearer <token>header'iga qo'shadi (2.4 — har so'rovga qo'lda token yozish kerak emas). Response interceptor — har javob kelganda ishlaydi: agar status 401 (token muddati tugadi/yaroqsiz) bo'lsa — refresh token bilan yangi access token olib so'rovni qayta yuboradi, yoki bo'lmasa logout qilib login'ga yo'naltiradi. Ikki nuqta: (1) axios instance — bir marta sozlash (baseURL, timeout, header'lar), interceptor — har so'rovga avtomatik mantiq (token qo'shish, xato boshqarish) — bu kodni markazlashtiradi (DRY, izchil); (2) request interceptor token qo'shadi, response interceptor 401'ni boshqaradi (refresh/logout). Bu — professional API qatlamining standart naqshi (token boshqaruvini har joyda qo'lda qaytarmasdan, bir joyda avtomatlashtirish).
2.8. Ma'lumot olish naqshi — loading/error/empty
MA'LUMOT OLISH — har sahifada BIR XIL naqsh (loading/error/empty/data):
function UsersPage() {
const { data, isLoading, error } = useQuery({ // 12.4 (TanStack Query)
queryKey: ["users"],
queryFn: () => api.get("/users").then(r => r.data),
});
if (isLoading) return <TableSkeleton />; // 1. yuklanmoqda (skeleton — 11.8: 2.9)
if (error) return <ErrorState onRetry={...} />; // 2. xato (qayta urinish)
if (!data?.length) return <EmptyState />; // 3. bo'sh (hech narsa yo'q)
return <UsersTable users={data} />; // 4. ma'lumot bor
}
4 HOLAT (har ma'lumot sahifasi SHU naqshda):
- LOADING skeleton (kontent shakli — 11.8: 2.9)
- ERROR xato xabari + qayta urinish tugmasi
- EMPTY "Hali ma'lumot yo'q" (+ yaratish tugmasi)
- DATA asosiy kontent (jadval/karta)
4 holat (loading/error/empty/data) — har sahifada izchil (foydalanuvchi tushunarli)
TanStack Query 12.4-bob — bu naqshni avtomatlashtiradi (kesh, retry, refetch)Ma'lumot olish naqshi — har ma'lumot sahifasida izchil qo'llanadigan to'rt holat. Sahifa serverdan ma'lumot olganda to'rt holat bo'lishi mumkin: (1) loading — ma'lumot yuklanmoqda skeleton (kontent shaklidagi ko'lanka — 11.8: 2.9 — spinner'dan yaxshiroq); (2) error — so'rov muvaffaqiyatsiz xato xabari + qayta urinish tugmasi; (3) empty — ma'lumot bor, lekin bo'sh (
data.length === 0) "Hali ma'lumot yo'q" + yaratish tugmasi; (4) data — ma'lumot bor asosiy kontent (jadval, karta). Har sahifa shu bir xil naqshni ishlatadi (if (isLoading) ... if (error) ... if (!data.length) ... return <Data/>). Bu naqshni TanStack Query 12.4-bob ancha soddalashtiradi (kesh, qayta urinish, fonda yangilash avtomatik). Ikki nuqta: (1) to'rt holat (loading/error/empty/data) — har ma'lumot sahifasida izchil boshqariladi (foydalanuvchi har doim aniq holatni ko'radi — "muzlab qolgan" ekran emas); (2) empty holat ko'pincha unutiladi (faqat loading/data o'ylanadi), lekin u muhim (yangi foydalanuvchi bo'sh dashboard ko'rganda "nima qilish kerak"ni bilin — yaratish tugmasi bilan). Bu naqsh — professional, foydalanuvchiga g'amxo'r UI'ning belgisi.
2.9. CRUD sahifa naqshi
CRUD (Create/Read/Update/Delete) — har resurs (users, products) BIR XIL naqshda:
RO'YXAT sahifasi (Read):
- jadval (ma'lumot — 2.8 naqsh) + qidiruv/filtr (useSearchParams — 11.9: 2.10)
- "Yangi" tugmasi yaratish formasi/sahifasi
- har qator: tahrir / o'chirish tugmasi
YARATISH/TAHRIRLASH (Create/Update) — bir xil forma (RHF + Zod — 11.10):
- yangi: bo'sh forma POST
- tahrir: mavjud ma'lumot (reset — 11.10: 2.11) PUT
O'CHIRISH (Delete):
- tasdiqlash modal (portal — 11.12) DELETE ro'yxatni yangilash
┌────────────────────────────────────────────────────────────┐
│ Ro'yxat (jadval+filtr) Yaratish/Tahrir (forma) O'chirish │
│ (tasdiq). Har resurs SHU naqshda (users, products, orders) │
└────────────────────────────────────────────────────────────┘
CRUD naqshi — bir marta yoz, har resursga mosla (usersproducts: bir xil struktura)
Forma (RHF+Zod — 11.10), modal (portal — 11.12), filtr (query — 11.9) birlashadiCRUD sahifa naqshi — har resurs (users, products, orders) bir xil struktura bilan boshqariladi. Ro'yxat sahifasi (Read): jadval (ma'lumot naqshi — 2.8) + qidiruv/filtr (
useSearchParams— URL'da, ulashiladigan — 11.9: 2.10) + "Yangi" tugmasi (yaratish formasi) + har qatorda tahrir/o'chirish tugmalari. Yaratish/Tahrirlash (Create/Update) — bir xil forma (RHF + Zod — 11.10): yangi rejimda bo'sh forma POST; tahrir rejimda mavjud ma'lumot bilan to'ldirilgan (reset — 11.10: 2.11) PUT. O'chirish (Delete) — tasdiqlash modal (portal — 11.12) DELETE ro'yxatni yangilash. Ikki nuqta: (1) CRUD naqshi — bir marta yozilib, har resursga moslanadi (users sahifasi va products sahifasi deyarli bir xil struktura — faqat ustunlar, forma maydonlari farq qiladi) — bu DRY va izchillik; (2) bu naqsh shu paytgacha o'rgangan barcha narsani birlashtiradi — forma (RHF + Zod — 11.10), modal (portal — 11.12), filtr (query — 11.9), ma'lumot olish 2.8-bob, jadval (11.4 ro'yxat). CRUD — dashboard'ning asosiy ish naqshi (admin panellar deyarli butunlay CRUD'dan iborat).
2.10. Bildirishnoma, mavzu va production tayyorlik
BILDIRISHNOMA (toast) — amal natijasini ko'rsatish (saqlandi, o'chirildi, xato):
- portal'ga render 11.12-bob, avtomatik yo'qolish (taymer — 11.5)
- Context yoki kutubxona (react-hot-toast, sonner — tayyor)
toast.success("Saqlandi"); toast.error("Xato yuz berdi");
MAVZU (dark/light) — Context + useLocalStorage (persist — 11.7):
- mavzu localStorage'da; <html data-theme="dark"> + CSS o'zgaruvchilar 1.5-bob
PRODUCTION TAYYORLIK (dashboard sifati):
Error boundary (har sahifa/widget — ilova qulamasin — 11.12)
Code splitting (har sahifa lazy — tez yuklash — 11.8)
a11y (klaviatura, aria, fokus — sidebar/modal/forma — 1.9, 11.12)
Loading/error holatlari (2.8 — "muzlab qolgan" ekran yo'q)
TypeScript (type-safe — 11.14); Performance (memo/virtualizatsiya — 11.11)
Toast — amal feedback (UX); mavzu — Context+persist; production — sifat checklist
Dashboard sifati = error boundary + lazy + a11y + holatlar + types (hammasi birga)Bildirishnoma, mavzu va production tayyorlik — dashboard'ning yakuniy sifat qatlami. Bildirishnoma (toast) — amal natijasini foydalanuvchiga ko'rsatish (saqlandi, o'chirildi, xato): portal'ga render 11.12-bob, avtomatik yo'qolish (taymer — 11.5), Context yoki tayyor kutubxona (
react-hot-toast,sonner) —toast.success("Saqlandi"),toast.error("Xato"). Mavzu (dark/light) — Context +useLocalStorage(persist — 11.7): mavzu localStorage'da saqlanadi,<html data-theme="dark">atributi + CSS o'zgaruvchilar 1.5-bob bilan amalga oshiriladi. Production tayyorlik (dashboard sifatini belgilovchi checklist): error boundary (har sahifa/widget — bittasi xato bersa butun ilova qulamasin — 11.12), code splitting (har sahifa lazy — tez boshlang'ich yuklash — 11.8), a11y (klaviatura navigatsiya, aria, fokus boshqaruvi — sidebar/modal/forma — 1.9, 11.12), loading/error holatlari (2.8 — "muzlab qolgan" ekran yo'q), TypeScript (type-safe — 11.14), performance (memo, virtualizatsiya katta jadvalda — 11.11). Ikki nuqta: (1) toast — amal feedback (foydalanuvchi har amal natijasini ko'rsin — UX); mavzu — Context + persist (foydalanuvchi tanlovi eslab qolinadi); (2) dashboard sifati = error boundary + lazy + a11y + holatlar + types — bularning hammasi birga professional, ishonchli, foydalanuvchiga g'amxo'r dashboard'ni tashkil qiladi. Bu — junior loyihasini senior loyihasidan ajratadigan farq.
2.11. Token saqlash: localStorage vs httpOnly cookie (xavfsizlik)
TOKENNI QAYERDA SAQLASH? — ikki asosiy variant, har birining tahdidi bor (14-QISM cross-ref):
┌───────────────────┬──────────────────────────┬──────────────────────────┐
│ │ localStorage / sessionStorage │ httpOnly cookie │
├───────────────────┼──────────────────────────┼──────────────────────────┤
│ JS o'qiy oladimi? │ HA — document orqali │ YO'Q — JS ko'rmaydi │
│ XSS tahdidi │ YUQORI (skript tokenni │ PAST (JS o'qiy olmaydi) │
│ │ o'g'irlashi mumkin) │ │
│ CSRF tahdidi │ PAST (avtomatik yubormaydi)│ BOR (cookie avtomatik │
│ │ │ ketadi SameSite kerak) │
│ Har so'rovga │ QO'LDA (interceptor) │ AVTOMATIK (brauzer) │
│ Sozlash │ Oson (frontend) │ Backend sozlaydi (Set-Cookie)│
└───────────────────┴──────────────────────────┴──────────────────────────┘
localStorage — qulay, lekin XSS bo'lsa token o'g'irlanadi (har skript o'qiy oladi)
httpOnly cookie — JS o'qiy olmaydi (XSS'ga chidamli), lekin CSRF himoyasi kerak (SameSite=Strict/Lax)
ENG XAVFSIZ naqsh: access token — xotirada (JS o'zgaruvchi), refresh token — httpOnly cookieToken saqlash — xavfsizlik tanlovi (14-QISM veb-xavfsizlik bilan chuqur bog'liq). Tokenni frontend'da saqlashning ikki asosiy joyi bor va har birining o'z tahdidi mavjud.
localStorage— qulay va oddiy (JSlocalStorage.getItem/setItembilan o'qiydi/yozadi, sahifa yangilanganda saqlanadi), lekin XSS hujumiga zaif: agar sahifaga zararli skript kirsa (masalan, tekshirilmagan foydalanuvchi kontenti orqali), ulocalStorageni o'qib tokenni o'g'irlashi mumkin — chunki har qanday JS unga kira oladi.httpOnlycookie —document.cookieorqali JS o'qiy olmaydi (brauzer himoyalaydi), shuning uchun XSS token o'g'irlay olmaydi; lekin cookie har so'rovga avtomatik qo'shilgani uchun CSRF (soxta so'rov) tahdidi paydo bo'ladi — buniSameSite=Strict/Laxva CSRF-token bilan yopadi. Amaliyotda eng xavfsiz naqsh: access token — faqat xotirada (React state yoki modul o'zgaruvchisi — sahifa yangilanishida yo'qoladi, lekin qisqa umrli), refresh token —httpOnlycookie (uzoq umrli, XSS'dan himoyalangan) — sahifa yangilanganda refresh cookie bilan yangi access token olinadi. Uch nuqta: (1)localStorageo'quv/prototip uchun qulay, lekin production'da XSS xavfini tushuning; (2)httpOnlycookie XSS'dan himoyalaydi, ammo CSRF himoyasi (SameSite) shart; (3) tanlov backend bilan kelishilgan bo'lishi kerak (cookie'ni backendSet-Cookiebilan o'rnatadi). Bu bobda soddalik uchunlocalStorageishlatiladi, lekin production dashboard uchun cookie yondashuvi tavsiya etiladi (14-QISM).
2.12. Access/refresh token va refresh interceptor (queue)
ACCESS + REFRESH TOKEN — nega ikkitasi?
ACCESS token — qisqa umrli (masalan 15 daqiqa); har so'rovda yuboriladi.
O'g'irlansa ham tez muddati tugaydi (zarar cheklangan).
REFRESH token — uzoq umrli (masalan 7 kun); FAQAT yangi access olishga.
Xavfsiz saqlanadi (httpOnly cookie — 2.11).
REFRESH OQIMI (401 yangi access so'rovni qayta yuborish):
so'rov 401 (access tugadi) POST /auth/refresh yangi access so'rovni qayta yubor
│
└─ refresh ham yaroqsiz logout login
MUAMMO: bir vaqtda 5 ta so'rov 401 olsa 5 marta refresh chaqiriladi (poyga)
YECHIM: refresh navbati (queue) — birinchi 401 refresh qiladi, qolganlar KUTADI,
keyin hammasi yangi token bilan qayta yuboriladi (bitta refresh)Access/refresh token — nega ikki token kerak? Access token — qisqa umrli (masalan 15 daqiqa), har so'rovda
Authorizationheader'da yuboriladi. Qisqa umri xavfsizlik uchun: agar o'g'irlansa ham, tez muddati tugagani uchun zarar cheklanadi. Refresh token — uzoq umrli (masalan 7 kun), faqat bitta vazifasi bor — yangi access token olish (POST /auth/refresh); shuning uchun u kamdan-kam ishlatiladi va xavfsiz joyda (httpOnly cookie — 2.11) saqlanadi. Refresh oqimi: so'rov 401 qaytarsa (access muddati tugagan), interceptorPOST /auth/refreshchaqiradi, yangi access token oladi va asl so'rovni qayta yuboradi — foydalanuvchi buni sezmaydi (uzluksiz tajriba). Agar refresh ham yaroqsiz bo'lsa (7 kun o'tgan) — logout va login'ga. Muhim muammo: agar bir vaqtda bir nechta so'rov 401 olsa (masalan sahifa 5 ta so'rov yuboradi), naive interceptor 5 marta refresh chaqiradi — poyga holati (bir refresh boshqasini bekor qilishi mumkin). Yechim — refresh navbati (queue): birinchi 401 refresh boshlaydi, qolgan so'rovlarisRefreshingbayrog'i bilan kutadi, refresh tugagach hammasi yangi token bilan qayta yuboriladi (bitta refresh, hamma so'rov tiklanadi). Bu naqsh Misol 15'da to'liq kod bilan ko'rsatilgan — professional interceptor'ning belgisi.
2.13. Role-based UI — menu/tugma/route rol bo'yicha yashirish
RBAC UI — foydalanuvchi ko'rmasligi kerak narsani KO'RSATMASLIK (nafaqat route, UI ham):
3 QATLAM (hammasi kerak):
1. ROUTE — RoleRoute (admin sahifaga user kira olmaydi — 2.6)
2. MENU — sidebar'da faqat ruxsat bor havolalar ko'rinadi (user "Users" menyuni ko'rmaydi)
3. TUGMA — amal tugmalari rol bo'yicha (user "O'chirish" tugmasini ko'rmaydi)
<Can role="admin"> {/* yordamchi komponent */}
<button onClick={onDelete}>O'chirish</button>
</Can>
// yoki hook:
const { hasRole } = useAuth();
{hasRole("admin") && <DeleteButton />}
UI yashirish — QULAYLIK (UX), HIMOYA emas (backend baribir tekshiradi — 2.4, 5.17)
3 qatlam: route + menu + tugma (izchil — user ko'rmasligi kerak narsani ko'rmaydi)Role-based UI — foydalanuvchi roliga qarab interfeysni moslash. RBAC (Role-Based Access Control) frontend'da faqat route himoyasi 2.6-bob emas, butun UIni qamraydi — foydalanuvchi ruxsati yo'q narsalarni umuman ko'rmasligi kerak (uni bosib xato olish o'rniga). Uch qatlam birga ishlaydi: (1) route —
RoleRouteadmin sahifaga oddiy foydalanuvchini kiritmaydi 2.6-bob; (2) menu — sidebar'da faqat ruxsat berilgan havolalar ko'rsatiladi (oddiy foydalanuvchi "Foydalanuvchilar" menyu bandini umuman ko'rmaydi); (3) tugma/amal — jadval qatoridagi "O'chirish", "Tahrir" kabi tugmalar rol bo'yicha ko'rsatiladi/yashiriladi. Buni qulay qilish uchun<Can role="admin">...</Can>yordamchi komponenti yokiuseAuth()danhasRole("admin")hook ishlatiladi — shartli render 11.5-bob. Ikki muhim nuqta: (1) UI yashirish — qulaylik va UX, himoya emas; foydalanuvchi brauzer'da kodni o'zgartirib tugmani "ko'rsatishi" mumkin, shuning uchun haqiqiy himoya backend'da (har amal token va rolni tekshiradi — 2.4, 5.17-bob); (2) uch qatlam izchil bo'lishi kerak — agar menyu yashirin, lekin route ochiq bo'lsa (yoki aksincha), foydalanuvchi chalkashadi yoki xato holatga tushadi. Role-based UI — professional dashboard'ning muhim, ko'pincha e'tibordan chetda qoladigan qismi.
2.14. Server state va TanStack Query — cache, invalidate, optimistic (12-QISM)
SERVER STATE ≠ CLIENT STATE — server ma'lumoti alohida boshqariladi (12.4 to'liq):
CLIENT state (useState/Context) │ SERVER state (TanStack Query)
- UI holati (modal ochiq?) │ - serverdagi ma'lumot (users, products)
- mavzu, forma qiymatlari │ - kesh, eskirish, qayta yuklash, sinxronizatsiya
- ilova o'zi egasi │ - server egasi (frontend faqat "nusxa" tutadi)
QUERY (o'qish) — kesh + avtomatik holat:
useQuery({ queryKey: ["products"], queryFn }) data/isLoading/error 2.8-bob
MUTATION (o'zgartirish) — POST/PUT/DELETE + keshni yangilash:
onSuccess: invalidateQueries(["products"]) ro'yxat qayta yuklanadi (yangi ma'lumot)
OPTIMISTIC UPDATE — javobni KUTMASDAN UI'ni darrov yangilash (tez his):
onMutate: keshni oldindan o'zgartir xato bo'lsa onError'da orqaga qaytar (rollback)
Server state — TanStack Query (kesh, dedup, retry, refetch — qo'lda yozmang)
invalidate — o'zgartirgandan keyin ro'yxatni yangilash; optimistic — tez UXServer state va TanStack Query (12-QISM to'liq ochadi — bu yerda dashboard konteksti). Muhim tushuncha: server state client state'dan farq qiladi. Client state (
useState, Context) — ilovaning o'zi egalik qiladigan holat (modal ochiqmi, mavzu, forma qiymatlari). Server state — serverda yashaydigan ma'lumot (users, products); frontend faqat uning nusxasini (keshini) tutadi, shuning uchun u eskirishi (stale), qayta yuklanishi, sinxronlanishi kerak. Buni qo'ldauseState + useEffectda boshqarish murakkab (kesh, dedup, qayta urinish, fonda yangilash) — shuning uchun TanStack Query ishlatiladi. Query (o'qish) —useQuerykesh va holatni (data/isLoading/error — 2.8) avtomatik beradi. Mutation (o'zgartirish — POST/PUT/DELETE) —useMutation;onSuccessdainvalidateQueries(["products"])bilan ro'yxat keshini "eskirdi" deb belgilaydi TanStack Query uni qayta yuklaydi (o'zgargan ma'lumot ko'rinadi). Optimistic update — javobni kutmasdan UI'ni darrov yangilash (masalan "like" bosilganda darrov ko'rsatish):onMutateda keshni oldindan o'zgartiriladi, agar so'rov xato bersaonErrorda orqaga qaytariladi (rollback) — bu ilovani "tez" his qildiradi. Ikki nuqta: (1) server state uchun TanStack Query (qo'lda kesh/retry/refetch yozish — xatoga moyil, takroriy); (2)invalidate— o'zgartirgandan keyin ro'yxatni yangi holatga keltirish,optimistic— foydalanuvchiga tez javob (kutish hissi yo'q). Bu — zamonaviy dashboard'da ma'lumot boshqaruvining asosi.
2.15. Ma'lumot jadvali — pagination, sort, filter, search
JADVAL — dashboard'ning markaziy komponenti (ko'p ma'lumotni boshqarish):
4 IMKONIYAT (odatda URL'da — useSearchParams — ulashiladigan/yangilashga chidamli — 11.9):
- PAGINATION — sahifalash (?page=2&limit=20) — katta ma'lumotni bo'lib yuklash
- SORT — tartiblash (?sort=name&order=asc) — ustun sarlavhasiga bosish
- FILTER — filtrlash (?status=active) — kategoriya/holat bo'yicha
- SEARCH — qidirish (?search=olma) — matn bo'yicha (debounce bilan — 11.7)
SERVER-SIDE (katta ma'lumot — tavsiya): CLIENT-SIDE (kichik ma'lumot):
- server pagination/sort qiladi - hammasi yuklanadi, frontend saralaydi
- ?page/sort/filter backend qaytaradi - kichik ro'yxatga (<1000) qulay
- queryKey'ga kiradi kesh har filtrga - katta ma'lumotga sekin
URL state (useSearchParams) — filtr/sahifa URL'da (ulashiladi, orqaga tugmasi ishlaydi)
Search — debounce 11.7-bob — har harfda so'rov yubormaslik (300ms kutib, bitta so'rov)Ma'lumot jadvali — dashboard'ning eng ko'p ishlatiladigan komponenti (admin panel deyarli jadvallardan iborat). To'liq jadval to'rt imkoniyatni beradi va ular odatda URL'da (
useSearchParams— 11.9) saqlanadi (ulashiladigan havola, brauzer orqaga tugmasi ishlaydi, sahifa yangilanishiga chidamli): (1) pagination — sahifalash (?page=2&limit=20), katta ma'lumotni bo'lib yuklash (hammasini birdan emas); (2) sort — tartiblash (?sort=name&order=asc), ustun sarlavhasiga bosib; (3) filter — filtrlash (?status=active), holat/kategoriya bo'yicha; (4) search — matn qidirish (?search=olma), debounce bilan (11.7 — har harf bosilganda so'rov yubormaslik, 300ms kutib bitta so'rov — server yuki va titrash kamayadi). Muhim qaror — server-side vs client-side: katta ma'lumotda (minglab qator) pagination/sort/filter serverda qilinishi kerak (backend faqat kerakli sahifani qaytaradi — tez, kam xotira); kichik ma'lumotda (bir necha yuz qator) frontend'da qilish mumkin (hammasi yuklanadi, browser saralaydi). Server-side'da filtr/sahifa qiymatlariqueryKeyga kiradi (["products", { page, sort, search }]) — har o'zgarishda TanStack Query yangi so'rov yuboradi va keshlaydi 2.14-bob. Ikki nuqta: (1) URL state (useSearchParams) — filtr holatini URL'da saqlash (ulashiladigan, orqaga tugma bilan ishlaydigan — komponent state'dan yaxshiroq bu holatda); (2) search — majburiy debounce (aks holda har bosishda ortiqcha so'rov). Jadval — dashboard ma'lumot boshqaruvining yuragi.
2.16. Breadcrumb, chart, i18n, UI kutubxona tanlovi
QO'SHIMCHA DASHBOARD ELEMENTLARI (professional to'liqlik):
BREADCRUMB — foydalanuvchi qayerda ekanini ko'rsatadi (navigatsiya konteksti):
Bosh sahifa / Mahsulotlar / Olma tahriri har bo'lak — havola (11.9 route)
(matchRoutes / useLocation'dan avtomatik quriladi)
CHART (vizualizatsiya) — dashboard statistikasi grafikda (recharts / chart.js):
<LineChart data={sales}>...</LineChart> daromad, foydalanuvchi o'sishi
(recharts — React'ga tabiiy, deklarativ; chart.js — canvas, kuchli, imperativ)
i18n (ko'p til — react-i18next) — matnlar tarjima kalitida (8.30 backend i18n bilan):
const { t } = useTranslation(); <h1>{t("dashboard.title")}</h1>
(til fayllari: uz.json, en.json, ru.json; til almashtirgich header'da)
UI KUTUBXONA — tanlov (vaqt tejash vs nazorat):
shadcn/ui — nusxa-joylashtir (kod sizniki, to'liq nazorat, Tailwind)
MUI — tayyor, boy komponentlar (Material Design, tez)
Ant Design — enterprise/admin uchun kuchli (jadval, forma boy)
Breadcrumb — navigatsiya konteksti; chart — ma'lumotni ko'rish; i18n — ko'p til
UI kutubxona — o'zing yozish (nazorat) yoki tayyor (tezlik) — loyihaga qarabQo'shimcha dashboard elementlari — professional to'liqlik uchun. Breadcrumb (non yo'li) — foydalanuvchi ilovaning qayerida ekanini ko'rsatadi ("Bosh sahifa / Mahsulotlar / Olma tahriri"), har bo'lak yuqori sahifaga havola (11.9 route); u odatda joriy URL'dan (
useLocation,matchRoutes) avtomatik quriladi. Chart (vizualizatsiya) — dashboard statistikasini grafikda ko'rsatish (daromad chizig'i, foydalanuvchi o'sishi, ustunli diagramma); ikki mashhur kutubxona: recharts (React'ga tabiiy, komponent-asosli, deklarativ —<LineChart><Line/></LineChart>) va chart.js (canvas'ga chizadi, juda kuchli va tez, lekin imperativ —react-chartjs-2o'rovi bilan). Dashboard'da recharts ko'pincha qulayroq (React uslubiga mos). i18n (ko'p til) — matnlarni tarjima kalitlari orqali berish (react-i18next):const { t } = useTranslation()vat("dashboard.title"); til fayllari (uz.json,en.json,ru.json) va header'dagi til almashtirgich (bu — backend i18n bilan bir tamoyil — 8.30-bob). UI kutubxona tanlovi — o'z komponentlaringizni nolldan yozish (to'liq nazorat, lekin sekin) yoki tayyor kutubxona (tez): shadcn/ui (komponent kodini loyihaga nusxalab qo'yasiz — kod sizniki, to'liq moslashtiriladigan, Tailwind bilan); MUI (Material Design, boy tayyor komponentlar); Ant Design (enterprise/admin panellar uchun ayniqsa kuchli — boy jadval, forma komponentlari). Ikki nuqta: (1) breadcrumb navigatsiya konteksti beradi, chart ma'lumotni ko'rinarli qiladi, i18n ilovani ko'p tilli qiladi — bularning har biri professional dashboard'ni to'ldiradi; (2) UI kutubxona tanlovi loyiha talabiga bog'liq — o'quv/portfolio uchun o'zingiz yozish (chuqur tushunish), tez ishlab chiqarish uchun tayyor kutubxona (samaradorlik).
3. Sintaksis — tez ma'lumotnoma
LAYOUT 2.2-bob: <Route element={<DashboardLayout/>}> + <Outlet/> (sidebar+header+kontent)
RESPONSIVE 2.3-bob: const isMobile = useMediaQuery("(max-width:768px)") // 11.7
AUTH CTX 2.5-bob: createContext<AuthContextType|null>(null) + useAuth() (11.14: 2.13)
PERSIST 2.5-bob: useLocalStorage<string|null>("token", null) // 11.7
PROTECTED 2.6-bob: if(isLoading) <Spinner/>; if(!user) <Navigate to="/login"/>; <Outlet/>
ROLE 2.6-bob: if(!allowedRoles.includes(user.role)) <Navigate to="/403"/>
API 2.7-bob: axios.create({baseURL}) + interceptors.request/response (token/401)
DATA 2.8-bob: if(isLoading)skeleton; if(error)retry; if(!data.length)empty; <Data/>
CRUD 2.9-bob: ro'yxat(jadval+filtr) forma(RHF+Zod) o'chir(modal tasdiq)
TOAST 2.10-bob: toast.success(...) / toast.error(...) // portal + taymer
REFRESH 2.12-bob: 401 POST /auth/refresh yangi access so'rov qayta (queue)
ROLE UI 2.13-bob: <Can role="admin">...</Can> | NAV.filter(i=>i.roles.includes(user.role))
QUERY 2.14-bob: useQuery(o'qish) + useMutation(onSuccess: invalidateQueries)
TABLE 2.15-bob: useSearchParams: ?page&sort&search + useDebounce(search,300) // 11.7
CHART 2.16-bob: <ResponsiveContainer><LineChart data><Line dataKey/></LineChart> // recharts
I18N 2.16-bob: const {t}=useTranslation(); t("dashboard.title") // react-i18next4. Batafsil kod namunalari
Misol 1 — Dashboard layout (sidebar + header + Outlet — 2.2)
import { Outlet, NavLink } from "react-router-dom";
function DashboardLayout() {
return (
<div className="dashboard-layout">
<aside className="sidebar">
<nav>
<NavLink to="/dashboard" className={({ isActive }) => isActive ? "active" : ""} end>
Dashboard
</NavLink>
<NavLink to="/users" className={({ isActive }) => isActive ? "active" : ""}>
Foydalanuvchilar
</NavLink>
<NavLink to="/products"> Mahsulotlar</NavLink>
<NavLink to="/settings"> Sozlamalar</NavLink>
</nav>
</aside>
<div className="main">
<header className="header">
<SearchBar />
<UserMenu />
</header>
<main className="content">
<Outlet /> {/* sahifa kontenti (11.9: 2.7) */}
</main>
</div>
</div>
);
}Misol 2 — Responsive sidebar (mobil — 2.3)
function DashboardLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const isMobile = useMediaQuery("(max-width: 768px)"); // 11.7
return (
<div className="layout">
<aside className={`sidebar ${isMobile && !sidebarOpen ? "hidden" : ""}`}>
<Sidebar onNavigate={() => setSidebarOpen(false)} /> {/* havolaga bossa yopil */}
</aside>
{isMobile && sidebarOpen && (
<div className="overlay" onClick={() => setSidebarOpen(false)} /> {/* fon — yopadi */}
)}
<div className="main">
<header>
{isMobile && <button onClick={() => setSidebarOpen(true)} aria-label="Menyu"></button>}
</header>
<main><Outlet /></main>
</div>
</div>
);
}Misol 3 — Auth context + persist (2.5)
interface User { id: string; name: string; role: "user" | "admin"; }
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
isLoading: boolean;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: React.PropsWithChildren) {
const [token, setToken] = useLocalStorage<string | null>("token", null); // 11.7 persist
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => { // mount'da: token bor user'ni tikla (2.5)
if (!token) { setIsLoading(false); return; }
api.get("/auth/me")
.then(r => setUser(r.data))
.catch(() => setToken(null)) // token yaroqsiz tozala
.finally(() => setIsLoading(false));
}, [token]);
const login = async (email: string, password: string) => {
const { data } = await api.post("/auth/login", { email, password });
setToken(data.token); // saqla (persist)
setUser(data.user);
};
const logout = () => { setToken(null); setUser(null); };
return <AuthContext.Provider value={{ user, login, logout, isLoading }}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextType {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth AuthProvider ichida");
return ctx;
}Misol 4 — Login sahifasi (RHF + Zod — 11.10)
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email("Email noto'g'ri"),
password: z.string().min(6, "Kamida 6 belgi"),
});
type LoginForm = z.infer<typeof loginSchema>;
function LoginPage() {
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from || "/dashboard"; // qaerga bormoqchi edi (11.9: 2.12)
const { register, handleSubmit, setError, formState: { errors, isSubmitting } } =
useForm<LoginForm>({ resolver: zodResolver(loginSchema) });
const onSubmit = async (data: LoginForm) => {
try {
await login(data.email, data.password);
navigate(from, { replace: true }); // login'dan keyin (11.9: 2.9)
} catch {
setError("root", { message: "Email yoki parol noto'g'ri" }); // server xatosi (11.10: 2.12)
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} placeholder="Email" />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register("password")} placeholder="Parol" />
{errors.password && <span>{errors.password.message}</span>}
{errors.root && <p className="error">{errors.root.message}</p>}
<button disabled={isSubmitting}>{isSubmitting ? "..." : "Kirish"}</button>
</form>
);
}Misol 5 — Protected route + rol (2.6)
import { Navigate, Outlet, useLocation } from "react-router-dom";
function ProtectedRoute() {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <FullPageSpinner />; // boshlang'ich tekshiruv (2.5)
if (!user) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
return <Outlet />;
}
function RoleRoute({ allowedRoles }: { allowedRoles: string[] }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
if (!allowedRoles.includes(user.role)) return <Navigate to="/403" replace />;
return <Outlet />;
}Misol 6 — API instance + interceptors (2.7)
// lib/api.ts
import axios from "axios";
export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 10000,
});
// Request — har so'rovga token (2.7)
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) config.headers.Authorization = `Bearer ${JSON.parse(token)}`;
return config;
});
// Response — 401 token tugadi (refresh yoki logout)
api.interceptors.response.use(
(res) => res,
async (error) => {
if (error.response?.status === 401) {
localStorage.removeItem("token");
window.location.href = "/login"; // logout login (oddiy yondashuv)
// (ilg'or: refresh token bilan yangilash + so'rovni qayta yuborish)
}
return Promise.reject(error);
}
);Misol 7 — Ma'lumot sahifasi: 4 holat (2.8)
import { useQuery } from "@tanstack/react-query"; // 12.4
function UsersPage() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["users"],
queryFn: () => api.get<User[]>("/users").then(r => r.data),
});
if (isLoading) return <TableSkeleton rows={8} />; // 1. loading (11.8: 2.9)
if (error) return <ErrorState message="Yuklab bo'lmadi" onRetry={refetch} />; // 2. error
if (!data?.length) return <EmptyState message="Foydalanuvchilar yo'q" />; // 3. empty
return <UsersTable users={data} />; // 4. data
}
// 4 holat — har ma'lumot sahifasida izchil (foydalanuvchi har doim aniq holatni ko'radi — 2.8)Misol 8 — CRUD: ro'yxat + filtr (2.9)
function ProductsPage() {
const [searchParams, setSearchParams] = useSearchParams(); // 11.9: 2.10
const search = searchParams.get("search") || "";
const { data, isLoading } = useQuery({
queryKey: ["products", search],
queryFn: () => api.get(`/products?search=${search}`).then(r => r.data),
});
return (
<div>
<div className="page-header">
<h1>Mahsulotlar</h1>
<Link to="/products/new" className="btn-primary">+ Yangi</Link> {/* yaratish */}
</div>
<input
value={search}
onChange={(e) => setSearchParams({ search: e.target.value })} // filtr URL'da 11.9-bob
placeholder="Qidirish..."
/>
{isLoading ? <TableSkeleton /> : <ProductsTable products={data ?? []} />}
</div>
);
}Misol 9 — CRUD: yaratish/tahrir formasi (2.9)
function ProductForm() {
const { id } = useParams(); // tahrir bo'lsa id bor (11.9: 2.6)
const isEdit = Boolean(id);
const navigate = useNavigate();
const { register, handleSubmit, reset } = useForm<ProductInput>({ resolver: zodResolver(productSchema) });
// Tahrir: mavjud ma'lumotni yukla (11.10: 2.11)
const { data: product } = useQuery({
queryKey: ["product", id],
queryFn: () => api.get(`/products/${id}`).then(r => r.data),
enabled: isEdit,
});
useEffect(() => { if (product) reset(product); }, [product, reset]);
const onSubmit = async (data: ProductInput) => {
if (isEdit) await api.put(`/products/${id}`, data); // tahrir PUT
else await api.post("/products", data); // yangi POST
toast.success(isEdit ? "Yangilandi" : "Qo'shildi");
navigate("/products");
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="Nom" />
<input type="number" {...register("price")} placeholder="Narx" />
<button>{isEdit ? "Saqlash" : "Qo'shish"}</button>
</form>
);
}Misol 10 — CRUD: o'chirish (tasdiq modal — 2.9, 11.12)
function DeleteButton({ productId, productName }: { productId: string; productName: string }) {
const [showConfirm, setShowConfirm] = useState(false);
const queryClient = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: () => api.delete(`/products/${productId}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["products"] }); // ro'yxatni yangilash (12.4)
toast.success("O'chirildi");
setShowConfirm(false);
},
});
return (
<>
<button onClick={() => setShowConfirm(true)} aria-label="O'chirish"></button>
{showConfirm && (
<Modal onClose={() => setShowConfirm(false)}> {/* portal — 11.12 */}
<p>"{productName}"ni o'chirishni tasdiqlaysizmi?</p>
<button onClick={() => mutate()} disabled={isPending}>Ha, o'chir</button>
<button onClick={() => setShowConfirm(false)}>Bekor</button>
</Modal>
)}
</>
);
}Misol 11 — Dashboard statistika kartalar (2.1)
function DashboardPage() {
const { data: stats, isLoading } = useQuery({
queryKey: ["stats"],
queryFn: () => api.get("/stats").then(r => r.data),
});
if (isLoading) return <StatsSkeleton />;
return (
<div className="stats-grid">
<StatCard title="Foydalanuvchilar" value={stats.users} icon="" trend="+12%" />
<StatCard title="Buyurtmalar" value={stats.orders} icon="" trend="+5%" />
<StatCard title="Daromad" value={`${stats.revenue} so'm`} icon="" trend="+8%" />
<StatCard title="Faol sessiyalar" value={stats.sessions} icon="" />
</div>
);
}
function StatCard({ title, value, icon, trend }: { title: string; value: string | number; icon: string; trend?: string }) {
return (
<div className="stat-card">
<span className="stat-icon">{icon}</span>
<div><p className="stat-title">{title}</p><p className="stat-value">{value}</p></div>
{trend && <span className="stat-trend">{trend}</span>}
</div>
);
}Misol 12 — Mavzu (dark/light — 2.10)
const ThemeContext = createContext<{ theme: string; toggle: () => void } | null>(null);
function ThemeProvider({ children }: React.PropsWithChildren) {
const [theme, setTheme] = useLocalStorage("theme", "light"); // 11.7 persist
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme); // <html data-theme="dark">
}, [theme]);
const toggle = () => setTheme(t => (t === "light" ? "dark" : "light"));
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
// CSS: [data-theme="dark"] { --bg: #1a1a1a; --text: #fff; } (1.5 o'zgaruvchilar)Misol 13 — To'liq routing (public/protected/admin — 2.6)
import { lazy, Suspense } from "react";
const Login = lazy(() => import("./pages/Login"));
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Users = lazy(() => import("./pages/Users"));
const Products = lazy(() => import("./pages/Products"));
function AppRouter() {
return (
<ErrorBoundary FallbackComponent={GlobalError}> {/* 11.12 */}
<Suspense fallback={<FullPageSpinner />}> {/* 11.8 lazy */}
<Routes>
<Route path="/login" element={<Login />} /> {/* public */}
<Route element={<ProtectedRoute />}> {/* himoyalangan */}
<Route element={<DashboardLayout />}> {/* layout */}
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
<Route path="/products/new" element={<ProductForm />} />
<Route path="/products/:id/edit" element={<ProductForm />} />
<Route element={<RoleRoute allowedRoles={["admin"]} />}> {/* faqat admin */}
<Route path="/users" element={<Users />} />
</Route>
</Route>
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</ErrorBoundary>
);
}Misol 14 — Providers tartibi (app setup — 2.1)
// main.tsx — barcha provider'lar to'g'ri tartibda
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // 12.4
import { BrowserRouter } from "react-router-dom";
const queryClient = new QueryClient();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}> {/* data fetching 12.4-bob */}
<ThemeProvider> {/* mavzu 2.10-bob */}
<BrowserRouter> {/* routing 11.9-bob */}
<AuthProvider> {/* auth (router ichida — useNavigate) */}
<AppRouter />
<Toaster /> {/* toast 2.10-bob */}
</AuthProvider>
</BrowserRouter>
</ThemeProvider>
</QueryClientProvider>
</StrictMode>
);
// Provider tartibi: Query Theme Router Auth (auth router ichida — navigatsiya uchun)Misol 15 — Refresh token interceptor (navbat/queue — 2.12)
// lib/api.ts — 401 refresh, bir vaqtdagi so'rovlarni navbatga qo'yish
let isRefreshing = false;
let queue: Array<(token: string) => void> = []; // refresh kutayotgan so'rovlar
api.interceptors.response.use(
(res) => res,
async (error) => {
const original = error.config;
if (error.response?.status !== 401 || original._retry) {
return Promise.reject(error); // 401 emas yoki allaqachon urindik
}
if (isRefreshing) { // refresh davom etmoqda NAVBATGA
return new Promise((resolve) => {
queue.push((token) => {
original.headers.Authorization = `Bearer ${token}`;
resolve(api(original)); // yangi token bilan qayta yubor
});
});
}
original._retry = true;
isRefreshing = true;
try {
const { data } = await api.post("/auth/refresh"); // refresh (cookie yoki refresh token)
const newToken = data.accessToken;
localStorage.setItem("token", JSON.stringify(newToken));
queue.forEach((cb) => cb(newToken)); // navbatdagi so'rovlarni bo'shat
queue = [];
original.headers.Authorization = `Bearer ${newToken}`;
return api(original); // asl so'rovni qayta yubor
} catch (refreshError) {
queue = [];
localStorage.removeItem("token");
window.location.href = "/login"; // refresh ham yaroqsiz logout
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
);
// Bir vaqtda ko'p 401 bo'lsa — bitta refresh, qolganlar kutadi (poyga yo'q — 2.12)Misol 16 — Role-based UI: <Can> va menyu (2.13)
// useAuth'ga hasRole qo'shilgan deb faraz qilamiz
function Can({ role, children }: { role: string | string[]; children: React.ReactNode }) {
const { user } = useAuth();
const roles = Array.isArray(role) ? role : [role];
if (!user || !roles.includes(user.role)) return null; // ruxsat yo'q hech narsa
return <>{children}</>;
}
// Sidebar — menyu bandlari rol bo'yicha (11.5 shartli render)
const NAV = [
{ to: "/dashboard", label: "Dashboard", roles: ["user", "admin"] },
{ to: "/products", label: "Mahsulotlar", roles: ["user", "admin"] },
{ to: "/users", label: "Foydalanuvchilar", roles: ["admin"] }, // faqat admin
];
function Sidebar() {
const { user } = useAuth();
return (
<nav>
{NAV.filter((i) => user && i.roles.includes(user.role)).map((i) => (
<NavLink key={i.to} to={i.to}>{i.label}</NavLink>
))}
</nav>
);
}
// Amal tugmasi — faqat admin ko'radi:
// <Can role="admin"><DeleteButton productId={p.id} /></Can>
// UI yashirish — QULAYLIK; haqiqiy himoya backend'da (2.13, 5.17)Misol 17 — Jadval: pagination + sort + qidiruv (URL state — 2.15)
function ProductsTablePage() {
const [params, setParams] = useSearchParams(); // filtr URL'da (11.9)
const page = Number(params.get("page") ?? 1);
const sort = params.get("sort") ?? "name";
const search = params.get("search") ?? "";
const debounced = useDebounce(search, 300); // 11.7 — har harfda so'rov yubormaslik
const { data, isLoading } = useQuery({
queryKey: ["products", { page, sort, search: debounced }], // filtr kesh kaliti (2.14)
queryFn: () =>
api.get("/products", { params: { page, limit: 20, sort, search: debounced } })
.then((r) => r.data),
placeholderData: (prev) => prev, // sahifa almashganda eski ma'lumot turadi
});
const setParam = (key: string, value: string) =>
setParams((p) => { p.set(key, value); if (key !== "page") p.set("page", "1"); return p; });
return (
<div>
<input defaultValue={search} onChange={(e) => setParam("search", e.target.value)}
placeholder="Qidirish..." />
<table>
<thead>
<tr>
<th onClick={() => setParam("sort", "name")}>Nom {sort === "name" ? "▲" : ""}</th>
<th onClick={() => setParam("sort", "price")}>Narx {sort === "price" ? "▲" : ""}</th>
</tr>
</thead>
<tbody>
{isLoading
? <tr><td colSpan={2}><TableSkeleton /></td></tr>
: data?.items.map((p: Product) => (
<tr key={p.id}><td>{p.name}</td><td>{p.price}</td></tr>
))}
</tbody>
</table>
<Pagination page={page} total={data?.totalPages ?? 1}
onChange={(n) => setParam("page", String(n))} />
</div>
);
}Misol 18 — Breadcrumb va chart (2.16)
// Breadcrumb — URL'dan avtomatik (har bo'lak — havola)
function Breadcrumb() {
const location = useLocation();
const parts = location.pathname.split("/").filter(Boolean); // ["products","olma"]
return (
<nav aria-label="breadcrumb">
<Link to="/dashboard">Bosh sahifa</Link>
{parts.map((part, i) => {
const to = "/" + parts.slice(0, i + 1).join("/");
return <span key={to}> / <Link to={to}>{decodeURIComponent(part)}</Link></span>;
})}
</nav>
);
}
// Chart — recharts (dashboard statistikasi grafikda)
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
function SalesChart({ data }: { data: { month: string; revenue: number }[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="var(--primary)" />
</LineChart>
</ResponsiveContainer>
);
}5. To'g'ri va noto'g'ri holatlar
1) Layout takrori
har sahifada sidebar/header qayta yozish (takror)
DashboardLayout + Outlet (bir marta — 2.2)2) Auth boshlang'ich holat
isLoading'siz protected route (token bor user sahifa yangilashda login'ga otiladi — 2.5)
if(isLoading) <Spinner/> (boshlang'ich tekshiruv — 2.6)3) Token boshqaruvi
har so'rovda qo'lda token header qo'shish (takror, unutiladi)
axios interceptor (har so'rovga avtomatik — 2.7)4) Ma'lumot holatlari
faqat data (loading/error/empty unutilgan — "muzlagan" ekran)
4 holat: loading/error/empty/data (2.8)5) Himoya
faqat frontend protected route (API ochiq — 2.4)
frontend (UX) + backend token tekshiruvi (5.16, 14)6) Production sifat
error boundary/lazy/a11y yo'q (bitta xato — oq ekran; sekin; nogironlar ishlata olmaydi)
error boundary + lazy + a11y + holatlar (2.10)7) Refresh interceptor (poyga)
har 401 alohida refresh chaqiradi (5 so'rov 5 refresh — poyga, token buziladi)
isRefreshing bayrog'i + navbat (bitta refresh, qolganlar kutadi — 2.12, Misol 15)8) Role-based UI
faqat route himoya (user "O'chirish" tugmasini ko'radi, bosib xato oladi)
route + menu + tugma (3 qatlam izchil — foydalanuvchi ko'rmasligi kerakni ko'rmaydi — 2.13)9) Server state
server ma'lumotini useState/useEffect'da qo'lda boshqarish (kesh/retry/refetch yo'q)
TanStack Query (useQuery/useMutation + invalidate — 2.14)10) Token saqlash
token'ni ko'r-ko'rona localStorage'da (XSS bo'lsa o'g'irlanadi — tahdidni bilmaslik)
tahdidni tushunish: access xotirada + refresh httpOnly cookie (production — 2.11, 14)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Sahifa yangilaganda login'ga otiladi (token bor bo'lsa ham)
Sababi: isLoading tekshiruvi yo'q — user hali yuklanmagan, protected route darrov login'ga 2.5-bob. Yechimi: if (isLoading) return <Spinner/> (Misol 5).
Xato 2 — useNavigate AuthProvider'da ishlamaydi
Sababi: AuthProvider BrowserRouter tashqarisida (router konteksti yo'q — 11.9). Yechimi: AuthProvider'ni BrowserRouter ichiga qo'y (Misol 14).
Xato 3 — Token har so'rovda yuborilmaydi
Sababi: interceptor sozlanmagan yoki noto'g'ri instance 2.7-bob. Yechimi: axios instance + request interceptor (Misol 6); har joyda shu api instance.
Xato 4 — Tahrir formasi ma'lumot bilan to'lmaydi
Sababi: async ma'lumot, reset chaqirilmagan (11.10: 2.11). Yechimi: useEffectda reset(data) (Misol 9).
Xato 5 — O'chirgandan keyin ro'yxat eskirgan
Sababi: cache yangilanmagan 12.4-bob. Yechimi: invalidateQueries (TanStack Query — Misol 10).
Xato 6 — Provider tartibi noto'g'ri (context topilmaydi)
Sababi: provider noto'g'ri tartibda (masalan auth router tashqarisida — 2.1). Yechimi: Query Theme Router Auth (Misol 14).
Xato 7 — Mobil sidebar yopilmaydi (havolaga bosgandan keyin)
Sababi: navigatsiyada setSidebarOpen(false) yo'q 2.3-bob. Yechimi: NavLink onClick yoki onNavigate bilan yop (Misol 2).
Xato 8 — Bir vaqtda ko'p so'rov 401 olganda token buziladi
Sababi: har 401 mustaqil refresh chaqiradi — bir nechta refresh poyga qiladi, biri boshqasini bekor qiladi 2.12-bob. Yechimi: isRefreshing bayrog'i + navbat (queue) — bitta refresh, qolganlar kutadi (Misol 15).
Xato 9 — Foydalanuvchi ruxsati yo'q tugmani bosib xato oladi
Sababi: faqat route himoyalangan, UI (menu/tugma) rol bo'yicha yashirilmagan 2.13-bob. Yechimi: <Can> / menyu filtri — uch qatlam izchil (Misol 16).
Xato 10 — Har harf yozilganda qidiruv so'rovi yuboriladi (server yuki)
Sababi: search input to'g'ridan-to'g'ri queryKeyga bog'langan, debounce yo'q 2.15-bob. Yechimi: useDebounce(search, 300) — kutib, bitta so'rov (Misol 17, 11.7).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Routing 11.9-bob: layout (Outlet), protected routes, nested — dashboard skeleti.
- Forma + Zod 11.10-bob: login, CRUD formalari (RHF + Zod).
- Hooks (11.5, 11.7): useAuth, useMediaQuery, useLocalStorage (auth, responsive, persist).
- Code splitting 11.8-bob: har sahifa lazy (tez yuklash).
- Error boundary/portal 11.12-bob: global error, modal (o'chirish tasdiq).
- TypeScript 11.14-bob: type-safe (auth, props, API).
- Data fetching 12.4-bob: TanStack Query (ma'lumot, mutation, cache, invalidate, optimistic — 2.14).
- Veb-xavfsizlik (14-QISM): token saqlash (localStorage vs httpOnly cookie), XSS/CSRF 2.11-bob.
- i18n 8.30-bob: frontend ko'p til (react-i18next) — backend i18n bilan bir tamoyil 2.16-bob.
- Backend (5.15-5.17): auth, JWT, access/refresh token, RBAC — frontend bilan ulanadi (2.12, 2.13).
8. Eng yaxshi amaliyotlar (best practices)
- Feature-based tuzilish (katta loyiha — bog'liq narsalar birga — 2.1, 11.3).
- Layout + Outlet (sidebar/header bir marta — DRY — 2.2).
- Responsive (useMediaQuery + mobil sidebar — 2.3).
- Auth: Context + persist + isLoading (token saqlash, boshlang'ich tekshiruv — 2.5).
- Protected + role routes (Outlet bilan; backend bilan — 2.6).
- API instance + interceptors (token/401 markazlashgan — 2.7).
- 4 holat (loading/error/empty/data — har sahifa — 2.8).
- CRUD naqshi (ro'yxat/forma/o'chirish — izchil — 2.9).
- Production: error boundary + lazy + a11y + types (sifat checklist — 2.10).
- Provider tartibi (QueryThemeRouterAuth — 2.1, Misol 14).
- Token xavfsizligi (access xotirada + refresh httpOnly cookie; XSS/CSRF tahdidini tushunish — 2.11, 14-QISM).
- Refresh navbati (bir vaqtdagi 401'larni bitta refresh bilan — poygasiz — 2.12, Misol 15).
- Role-based UI (route + menu + tugma — uch qatlam izchil; backend baribir tekshiradi — 2.13).
- Server state — TanStack Query (useState/useEffect emas; invalidate/optimistic — 2.14).
- Jadval URL state + debounce (pagination/sort/filter/search URL'da; qidiruvga debounce — 2.15).
9. Amaliy loyiha: "To'liq Admin Dashboard"
11-QISM'ning capstone loyihasi — barcha bilimni birlashtiruvchi to'liq dashboard.
Maqsad
Autentifikatsiyali, CRUD'li, responsive, production-tayyor admin dashboard yarat (masalan mahsulot/foydalanuvchi boshqaruvi).
Talablar (requirements)
- Layout: sidebar + header + Outlet, responsive (mobil sidebar — Misol 1, 2).
- Auth: login (RHF+Zod), token persist, Context, isLoading (Misol 3, 4).
- Protected + role: himoyalangan sahifalar, admin-only sahifa (Misol 5, 13).
- API qatlami: axios instance + interceptors (token, 401 — Misol 6).
- Ma'lumot holatlari: har sahifa loading/error/empty/data (Misol 7, 2.8).
- CRUD: kamida bitta resurs (mahsulotlar) — ro'yxat/yaratish/tahrir/o'chirish (Misol 8-10).
- Dashboard sahifa: statistika kartalar + kamida bitta chart (recharts — Misol 11, 18).
- Mavzu: dark/light, persist (Misol 12).
- Production: error boundary + lazy routes + toast + a11y (Misol 13, 2.10).
- TypeScript: to'liq type-safe (auth, props, API — 11.14).
- Jadval: pagination + sort + qidiruv (URL state + debounce — Misol 17, 2.15).
- Role-based UI: menyu va amal tugmalari rol bo'yicha (Misol 16, 2.13).
- Refresh (ixtiyoriy, ilg'or): access/refresh token + queue interceptor (Misol 15, 2.12).
Maslahatlar (hint)
- Backend: o'zingiz yozgan NestJS (8-QISM) yoki mock API (json-server, mockapi.io).
- Provider tartibi: QueryThemeRouterAuth (Misol 14, Xato 6).
isLoadingauth — sahifa yangilashda login'ga otmaslik (Xato 1).- Interceptor — token markazlashgan (har so'rovga avtomatik — Misol 6).
- CRUD — bir resurs uchun mukammal qil, keyin boshqasiga mosla (DRY — 2.9).
- Production checklist: error boundary, lazy, a11y, 4 holat — boshidan 2.10-bob.
"Tayyor" mezonlari (acceptance criteria)
- Layout (sidebar+header+Outlet), mobilda responsive.
- Login token protected ishlaydi (persist — yangilashda qoladi).
- Protected + admin-only sahifalar.
- API instance + interceptor (token avtomatik, 401 boshqarilgan).
- Har sahifa 4 holat (loading/error/empty/data).
- To'liq CRUD (ro'yxat/yaratish/tahrir/o'chirish + tasdiq modal).
- Dashboard statistika.
- Mavzu (dark/light, persist).
- Jadval: pagination + sort + qidiruv (URL'da, debounce).
- Role-based UI (menyu/tugma rol bo'yicha yashiringan).
- Dashboard'da kamida bitta chart (recharts/chart.js).
- Error boundary + lazy + toast + a11y.
- To'liq TypeScript (compile toza).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda butun 11-QISM bilimini bitta real loyihada birlashtirdik:
- Arxitektura (qatlamlar, feature-based — 2.1); layout (sidebar+header+Outlet — 2.2); responsive (mobil — 2.3).
- Auth oqimi (logintokenprotected — 2.4); auth state + persist (Context — 2.5); protected + role routes 2.6-bob.
- API qatlami (interceptors — 2.7); 4 holat (loading/error/empty/data — 2.8); CRUD naqshi 2.9-bob; toast/mavzu/production 2.10-bob.
- Token xavfsizligi (localStorage vs httpOnly cookie — 2.11); access/refresh + refresh queue 2.12-bob; role-based UI 2.13-bob.
- Server state (TanStack Query — kesh/invalidate/optimistic — 2.14); jadval (pagination/sort/filter/search — 2.15); breadcrumb/chart/i18n/UI kutubxona 2.16-bob.
Endi siz to'liq, professional, production-tayyor admin dashboard qura olasiz — bu sizning portfoliongizning markaziy loyihasi bo'lishi mumkin. Eng muhimi — siz alohida ko'nikmalarni (routing, forma, auth, state) bir arxitekturada birlashtirishni o'rgandingiz (junior va senior farqi).
Keyingi bob — 11.16-bob: Swiper va animatsiya (Framer Motion). Dashboard funksional, endi uni jonli va chiroyli qilamiz. Swiper (zamonaviy, teginishga sezgir slayder/karusel — mahsulot galereyasi, banner, onboarding) va Framer Motion (React uchun eng kuchli animatsiya kutubxonasi — kirish/chiqish animatsiyalari, gesture, layout animatsiya, micro-interaksiyalar). Animatsiya — UX'ning "his qilinadigan sifati": to'g'ri ishlatilsa, ilova jonli va professional ko'rinadi.
Foydalanilgan rasmiy/ishonchli manbalar
- React rasmiy hujjati — Context, kompozitsiya, shartli render,
useEffectsinxronizatsiya. - React Router rasmiy hujjati — nested layouts (
Outlet), protected routes,useSearchParams,Navigate, lazy routes. - TanStack Query rasmiy hujjati —
useQuery/useMutation, kesh,invalidateQueries, optimistic update,placeholderData. - Axios rasmiy hujjati — instance, request/response interceptors, xatolarni boshqarish.
- React Hook Form + Zod hujjati — forma validatsiyasi,
zodResolver,resetbilan tahrir. - OWASP — JWT saqlash tavsiyalari (localStorage vs cookie), XSS va CSRF himoyasi (14-QISM asosi).
- MDN —
httpOnly/SameSitecookie,Web Storage(localStorage/sessionStorage) xavfsizligi. - WAI-ARIA Authoring Practices — navigatsiya, dialog (modal), fokus boshqaruvi (a11y — 1.9, 11.12).
- recharts va react-i18next rasmiy hujjatlari — grafik vizualizatsiya va ko'p tillilik.
- Dashboard/admin panel dizayn naqshlari (loading/empty holatlar, jadval UX).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!