WisarWisar
Dasturlash kitobi/11-QISM — React47 daqiqa

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

text
  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

text
  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). DashboardLayout komponenti 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

text
  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, sidebarOpen state 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) useMediaQuery 11.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

text
  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/login yuboradi; (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 avtomatik Authorization: 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

text
  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 ilova useAuth() bilan foydalanuvchini, login/logout funksiyalarini oladi (11.7, 11.14: 2.13 — type-safe). Persist (sahifani yangilaganda eslab qolish — 11.7 useLocalStorage): token localStorageda 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/me yoki token'dan) — chunki sahifa yangilanganda React state (user) yo'qoladi, lekin token localStorage'da qoladi undan user qayta olinadi. isLoading muhim — 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

text
  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). ProtectedRouteuseAuth()dan user va isLoadingni oladi: agar isLoading (boshlang'ich tekshiruv davom etmoqda — 2.5) — spinner; agar user yo'q — login'ga yo'naltiriladi (va from saqlanadi — qayta kelish uchun); aks holda <Outlet /> (himoyalangan bola sahifalar — 11.9: 2.7). Rol bilan (RBAC — 5.17 frontend ko'rinishi): RoleRouteallowedRoles prop 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/RoleRouteOutlet bilan (bir guruh route'ni birata himoyalash — DRY); (2) isLoading tekshiruvi 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

text
  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.4

API 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

text
  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

text
  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) birlashadi

CRUD 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

text
  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.

text
  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 cookie

Token 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 (JS localStorage.getItem/setItem bilan o'qiydi/yozadi, sahifa yangilanganda saqlanadi), lekin XSS hujumiga zaif: agar sahifaga zararli skript kirsa (masalan, tekshirilmagan foydalanuvchi kontenti orqali), u localStorageni o'qib tokenni o'g'irlashi mumkin — chunki har qanday JS unga kira oladi. httpOnly cookiedocument.cookie orqali 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 — buni SameSite=Strict/Lax va 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 — httpOnly cookie (uzoq umrli, XSS'dan himoyalangan) — sahifa yangilanganda refresh cookie bilan yangi access token olinadi. Uch nuqta: (1) localStorage o'quv/prototip uchun qulay, lekin production'da XSS xavfini tushuning; (2) httpOnly cookie XSS'dan himoyalaydi, ammo CSRF himoyasi (SameSite) shart; (3) tanlov backend bilan kelishilgan bo'lishi kerak (cookie'ni backend Set-Cookie bilan o'rnatadi). Bu bobda soddalik uchun localStorage ishlatiladi, lekin production dashboard uchun cookie yondashuvi tavsiya etiladi (14-QISM).

2.12. Access/refresh token va refresh interceptor (queue)

text
  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 Authorization header'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), interceptor POST /auth/refresh chaqiradi, 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'rovlar isRefreshing bayrog'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

text
  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) routeRoleRoute admin 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 yoki useAuth()dan hasRole("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)

text
  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 UX

Server 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'lda useState + useEffectda boshqarish murakkab (kesh, dedup, qayta urinish, fonda yangilash) — shuning uchun TanStack Query ishlatiladi. Query (o'qish) — useQuery kesh va holatni (data/isLoading/error — 2.8) avtomatik beradi. Mutation (o'zgartirish — POST/PUT/DELETE) — useMutation; onSuccessda invalidateQueries(["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 bersa onErrorda 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.

text
  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 qiymatlari queryKeyga 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

text
  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 qarab

Qo'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-2 o'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() va t("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

text
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-i18next

4. Batafsil kod namunalari

Misol 1 — Dashboard layout (sidebar + header + Outlet — 2.2)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
// 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)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
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)

tsx
// 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)

tsx
// 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)

tsx
// 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)

tsx
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)

tsx
// 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

text
 har sahifada sidebar/header qayta yozish (takror)
 DashboardLayout + Outlet (bir marta — 2.2)

2) Auth boshlang'ich holat

text
 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

text
 har so'rovda qo'lda token header qo'shish (takror, unutiladi)
 axios interceptor (har so'rovga avtomatik — 2.7)

4) Ma'lumot holatlari

text
 faqat data (loading/error/empty unutilgan — "muzlagan" ekran)
 4 holat: loading/error/empty/data (2.8)

5) Himoya

text
 faqat frontend protected route (API ochiq — 2.4)
 frontend (UX) + backend token tekshiruvi (5.16, 14)

6) Production sifat

text
 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)

text
 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

text
 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

text
 server ma'lumotini useState/useEffect'da qo'lda boshqarish (kesh/retry/refetch yo'q)
 TanStack Query (useQuery/useMutation + invalidate — 2.14)

10) Token saqlash

text
 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)

  1. Layout: sidebar + header + Outlet, responsive (mobil sidebar — Misol 1, 2).
  2. Auth: login (RHF+Zod), token persist, Context, isLoading (Misol 3, 4).
  3. Protected + role: himoyalangan sahifalar, admin-only sahifa (Misol 5, 13).
  4. API qatlami: axios instance + interceptors (token, 401 — Misol 6).
  5. Ma'lumot holatlari: har sahifa loading/error/empty/data (Misol 7, 2.8).
  6. CRUD: kamida bitta resurs (mahsulotlar) — ro'yxat/yaratish/tahrir/o'chirish (Misol 8-10).
  7. Dashboard sahifa: statistika kartalar + kamida bitta chart (recharts — Misol 11, 18).
  8. Mavzu: dark/light, persist (Misol 12).
  9. Production: error boundary + lazy routes + toast + a11y (Misol 13, 2.10).
  10. TypeScript: to'liq type-safe (auth, props, API — 11.14).
  11. Jadval: pagination + sort + qidiruv (URL state + debounce — Misol 17, 2.15).
  12. Role-based UI: menyu va amal tugmalari rol bo'yicha (Misol 16, 2.13).
  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).
  • isLoading auth — 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, useEffect sinxronizatsiya.
  • React Router rasmiy hujjati — nested layouts (Outlet), protected routes, useSearchParams, Navigate, lazy routes.
  • TanStack Query rasmiy hujjatiuseQuery/useMutation, kesh, invalidateQueries, optimistic update, placeholderData.
  • Axios rasmiy hujjati — instance, request/response interceptors, xatolarni boshqarish.
  • React Hook Form + Zod hujjati — forma validatsiyasi, zodResolver, reset bilan tahrir.
  • OWASP — JWT saqlash tavsiyalari (localStorage vs cookie), XSS va CSRF himoyasi (14-QISM asosi).
  • MDNhttpOnly/SameSite cookie, 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!
11.15-bob: Dashboard loyiha — layout, auth — Wisar