perf(arayüz): sayfa bazlı kod bölme, oturum isteği paralel, ana sayfaları önden yükleme

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-13 23:19:57 +03:00
parent 57272a4c7f
commit 8ee3672d57
7 changed files with 152 additions and 57 deletions
+2 -1
View File
@@ -68,7 +68,8 @@ npm run db:restore -- <dosya|latest> <hedef_db> [--force]
- Frontend: Tailwind v4 (`src/index.css` token'ları), shadcn/ui (style `radix-nova`, `cn` paketi), `@/` → `src/`. Renkler: `bg-needs`/`text-needs-fg`/`bg-needs-soft` (wants, culture, unexpected aynı), `text-income`/`text-expense`/`text-warning`. SVG/JS'te (Recharts vb.) ham değişken kullan: `var(--needs)`, `var(--primary)` — `@theme inline` yüzünden `--color-*` derlenmiş CSS'te olmayabilir. Tutarlarda `money` sınıfı. Butonlar ≥44 px (shadcn boyutları büyütüldü). - Frontend: Tailwind v4 (`src/index.css` token'ları), shadcn/ui (style `radix-nova`, `cn` paketi), `@/` → `src/`. Renkler: `bg-needs`/`text-needs-fg`/`bg-needs-soft` (wants, culture, unexpected aynı), `text-income`/`text-expense`/`text-warning`. SVG/JS'te (Recharts vb.) ham değişken kullan: `var(--needs)`, `var(--primary)` — `@theme inline` yüzünden `--color-*` derlenmiş CSS'te olmayabilir. Tutarlarda `money` sınıfı. Butonlar ≥44 px (shadcn boyutları büyütüldü).
- API: `lib/api.ts` (`api.get/post/...`, hata → `ApiError` Türkçe `message`), `lib/query.ts` (QueryClient + `queryKeys`), `lib/auth.ts` (`useMe` → undefined/null/MeResponse, `useLogin`/`useRegister`/`useLogout`). - API: `lib/api.ts` (`api.get/post/...`, hata → `ApiError` Türkçe `message`), `lib/query.ts` (QueryClient + `queryKeys`), `lib/auth.ts` (`useMe` → undefined/null/MeResponse, `useLogin`/`useRegister`/`useLogout`).
- Tipler: `frontend/src/types/` **elle düzenlenmez**; backend'de DTO'ya `#[derive(TS)] #[ts(export)]` ekle → `npm run types`. İstek yapılarında `optional_fields`. - Tipler: `frontend/src/types/` **elle düzenlenmez**; backend'de DTO'ya `#[derive(TS)] #[ts(export)]` ekle → `npm run types`. İstek yapılarında `optional_fields`.
- Sayfalar: `src/router.tsx`; adresler `components/layout/nav-items.ts` (`paths`); sayfa bileşenleri `features/<alan>/page.tsx`; iskelet `components/layout/app-layout.tsx`. Sayfa başlığı `PageHeader` (sekme başlığını da ayarlar). Metinler `lib/strings.ts` (`t`). - Sayfalar: `src/router.tsx` — her sayfa **ayrı JS parçası** (`lazy: page(() => import(...), 'XPage')`; yeni sayfa ekleyince statik import etme). `main.tsx` `prefetchMe` (oturum isteği sayfa koduyla paralel), `AppLayout` alt menü sayfalarını boşta önden indirir + gezinmede ince ilerleme çizgisi. İlk yükleme yedeği `Splash`.
- Sayfa adresleri: adresler `components/layout/nav-items.ts` (`paths`); sayfa bileşenleri `features/<alan>/page.tsx`; iskelet `components/layout/app-layout.tsx`. Sayfa başlığı `PageHeader` (sekme başlığını da ayarlar). Metinler `lib/strings.ts` (`t`).
- Yönlendirme: `RequireAuth` (oturum yok → `/giris?donus=`), `RequireGuest` (oturum var → `donus` ya da panel; formlar ayrıca navigate etmez). `donus` daima `safeReturnPath` ile. - Yönlendirme: `RequireAuth` (oturum yok → `/giris?donus=`), `RequireGuest` (oturum var → `donus` ya da panel; formlar ayrıca navigate etmez). `donus` daima `safeReturnPath` ile.
- Geliştirmede tasarım önizlemesi: `/dev/tasarim`. - Geliştirmede tasarım önizlemesi: `/dev/tasarim`.
- Formlar: react-hook-form + zod (`mode: 'onTouched'`), alanlar `components/form-field.tsx` (`FormField`, `PasswordInput`, `FormError`); sunucu hatası `setError` ile ilgili alana. Bildirim: `toast` (sonner, `components/toaster.tsx`). - Formlar: react-hook-form + zod (`mode: 'onTouched'`), alanlar `components/form-field.tsx` (`FormField`, `PasswordInput`, `FormError`); sunucu hatası `setError` ile ilgili alana. Bildirim: `toast` (sonner, `components/toaster.tsx`).
+2 -1
View File
@@ -1,7 +1,7 @@
# 🧠 Proje Hafızası # 🧠 Proje Hafızası
## ▶️ Sıradaki Adım ## ▶️ Sıradaki Adım
**Faz 10 – Adım 4:** Performans — `npm run build` çıktısında paket boyutlarını ölç (ana JS, Recharts/SheetJS zaten lazy), sayfa bazlı kod bölme (`React.lazy` route), Axum statik dosyalarda `/assets/*` (hash'li) için `Cache-Control: public, max-age=31536000, immutable`, `index.html` için `no-cache`; sıkıştırma zaten var. Önce/sonra ölçümü raporla. **Faz 10 – Adım 5:** PWA — `vite-plugin-pwa` (gerekçe: manifest + Workbox service worker üretimi; CLAUDE.md yığınında var): manifest (ad, kısa ad, tema rengi, `display: standalone`, `start_url: /`, ikonlar `public/icon-192/512/maskable` zaten var), service worker yalnız uygulama kabuğu + hash'li `/assets/*` (precache), **`/api` asla önbelleğe alınmaz** (NetworkOnly), SPA gezinme `index.html` (navigateFallback, `/api` hariç), güncelleme bildirimi (`registerType: 'prompt'` → sonner "Yeni sürüm var — Yenile"), çevrimdışıyken anlaşılır mesaj. Axum: `sw.js` ve `manifest.webmanifest` `no-cache`. iOS: apple-touch-icon zaten var. Önce kullanıcıya kısa özet.
**Bekleyen (kullanıcıda):** Faz 5 – Adım 5 canlı yedek kurulumu (`docs/DEPLOY.md` §6.1–6.3) → çıktılar gelince Faz 5 ✅. **Bekleyen (kullanıcıda):** Faz 5 – Adım 5 canlı yedek kurulumu (`docs/DEPLOY.md` §6.1–6.3) → çıktılar gelince Faz 5 ✅.
## 📌 Alınan Kararlar ## 📌 Alınan Kararlar
@@ -151,3 +151,4 @@
- **Faz 10 – Adım 1 (2026-11-13):** Ayarlar menüsü (`features/settings/page.tsx`: Kategoriler, Ödeme türleri, Veri ve yedek + tema) ve alt sayfalar (`subpage.tsx`, `catalog-pages.tsx`, `data-page.tsx`); ekstre sayfası "‹ Veri ve yedek"e döner. `catalog-editor.tsx`: satırda ↑ ↓ ✎ (44 px, `aria-label` adlı), düzenleme formu ad + emoji (hızlı seçim paleti, `aria-pressed`), Pasifleştir/Yeniden etkinleştir + toast "Geri al" (sürümsüz PATCH), aynı ad hatası backend'den. `lib/catalog.ts` mutasyonları `['categories'|'payment-methods']` + rapor/dönem önbelleğini yeniler. Telefonda grup başlığı bölünüyordu → açıklama alta. Frontend 136 test; e2e 25 kontrol (DB silindi). - **Faz 10 – Adım 1 (2026-11-13):** Ayarlar menüsü (`features/settings/page.tsx`: Kategoriler, Ödeme türleri, Veri ve yedek + tema) ve alt sayfalar (`subpage.tsx`, `catalog-pages.tsx`, `data-page.tsx`); ekstre sayfası "‹ Veri ve yedek"e döner. `catalog-editor.tsx`: satırda ↑ ↓ ✎ (44 px, `aria-label` adlı), düzenleme formu ad + emoji (hızlı seçim paleti, `aria-pressed`), Pasifleştir/Yeniden etkinleştir + toast "Geri al" (sürümsüz PATCH), aynı ad hatası backend'den. `lib/catalog.ts` mutasyonları `['categories'|'payment-methods']` + rapor/dönem önbelleğini yeniler. Telefonda grup başlığı bölünüyordu → açıklama alta. Frontend 136 test; e2e 25 kontrol (DB silindi).
- **Faz 10 – Adım 2 (2026-11-13):** Backend: `PATCH /auth/me` (ad), `POST /auth/me/password` (mevcut şifre + limiter `password:<uid>`, `sessions_valid_from` → diğer oturumlar 401, bu cihaza yeni çerez, "beni hatırla" eski çerezin `exp-iat`'ından), `PATCH /household` (sahip: ad, `budget_split_pct` toplam 100, `budget_suggestion`), `DELETE /household/members/{id}` (sahip; kendini/sahibi çıkaramaz; e-posta `#removed-<id>` → yeniden davet edilebilir). `GET /household` artık `settings` döner. `services/account.rs`. Testler `tests/account.rs` (4) + 2 bilinçli bozma yakalandı. Frontend: `/ayarlar/hane` (ad, üyeler + çıkar onaylı, davet oluştur → panoya kopyala / paylaş / iptal), `/ayarlar/hesap` (ad, e-posta salt okunur, şifre formu: zod tekrar + farklı, sunucu hatası alana), `/ayarlar/butce` (4 yüzde canlı toplam, 50/30/10/10 düğmesi, yöntem radyo; üye salt okunur). Öneri seçici ve sihirbaz hanenin yöntemiyle açılır (veri yoksa yüzdelere düşer). E2E 28 kontrol: **davet bağlantısıyla gerçek kayıt** dahil (`REGISTRATION=invite_only`). Not: başsız Chrome'da pano için `Emulation.setFocusEmulationEnabled`. Backend 205, frontend 140 test. - **Faz 10 – Adım 2 (2026-11-13):** Backend: `PATCH /auth/me` (ad), `POST /auth/me/password` (mevcut şifre + limiter `password:<uid>`, `sessions_valid_from` → diğer oturumlar 401, bu cihaza yeni çerez, "beni hatırla" eski çerezin `exp-iat`'ından), `PATCH /household` (sahip: ad, `budget_split_pct` toplam 100, `budget_suggestion`), `DELETE /household/members/{id}` (sahip; kendini/sahibi çıkaramaz; e-posta `#removed-<id>` → yeniden davet edilebilir). `GET /household` artık `settings` döner. `services/account.rs`. Testler `tests/account.rs` (4) + 2 bilinçli bozma yakalandı. Frontend: `/ayarlar/hane` (ad, üyeler + çıkar onaylı, davet oluştur → panoya kopyala / paylaş / iptal), `/ayarlar/hesap` (ad, e-posta salt okunur, şifre formu: zod tekrar + farklı, sunucu hatası alana), `/ayarlar/butce` (4 yüzde canlı toplam, 50/30/10/10 düğmesi, yöntem radyo; üye salt okunur). Öneri seçici ve sihirbaz hanenin yöntemiyle açılır (veri yoksa yüzdelere düşer). E2E 28 kontrol: **davet bağlantısıyla gerçek kayıt** dahil (`REGISTRATION=invite_only`). Not: başsız Chrome'da pano için `Emulation.setFocusEmulationEnabled`. Backend 205, frontend 140 test.
- **Faz 10 – Adım 3 (2026-11-13):** Tarama betiği (scratchpad `e2e/scan.mjs`): 21 sayfa × telefon açık/koyu + masaüstü (+ boş hane, giriş/kayıt); sayfa içi denetim (adsız denetim, <44 px hedef, canvas ile oklch→sRGB kontrast, tek h1, alt, lang, taşma) + Tab ile odak halkası. İlk sonuç 89 kontrast / 75 küçük hedef / 27 adsız / 3 h1 → benzersiz: muted yazı 4,35 (→ token 0.52), yeşil 4,37 (→ #166534), `sm` düğmeler 36 px (→ pointer-coarse 44), GFM görev kutusu adsız (→ "Tamamlandı/Yapılacak"), rapor 2 h1 (→ `shiftHeadings`). **Ekran görüntüsünden:** telefonda Defter formu taşıyordu (ızgara öğesi `min-width:auto` 425 px) — otomatik kontrol kaçırdı çünkü mobil öykünmede `innerWidth` da büyür → kontrol `screen.width`/390 ile; Raporlar grafik kartları da taşıyordu → `Section` + rapor bölümüne `min-w-0`. Eklenen: %80 bütçe uyarısı (`lib/budget-level.ts`), Raporlar boş durumu (dönem yok → Dönemi başlat / Ekstreden aktar; filtre boş → Filtreleri temizle), tutarlarda bölünmez boşluk. Sonra: kontrast/adsız/h1/telefon hedef 0. Frontend 142 test. - **Faz 10 – Adım 3 (2026-11-13):** Tarama betiği (scratchpad `e2e/scan.mjs`): 21 sayfa × telefon açık/koyu + masaüstü (+ boş hane, giriş/kayıt); sayfa içi denetim (adsız denetim, <44 px hedef, canvas ile oklch→sRGB kontrast, tek h1, alt, lang, taşma) + Tab ile odak halkası. İlk sonuç 89 kontrast / 75 küçük hedef / 27 adsız / 3 h1 → benzersiz: muted yazı 4,35 (→ token 0.52), yeşil 4,37 (→ #166534), `sm` düğmeler 36 px (→ pointer-coarse 44), GFM görev kutusu adsız (→ "Tamamlandı/Yapılacak"), rapor 2 h1 (→ `shiftHeadings`). **Ekran görüntüsünden:** telefonda Defter formu taşıyordu (ızgara öğesi `min-width:auto` 425 px) — otomatik kontrol kaçırdı çünkü mobil öykünmede `innerWidth` da büyür → kontrol `screen.width`/390 ile; Raporlar grafik kartları da taşıyordu → `Section` + rapor bölümüne `min-w-0`. Eklenen: %80 bütçe uyarısı (`lib/budget-level.ts`), Raporlar boş durumu (dönem yok → Dönemi başlat / Ekstreden aktar; filtre boş → Filtreleri temizle), tutarlarda bölünmez boşluk. Sonra: kontrast/adsız/h1/telefon hedef 0. Frontend 142 test.
- **Faz 10 – Adım 4 (2026-11-13):** Önbellek başlıkları zaten doğruydu (Faz 5: `/assets/*` 1 yıl immutable, HTML no-cache, sıkıştırma). Ana JS 950 KB (gz 284) tek paket → sayfa bazlı `lazy` route: ana 402 KB (gz 128), sayfalar 2–23 KB; Markdown (49 KB gz) ve zod/form (40 KB gz) yalnız kullanan sayfada. İlk sürümde Defter yavaşlamıştı (lazy route → `/auth/me` sıralı) → `prefetchMe` ile paralel; `HydrateFallback: Splash`. Boşta alt menü sayfalarını önden indirme + `useNavigation` ilerleme çizgisi. Ölçüm (scratchpad `e2e/perf.mjs`: yavaş 4G 1,6 Mbps/150 ms + CPU 4×, soğuk; eski sürüm `git worktree` ile ayrı derlenip aynı koşulda, 2 tekrar ort.): Giriş 2,97→2,23 sn, Panel 2,42→2,03, Raporlar 2,36→2,11, Defter 2,33→2,37 (aynı); hazırda JS 295→171–221 KB. Regresyon: katalog (25) + rapor (21) e2e geçti. Not: e2e rapor başlığı artık `.md-h1`.
+2 -2
View File
@@ -90,5 +90,5 @@
- ✅ 1. Ayarlar yeniden düzeni (`/ayarlar` menü + tema) · **Kategori yönetimi** (grup grup ekle, ad/emoji düzenle, sırala, pasifleştir) · **Ödeme türü yönetimi** · Veri sayfası `/ayarlar/veri` - ✅ 1. Ayarlar yeniden düzeni (`/ayarlar` menü + tema) · **Kategori yönetimi** (grup grup ekle, ad/emoji düzenle, sırala, pasifleştir) · **Ödeme türü yönetimi** · Veri sayfası `/ayarlar/veri`
- ✅ 2. Hane ve hesap: hane adı, üyeler, **davet bağlantısı** (oluştur/kopyala/iptal), üye çıkarma; profil adı, **şifre değiştirme**; varsayılan bütçe yüzdeleri + öneri yöntemi - ✅ 2. Hane ve hesap: hane adı, üyeler, **davet bağlantısı** (oluştur/kopyala/iptal), üye çıkarma; profil adı, **şifre değiştirme**; varsayılan bütçe yüzdeleri + öneri yöntemi
- ✅ 3. Arayüz cilası ve erişilebilirlik: tüm sayfaların telefon/masaüstü/koyu tema taraması, boş durumlar, skeleton, %80 bütçe uyarısı, odak/klavye, kontrast, onay diyalogları - ✅ 3. Arayüz cilası ve erişilebilirlik: tüm sayfaların telefon/masaüstü/koyu tema taraması, boş durumlar, skeleton, %80 bütçe uyarısı, odak/klavye, kontrast, onay diyalogları
- 🔄 4. Performans: sayfa bazlı kod bölme (lazy route), statik dosyalarda uzun önbellek, paket boyutu - ✅ 4. Performans: sayfa bazlı kod bölme (lazy route), statik dosyalarda uzun önbellek, paket boyutu
- ⏳ 5. PWA: manifest + service worker (uygulama kabuğu; `/api` asla önbelleğe alınmaz), kurulum, güncelleme bildirimi - 🔄 5. PWA: manifest + service worker (uygulama kabuğu; `/api` asla önbelleğe alınmaz), kurulum, güncelleme bildirimi
+43 -1
View File
@@ -1,5 +1,6 @@
import { useEffect } from 'react'
import { LogOut } from 'lucide-react' import { LogOut } from 'lucide-react'
import { NavLink, Outlet, ScrollRestoration, useLocation } from 'react-router' import { NavLink, Outlet, ScrollRestoration, useLocation, useNavigation } from 'react-router'
import { Logo } from '@/components/logo' import { Logo } from '@/components/logo'
import { ThemeToggle } from '@/components/theme-toggle' import { ThemeToggle } from '@/components/theme-toggle'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
@@ -14,8 +15,10 @@ import { bottomNav, moreNav, paths, sideNav } from './nav-items'
* - Masaüstü (≥ md): solda kenar çubuğu * - Masaüstü (≥ md): solda kenar çubuğu
*/ */
export function AppLayout() { export function AppLayout() {
usePrefetchMainPages()
return ( return (
<div className="min-h-dvh"> <div className="min-h-dvh">
<NavigationProgress />
<Sidebar /> <Sidebar />
{/* Mobil üst çubuk */} {/* Mobil üst çubuk */}
@@ -37,6 +40,45 @@ export function AppLayout() {
) )
} }
/**
* Alt menüdeki ana sayfaların kodu, tarayıcı boşta kalınca önceden indirilir
* (ilk açılışı yavaşlatmaz; menüde gezinirken bekleme olmaz).
*/
function usePrefetchMainPages() {
useEffect(() => {
const load = () => {
void import('@/features/dashboard/page')
void import('@/features/ledger/page')
void import('@/features/planning/page')
void import('@/features/savings/page')
}
if ('requestIdleCallback' in window) {
const id = window.requestIdleCallback(load, { timeout: 5000 })
return () => window.cancelIdleCallback(id)
}
const id = setTimeout(load, 2000)
return () => clearTimeout(id)
}, [])
}
/** Sayfa kodu inerken (yavaş bağlantı) üstte ince ilerleme çizgisi; anlık geçişte görünmez */
function NavigationProgress() {
const loading = useNavigation().state === 'loading'
return (
<div
role="progressbar"
aria-label={t.common.loading}
aria-hidden={!loading}
className={cn(
'pointer-events-none fixed inset-x-0 top-0 z-50 h-0.5 origin-left bg-primary transition-[transform,opacity] print:hidden',
loading
? 'scale-x-75 opacity-100 delay-150 duration-[2s] ease-out'
: 'scale-x-100 opacity-0 duration-300',
)}
/>
)
}
function Sidebar() { function Sidebar() {
return ( return (
<aside className="fixed inset-y-0 left-0 z-30 hidden w-64 flex-col border-r bg-sidebar md:flex print:hidden"> <aside className="fixed inset-y-0 left-0 z-30 hidden w-64 flex-col border-r bg-sidebar md:flex print:hidden">
+12 -6
View File
@@ -6,7 +6,7 @@
* - `null` → oturum yok * - `null` → oturum yok
* - `MeResponse`→ giriş yapılmış * - `MeResponse`→ giriş yapılmış
*/ */
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query'
import type { LoginRequest } from '@/types/LoginRequest' import type { LoginRequest } from '@/types/LoginRequest'
import type { MeResponse } from '@/types/MeResponse' import type { MeResponse } from '@/types/MeResponse'
import type { RegisterRequest } from '@/types/RegisterRequest' import type { RegisterRequest } from '@/types/RegisterRequest'
@@ -23,12 +23,18 @@ async function fetchMe(): Promise<MeResponse | null> {
} }
} }
const meQuery = { queryKey: queryKeys.me, queryFn: fetchMe, staleTime: 5 * 60_000 }
export function useMe() { export function useMe() {
return useQuery({ return useQuery(meQuery)
queryKey: queryKeys.me, }
queryFn: fetchMe,
staleTime: 5 * 60_000, /**
}) * Açılışta oturumu hemen sor: sayfa kodu (ayrı JS parçası) inerken istek paralel yürür,
* sıralı bekleme olmaz. `useMe` aynı sorguyu önbellekten alır.
*/
export function prefetchMe(client: QueryClient) {
void client.prefetchQuery(meQuery)
} }
export function useLogin() { export function useLogin() {
+2
View File
@@ -5,11 +5,13 @@ import { RouterProvider } from 'react-router'
import './index.css' import './index.css'
import { queryClient } from '@/lib/query' import { queryClient } from '@/lib/query'
import { Toaster } from '@/components/toaster' import { Toaster } from '@/components/toaster'
import { prefetchMe } from '@/lib/auth'
import { installPrintTheme } from '@/lib/print' import { installPrintTheme } from '@/lib/print'
import { ThemeProvider } from '@/lib/theme' import { ThemeProvider } from '@/lib/theme'
import { router } from './router' import { router } from './router'
installPrintTheme() installPrintTheme()
prefetchMe(queryClient)
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
+89 -46
View File
@@ -1,66 +1,106 @@
import type { ComponentType } from 'react'
import { createBrowserRouter, type RouteObject } from 'react-router' import { createBrowserRouter, type RouteObject } from 'react-router'
import { AppLayout } from '@/components/layout/app-layout' import { AppLayout } from '@/components/layout/app-layout'
import { paths } from '@/components/layout/nav-items' import { paths } from '@/components/layout/nav-items'
import { RequireAuth, RequireGuest } from '@/components/layout/require-auth' import { RequireAuth, RequireGuest, Splash } from '@/components/layout/require-auth'
import { LoginPage } from '@/features/auth/login-page'
import { RegisterPage } from '@/features/auth/register-page' /**
import { DashboardPage } from '@/features/dashboard/page' * Sayfalar ayrı JS parçaları: yalnızca açılınca indirilir (ilk açılış küçük kalır).
import { DesignPreview } from '@/features/dev/design-preview' * React Router `lazy`: yeni sayfanın kodu inerken eski sayfa ekranda kalır.
import { NotFoundPage } from '@/features/errors/not-found-page' */
import { LedgerPage } from '@/features/ledger/page' const page =
import { MorePage } from '@/features/more/page' <K extends string>(load: () => Promise<Record<K, ComponentType>>, name: K) =>
import { PeriodWizardPage } from '@/features/period-wizard/page' async () => ({ Component: (await load())[name] })
import { PlanningPage } from '@/features/planning/page'
import { ReportsPage } from '@/features/reports/page'
import { ClosePeriodPage } from '@/features/review/close-page'
import { MonthlyReportPage } from '@/features/review/report-page'
import { ReviewPage } from '@/features/review/page'
import { AssetPage } from '@/features/savings/asset-page'
import { SavingsPage } from '@/features/savings/page'
import { CategoriesPage, PaymentMethodsPage } from '@/features/settings/catalog-pages'
import { AccountPage } from '@/features/settings/account-page'
import { BudgetDefaultsPage } from '@/features/settings/budget-page'
import { DataPage } from '@/features/settings/data-page'
import { HouseholdPage } from '@/features/settings/household-page'
import { SettingsPage } from '@/features/settings/page'
import { StatementPage } from '@/features/statement/page'
const routes: RouteObject[] = [ const routes: RouteObject[] = [
// Oturumsuz // Oturumsuz
{ {
element: <RequireGuest />, element: <RequireGuest />,
HydrateFallback: Splash,
children: [ children: [
{ path: paths.login, element: <LoginPage /> }, { path: paths.login, lazy: page(() => import('@/features/auth/login-page'), 'LoginPage') },
{ path: paths.register, element: <RegisterPage /> }, {
path: paths.register,
lazy: page(() => import('@/features/auth/register-page'), 'RegisterPage'),
},
], ],
}, },
// Oturumlu (iskelet: kenar çubuğu / alt menü) // Oturumlu (iskelet: kenar çubuğu / alt menü)
{ {
element: <RequireAuth />, element: <RequireAuth />,
HydrateFallback: Splash,
children: [ children: [
{ {
element: <AppLayout />, element: <AppLayout />,
children: [ children: [
{ index: true, element: <DashboardPage /> }, { index: true, lazy: page(() => import('@/features/dashboard/page'), 'DashboardPage') },
{ path: paths.ledger, element: <LedgerPage /> }, { path: paths.ledger, lazy: page(() => import('@/features/ledger/page'), 'LedgerPage') },
{ path: paths.planning, element: <PlanningPage /> }, {
{ path: paths.periodWizard, element: <PeriodWizardPage /> }, path: paths.planning,
{ path: paths.savings, element: <SavingsPage /> }, lazy: page(() => import('@/features/planning/page'), 'PlanningPage'),
{ path: paths.asset, element: <AssetPage /> }, },
{ path: paths.review, element: <ReviewPage /> }, {
{ path: paths.closePeriod, element: <ClosePeriodPage /> }, path: paths.periodWizard,
{ path: paths.monthlyReport, element: <MonthlyReportPage /> }, lazy: page(() => import('@/features/period-wizard/page'), 'PeriodWizardPage'),
{ path: paths.reports, element: <ReportsPage /> }, },
{ path: paths.settings, element: <SettingsPage /> }, {
{ path: paths.settingsCategories, element: <CategoriesPage /> }, path: paths.savings,
{ path: paths.settingsPaymentMethods, element: <PaymentMethodsPage /> }, lazy: page(() => import('@/features/savings/page'), 'SavingsPage'),
{ path: paths.settingsData, element: <DataPage /> }, },
{ path: paths.settingsHousehold, element: <HouseholdPage /> }, {
{ path: paths.settingsAccount, element: <AccountPage /> }, path: paths.asset,
{ path: paths.settingsBudget, element: <BudgetDefaultsPage /> }, lazy: page(() => import('@/features/savings/asset-page'), 'AssetPage'),
{ path: paths.statement, element: <StatementPage /> }, },
{ path: paths.more, element: <MorePage /> }, { path: paths.review, lazy: page(() => import('@/features/review/page'), 'ReviewPage') },
{ path: '*', element: <NotFoundPage /> }, {
path: paths.closePeriod,
lazy: page(() => import('@/features/review/close-page'), 'ClosePeriodPage'),
},
{
path: paths.monthlyReport,
lazy: page(() => import('@/features/review/report-page'), 'MonthlyReportPage'),
},
{
path: paths.reports,
lazy: page(() => import('@/features/reports/page'), 'ReportsPage'),
},
{
path: paths.settings,
lazy: page(() => import('@/features/settings/page'), 'SettingsPage'),
},
{
path: paths.settingsCategories,
lazy: page(() => import('@/features/settings/catalog-pages'), 'CategoriesPage'),
},
{
path: paths.settingsPaymentMethods,
lazy: page(() => import('@/features/settings/catalog-pages'), 'PaymentMethodsPage'),
},
{
path: paths.settingsData,
lazy: page(() => import('@/features/settings/data-page'), 'DataPage'),
},
{
path: paths.settingsHousehold,
lazy: page(() => import('@/features/settings/household-page'), 'HouseholdPage'),
},
{
path: paths.settingsAccount,
lazy: page(() => import('@/features/settings/account-page'), 'AccountPage'),
},
{
path: paths.settingsBudget,
lazy: page(() => import('@/features/settings/budget-page'), 'BudgetDefaultsPage'),
},
{
path: paths.statement,
lazy: page(() => import('@/features/statement/page'), 'StatementPage'),
},
{ path: paths.more, lazy: page(() => import('@/features/more/page'), 'MorePage') },
{
path: '*',
lazy: page(() => import('@/features/errors/not-found-page'), 'NotFoundPage'),
},
], ],
}, },
], ],
@@ -69,7 +109,10 @@ const routes: RouteObject[] = [
// Tasarım önizlemesi yalnızca geliştirmede (canlı derlemeye girmez) // Tasarım önizlemesi yalnızca geliştirmede (canlı derlemeye girmez)
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
routes.unshift({ path: '/dev/tasarim', element: <DesignPreview /> }) routes.unshift({
path: '/dev/tasarim',
lazy: page(() => import('@/features/dev/design-preview'), 'DesignPreview'),
})
} }
export const router = createBrowserRouter(routes) export const router = createBrowserRouter(routes)