import { useCallback, useEffect, useRef, useState } from 'react'; import { AppState, type AppStateStatus } from 'react-native'; import { apiFetch } from '../lib/api'; export type MailAccount = { id: string; email: string; provider: string; title?: string | null; isActive: boolean; paused?: boolean; lastScannedAt: string | null; nextScanAt: string | null; totalBlocked: number; totalScanned: number; scanInterval: number; blockRate: number; lastConnectError?: string | null; lastConnectErrorAt?: string | null; lastIdleHeartbeatAt?: string | null; }; export type DailyStat = { date: string; label: string; count: number; }; export type MailStatusResponse = { connected: boolean; accounts: MailAccount[]; totalBlocked: number; totalScanned: number; dailyStats: DailyStat[]; }; export type Plan = 'free' | 'pro' | 'legend'; export type UseMailStatusReturn = { connected: boolean; accounts: MailAccount[]; totalBlocked: number; totalScanned: number; dailyStats: DailyStat[]; /** Plan-derived account limit: free=1, pro=3, legend=Infinity */ maxAccounts: number; loading: boolean; error: string | null; refresh: () => Promise; }; const POLL_INTERVAL_MS = 30_000; function deriveMaxAccounts(plan: Plan): number { if (plan === 'free') return 1; if (plan === 'pro') return 3; return Infinity; } /** * Fetched GET /api/mail/status mit: * - initialem Fetch on mount * - 30s-Polling solange App im Vordergrund (AppState === 'active') * - manuell triggerbar via refresh() * * TODO: Ersetze Polling durch IDLE-Realtime-Websocket wenn Phase-10-Backend fertig ist. */ export function useMailStatus(plan: Plan): UseMailStatusReturn { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const intervalRef = useRef | null>(null); const appStateRef = useRef(AppState.currentState); const fetchStatus = useCallback(async () => { try { const res = await apiFetch('/api/mail/status'); setData(res); setError(null); } catch (e: any) { console.error('[useMailStatus] fetch failed:', e?.message ?? e); setError(e?.message ?? 'unknown'); } finally { setLoading(false); } }, []); // Polling starten / stoppen je nach AppState const startPolling = useCallback(() => { if (intervalRef.current) return; intervalRef.current = setInterval(() => { fetchStatus(); }, POLL_INTERVAL_MS); }, [fetchStatus]); const stopPolling = useCallback(() => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }, []); useEffect(() => { fetchStatus(); startPolling(); const sub = AppState.addEventListener('change', (nextState: AppStateStatus) => { const wasActive = appStateRef.current === 'active'; const isNowActive = nextState === 'active'; appStateRef.current = nextState; if (!wasActive && isNowActive) { // App kommt in Vordergrund — sofort refreshen + Polling neu starten fetchStatus(); startPolling(); } else if (wasActive && !isNowActive) { // App geht in Hintergrund — Polling stoppen stopPolling(); } }); return () => { stopPolling(); sub.remove(); }; }, [fetchStatus, startPolling, stopPolling]); const maxAccounts = deriveMaxAccounts(plan); return { connected: data?.connected ?? false, accounts: data?.accounts ?? [], totalBlocked: data?.totalBlocked ?? 0, totalScanned: data?.totalScanned ?? 0, dailyStats: data?.dailyStats ?? [], maxAccounts, loading, error, refresh: fetchStatus, }; }