import { useCallback, useEffect, useState } from 'react'; import { apiFetch } from '../lib/api'; import type { CooldownEntry } from '../components/profile/StreakSection'; import type { ApprovedDomain } from '../components/profile/ApprovedDomainsList'; export type SocialStats = { postsCount: number; followersCount: number; }; export type ApprovedDomainsData = { count: number; list: ApprovedDomain[]; }; export type CooldownHistoryData = { items: CooldownEntry[]; nextCursor: string | null; }; export type SosInsightsData = { last30Days: { sessions: number; overcome: number; overcomeRate: number }; helpedBy: { breathing: number; game: number; talk: number; other: number }; topEmotion: string | null; }; export type BackendCooldownEntry = { id: string; startedAt: string; cooldownEndsAt: string; durationMinutes: number; status: 'active' | 'resolved' | 'cancelled'; resolvedAt: string | null; cancelledAt: string | null; reason: string | null; }; function formatDuration(minutes: number): string { if (minutes < 60) return `${minutes}min`; const h = Math.round(minutes / 60); return `${h}h`; } function formatStartedAt(isoString: string): string { const d = new Date(isoString); const day = String(d.getDate()).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0'); return `${day}.${month}.`; } function mapCooldownEntry(raw: BackendCooldownEntry): CooldownEntry { return { id: raw.id, startedAt: formatStartedAt(raw.startedAt), rawStartedAt: raw.startedAt, durationLabel: formatDuration(raw.durationMinutes), status: raw.status, reason: raw.reason, }; } function useFetchOnce( url: string, ): { data: T | null; loading: boolean; error: boolean; reload: () => void } { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [version, setVersion] = useState(0); useEffect(() => { if (!url) { setLoading(false); return; } let cancelled = false; setLoading(true); setError(false); apiFetch(url) .then((res) => { if (cancelled) return; setData(res); }) .catch(() => { if (!cancelled) setError(true); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [url, version]); const reload = useCallback(() => setVersion((v) => v + 1), []); return { data, loading, error, reload }; } export function useSocialStats(userId: string | undefined) { const url = userId ? `/api/social/profile/${userId}` : ''; const { data, loading, error, reload } = useFetchOnce<{ postsCount: number; followersCount: number; }>(url); return { stats: data ? ({ postsCount: data.postsCount, followersCount: data.followersCount } as SocialStats) : null, loading, error, reload, }; } export function useApprovedDomains() { const { data, loading, error, reload } = useFetchOnce( '/api/profile/me/approved-domains', ); return { domains: data, loading, error, reload }; } export function useCooldownHistory() { const { data, loading, error, reload } = useFetchOnce<{ items: BackendCooldownEntry[]; nextCursor: string | null; }>('/api/profile/me/cooldown-history?limit=20'); const mapped: CooldownHistoryData | null = data ? { items: data.items.map(mapCooldownEntry), nextCursor: data.nextCursor, } : null; return { cooldownHistory: mapped, loading, error, reload }; } export function useCooldownHistoryFull() { const { data, loading, error, reload } = useFetchOnce<{ items: BackendCooldownEntry[]; nextCursor: string | null; }>('/api/profile/me/cooldown-history?limit=100'); return { rawCooldowns: data?.items ?? null, loading, error, reload }; } export function useSosInsights() { const { data, loading, error, reload } = useFetchOnce( '/api/profile/me/sos-insights', ); return { sosInsights: data, loading, error, reload }; } export type Demographics = { birthYear: number | null; gender: string | null; maritalStatus: string | null; employmentStatus: string | null; shiftWork: boolean | null; industry: string | null; jobTenure: string | null; bundesland: string | null; city: string | null; }; type DemographicsResponse = Demographics & { consentAt: string | null; withdrawnAt: string | null; }; export type ProtectionCoverageData = { firstProtectionAt: string | null; protectedDays: number; unprotectedDays: number; currentStreakDays: number; longestStreakDays: number; }; export function useProtectionCoverage() { const { data, loading, error, reload } = useFetchOnce( '/api/protection/coverage', ); return { coverage: data, loading, error, reload }; } export function useDemographics() { const { data, loading, error, reload } = useFetchOnce( '/api/profile/me/demographics', ); const demographics: Demographics | null = data ? { birthYear: data.birthYear, gender: data.gender, maritalStatus: data.maritalStatus, employmentStatus: data.employmentStatus, shiftWork: data.shiftWork, industry: data.industry, jobTenure: data.jobTenure, bundesland: data.bundesland, city: data.city, } : null; return { demographics, consentAt: data?.consentAt ?? null, withdrawnAt: data?.withdrawnAt ?? null, loading, error, reload, }; }