import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { qk } from "../lib/queryKeys"; import { History, RefreshCw } from "lucide-react"; import { api } from "../lib/api"; import { formatEta, formatNumber, quotaActionLabel } from "../lib/format"; import { notify } from "../lib/notifications"; import { notifyYouTubeActionError } from "../lib/youtubeErrors"; import { useWizard } from "./wizardContext"; import Tooltip from "./ui/Tooltip"; import { LoadingState, StateMessage } from "./ui/QueryState"; import { Section } from "./ui/form"; import { SectionLabel } from "./ui/Headings"; import { StatBars, StatTile, StatTiles } from "./ui/StatTiles"; import { notifyActionError } from "../lib/actionErrors"; import { Button } from "./ui/Button"; // Usage & stats page: your sync status, your own API usage and the manual sync actions (they used to // live in Settings → Sync; they moved here so Settings stays config-only). The instance-wide quota // view is the activity page's Quota tab. export default function Stats() { return (
); } // Per-user view: sync status + your own API usage + the manual sync actions. function Overview() { const { t, i18n } = useTranslation(); const lang = i18n.language; const qc = useQueryClient(); // The 403 case needs somewhere to go, or the toast names a fix the user cannot reach from here. const { openWizard } = useWizard(); const status = useQuery({ queryKey: qk.myStatus(), queryFn: api.myStatus }); const usage = useQuery({ queryKey: qk.myUsage(), queryFn: api.myUsage }); const s = status.data; const syncSubs = useMutation({ mutationFn: () => api.syncSubscriptions(), onSuccess: (r: { subscriptions?: number }) => { qc.invalidateQueries({ queryKey: qk.myStatus() }); qc.invalidateQueries({ queryKey: qk.channels() }); notify({ level: "success", message: t("stats.sync.synced", { count: r.subscriptions ?? 0 }), }); }, // The same helper and the same wizard handle as the Channels page: it translates the 403 into // "connect YouTube" WITH a button that opens the wizard — instead of surfacing the route's bare // English detail under a Hungarian UI — and delegates everything else to notifyActionError so // the server's own reason wins, which is what makes the SEC-14 cooldown read as a pace limit // rather than "Sync failed". onError: (e) => notifyYouTubeActionError(e, t("stats.sync.syncFailed"), openWizard), }); const deepAll = useMutation({ mutationFn: () => api.deepAll(true), onSuccess: (r: { updated?: number }) => { qc.invalidateQueries({ queryKey: qk.myStatus() }); qc.invalidateQueries({ queryKey: qk.channels() }); notify({ level: "success", message: t("stats.sync.fullHistoryRequested", { count: r.updated ?? 0 }), }); }, onError: (e) => notifyActionError(e, t("stats.sync.fullHistoryFailed")), }); return ( <>
{s ? ( {s.deep_pending_count > 0 && ( )} ) : status.isError ? ( status.refetch()}> {t("common.loadError")} ) : ( )}
{usage.data ? ( <> {Object.keys(usage.data.by_action).length > 0 && (
{t("stats.sync.byAction")} formatNumber(n, lang)} rows={Object.entries(usage.data.by_action).map(([action, units]) => ({ id: action, label: quotaActionLabel(t, action), value: units, }))} />
)} ) : usage.isError ? ( usage.refetch()}> {t("common.loadError")} ) : usage.isLoading ? ( ) : (
{t("stats.sync.noUsage")}
)}
); }