import { useState, useCallback, useEffect } from "react"; import { fetchClaudeOrgs, fetchClaudeUsage } from "@/lib/api/claudeApi"; import { loadClaudeSessionKey, saveClaudeSessionKey, clearClaudeSessionKey, loadClaudeLastActiveOrg, saveClaudeLastActiveOrg, clearClaudeLastActiveOrg, } from "@/lib/storage"; import type { ClaudeAuth, ClaudeUsageResponse } from "@/types/claude"; type Status = "idle" | "loading" | "success" | "error"; async function doFetch( auth: ClaudeAuth, set: { auth: (v: ClaudeAuth | null) => void; usage: (v: ClaudeUsageResponse | null) => void; status: (v: Status) => void; error: (v: string | null) => void; } ) { set.status("loading"); set.error(null); try { let orgUuid = auth.lastActiveOrg; if (!orgUuid) { const orgs = await fetchClaudeOrgs(auth); if (!orgs.length) throw new Error("NO_ORGS_FOUND"); orgUuid = orgs[0].uuid; } const data = await fetchClaudeUsage(auth, orgUuid); set.usage(data); set.status("success"); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR"; set.error(msg); set.status("error"); if (msg === "TOKEN_EXPIRED") { await clearClaudeSessionKey(); set.auth(null); } } } export function useClaudeUsage() { const [auth, setAuth] = useState(null); const [pendingKey, setPendingKey] = useState(""); const [pendingOrg, setPendingOrg] = useState(""); const [usage, setUsage] = useState(null); const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); const setters = { auth: setAuth, usage: setUsage, status: setStatus, error: setError }; useEffect(() => { Promise.all([loadClaudeSessionKey(), loadClaudeLastActiveOrg()]).then( ([key, org]) => { if (key) setAuth({ sessionKey: key, lastActiveOrg: org ?? undefined }); } ); }, []); useEffect(() => { if (auth) void doFetch(auth, setters); }, [auth]); const saveKey = useCallback(async () => { const trimmed = pendingKey.trim(); if (!trimmed.startsWith("sk-ant-")) { setError("Session key must start with sk-ant-"); return; } const orgTrimmed = pendingOrg.trim() || undefined; await saveClaudeSessionKey(trimmed); if (orgTrimmed) await saveClaudeLastActiveOrg(orgTrimmed); else await clearClaudeLastActiveOrg(); setAuth({ sessionKey: trimmed, lastActiveOrg: orgTrimmed }); setPendingKey(""); setPendingOrg(""); setError(null); }, [pendingKey, pendingOrg]); const refresh = useCallback(async () => { if (!auth) return; await doFetch(auth, setters); }, [auth]); const reload = useCallback(async () => { const [key, org] = await Promise.all([loadClaudeSessionKey(), loadClaudeLastActiveOrg()]); if (!key) { setAuth(null); setUsage(null); setStatus("idle"); return; } // Creating a new object always triggers the [auth] useEffect even if values are identical setAuth({ sessionKey: key, lastActiveOrg: org ?? undefined }); }, []); const clearKey = useCallback(async () => { await clearClaudeSessionKey(); setAuth(null); setUsage(null); setStatus("idle"); setError(null); }, []); return { auth, pendingKey, setPendingKey, pendingOrg, setPendingOrg, usage, status, error, saveKey, refresh, reload, clearKey, }; }