feat(arayüz): sayfa yönlendirme, korumalı sayfalar ve uygulama iskeleti

- react-router: Türkçe adresler (/defter, /plan, /birikim, ...), 404 sayfası
- Korumalı sayfalar: oturum yoksa /giris?donus=..., girişten sonra geri dönüş
- Açık yönlendirme koruması (safeReturnPath, 10 test)
- İskelet: mobilde 5 öğeli alt menü, masaüstünde sol kenar çubuğu
- Diğer sayfası (değerlendirme, raporlar, ayarlar, tema, çıkış)
- Arayüz metinleri lib/strings.ts'de toplanmaya başlandı
- Tasarım önizlemesi /dev/tasarim (yalnızca geliştirmede)
- Düzeltme: çıkışta önbellek temizliği ekranı güncellemiyordu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 00:46:17 +03:00
parent 906f05f62f
commit c6d4c30461
29 changed files with 690 additions and 74 deletions
@@ -0,0 +1,42 @@
import { Navigate, Outlet, useLocation, useSearchParams } from 'react-router'
import { Logo } from '@/components/logo'
import { useMe } from '@/lib/auth'
import { safeReturnPath } from '@/lib/return-path'
import { t } from '@/lib/strings'
import { paths } from './nav-items'
/** Oturum kontrol edilirken gösterilen tam ekran açılış. */
export function Splash() {
return (
<div className="grid min-h-dvh place-items-center" role="status" aria-label={t.common.loading}>
<Logo size={56} className="animate-pulse" />
</div>
)
}
/** Oturum gerektiren sayfalar. Oturum yoksa → /giris?donus=<şimdiki adres> */
export function RequireAuth() {
const me = useMe()
const location = useLocation()
if (me.isPending) return <Splash />
if (!me.data) {
const back = location.pathname + location.search
const to = back === '/' ? paths.login : `${paths.login}?donus=${encodeURIComponent(back)}`
return <Navigate to={to} replace />
}
return <Outlet />
}
/**
* Yalnızca oturumsuzken: giriş/kayıt.
* Oturum açılınca (giriş/kayıt başarılı olduğunda da) `?donus=` adresine, yoksa panele gönderir.
* Yönlendirme yalnızca burada yapılır — formlar ayrıca `navigate` çağırmaz (yarış durumu olmasın).
*/
export function RequireGuest() {
const me = useMe()
const [params] = useSearchParams()
if (me.isPending) return <Splash />
if (me.data) return <Navigate to={safeReturnPath(params.get('donus'), paths.dashboard)} replace />
return <Outlet />
}