feat(arayüz): API istemcisi, TanStack Query, ts-rs tip üretimi ve oturum durumu

- Backend: ts-rs ile DTO'lardan TypeScript tipleri (frontend/src/types, npm run types)
- Hata ve health yanıtları tipli yapılara çevrildi
- lib/api.ts: tek API istemcisi, Türkçe ApiError, ağ hatası mesajı
- lib/query.ts: QueryClient (4xx tekrar denenmez, 401 → oturum kapanır)
- lib/auth.ts: useMe, useLogin, useRegister, useLogout
- Vitest: 7 API istemcisi testi (toplam 29)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 00:39:56 +03:00
parent f9be08114d
commit 906f05f62f
41 changed files with 558 additions and 49 deletions
+42
View File
@@ -0,0 +1,42 @@
/**
* TanStack Query ayarları: sunucu verisinin önbelleği, yeniden deneme, yenileme.
*/
import { QueryCache, QueryClient } from '@tanstack/react-query'
import { ApiError } from './api'
/** Önbellek anahtarları tek yerde (yazım hatası olmasın) */
export const queryKeys = {
me: ['me'] as const,
health: ['health'] as const,
household: ['household'] as const,
categories: (includeInactive = false) => ['categories', { includeInactive }] as const,
paymentMethods: (includeInactive = false) => ['payment-methods', { includeInactive }] as const,
}
/** 4xx hataları tekrar denemenin anlamı yok (yanlış istek / yetki); ağ ve 5xx için 2 kez dene. */
function shouldRetry(failureCount: number, error: unknown) {
if (error instanceof ApiError && error.status >= 400 && error.status < 500) return false
return failureCount < 2
}
export const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
// Herhangi bir istek 401 alırsa oturum bitmiş demektir → "me" boşaltılır,
// korumalı sayfalar (Adım 4) kullanıcıyı giriş ekranına yönlendirir.
if (error instanceof ApiError && error.isUnauthorized) {
queryClient.setQueryData(queryKeys.me, null)
}
},
}),
defaultOptions: {
queries: {
retry: shouldRetry,
staleTime: 30_000, // 30 sn içinde aynı veri tekrar istenmez
refetchOnWindowFocus: true, // sekmeye dönünce güncel veri (ör. eşin eklediği harcama)
},
mutations: {
retry: false,
},
},
})