Files
codexbar-mobile/hooks/useClaudeUsage.ts
T
space d5b0f9c833
CodexBar Mobile Build / build-android (push) Successful in 31m31s
CodexBar Mobile Build / release (push) Failing after 23s
feat: production-ready UI with onboarding wizard and dashboard
- Onboarding flow (welcome -> Codex setup -> Claude setup -> done) with
  dark-themed screens and step indicators; completion stored in SecureStore
- Smart root redirect checks onboarding state on launch
- Dashboard tab shows both services at a glance via ServiceStatusCard;
  useFocusEffect + reload() keeps it fresh when credentials change in tabs
- Settings tab manages credentials and exposes re-run setup wizard
- Polished Codex and Claude detail screens with service headers and empty states
- 4-tab layout (Home, Codex, Claude, Settings) with dark-mode tab bar
- Both hooks gain reload() for cross-tab credential refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 17:52:06 +02:00

116 lines
3.2 KiB
TypeScript

import { useState, useCallback, useEffect } from "react";
import { fetchClaudeOrgs, fetchClaudeUsage } from "@/lib/api/claudeApi";
import {
loadClaudeSessionKey,
saveClaudeSessionKey,
clearClaudeSessionKey,
} from "@/lib/storage";
import type { ClaudeUsageResponse } from "@/types/claude";
type Status = "idle" | "loading" | "success" | "error";
async function doFetch(
sessionKey: string,
set: {
sessionKey: (v: string | null) => void;
usage: (v: ClaudeUsageResponse | null) => void;
status: (v: Status) => void;
error: (v: string | null) => void;
}
) {
set.status("loading");
set.error(null);
try {
const orgs = await fetchClaudeOrgs(sessionKey);
if (!orgs.length) throw new Error("NO_ORGS_FOUND");
const orgUuid = orgs[0].uuid;
const data = await fetchClaudeUsage(sessionKey, 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.sessionKey(null);
}
}
}
export function useClaudeUsage() {
const [sessionKey, setSessionKey] = useState<string | null>(null);
const [pendingKey, setPendingKey] = useState("");
const [usage, setUsage] = useState<ClaudeUsageResponse | null>(null);
const [status, setStatus] = useState<Status>("idle");
const [error, setError] = useState<string | null>(null);
const setters = { sessionKey: setSessionKey, usage: setUsage, status: setStatus, error: setError };
useEffect(() => {
loadClaudeSessionKey().then((stored) => {
if (stored) setSessionKey(stored);
});
}, []);
useEffect(() => {
if (sessionKey) void doFetch(sessionKey, setters);
}, [sessionKey]);
const saveKey = useCallback(async () => {
const trimmed = pendingKey.trim();
if (!trimmed.startsWith("sk-ant-")) {
setError("Session key must start with sk-ant-");
return;
}
await saveClaudeSessionKey(trimmed);
setSessionKey(trimmed);
setPendingKey("");
setError(null);
}, [pendingKey]);
const refresh = useCallback(async () => {
if (!sessionKey) return;
await doFetch(sessionKey, setters);
}, [sessionKey]);
// Re-reads the session key from storage and fetches fresh data. Call from
// useFocusEffect so the dashboard stays current when the key is saved from
// another tab. If the string value is unchanged, React bails out on setState
// so we trigger the fetch directly.
const reload = useCallback(async () => {
const stored = await loadClaudeSessionKey();
if (!stored) {
setSessionKey(null);
setUsage(null);
setStatus("idle");
return;
}
// If already set to the same string, setState bails out and [sessionKey]
// effect won't fire, so call the fetch directly.
setSessionKey(stored);
void doFetch(stored, setters);
}, []);
const clearKey = useCallback(async () => {
await clearClaudeSessionKey();
setSessionKey(null);
setUsage(null);
setStatus("idle");
setError(null);
}, []);
return {
sessionKey,
pendingKey,
setPendingKey,
usage,
status,
error,
saveKey,
refresh,
reload,
clearKey,
};
}