[skip ci] Initial commit — CodexBar Mobile Expo app

This commit is contained in:
2026-06-24 16:28:28 +02:00
parent 4f32782eaf
commit 0bcd95ee1b
30 changed files with 3562 additions and 141 deletions
+82
View File
@@ -0,0 +1,82 @@
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";
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);
useEffect(() => {
loadClaudeSessionKey().then((stored) => {
if (stored) setSessionKey(stored);
});
}, []);
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;
setStatus("loading");
setError(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);
setUsage(data);
setStatus("success");
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR";
setError(msg);
setStatus("error");
if (msg === "TOKEN_EXPIRED") {
await clearClaudeSessionKey();
setSessionKey(null);
}
}
}, [sessionKey]);
useEffect(() => {
if (sessionKey) void refresh();
}, [sessionKey]);
const clearKey = useCallback(async () => {
await clearClaudeSessionKey();
setSessionKey(null);
setUsage(null);
setStatus("idle");
setError(null);
}, []);
return {
sessionKey,
pendingKey,
setPendingKey,
usage,
status,
error,
saveKey,
refresh,
clearKey,
};
}
+65
View File
@@ -0,0 +1,65 @@
import { useState, useCallback, useEffect } from "react";
import { fetchCodexUsage } from "@/lib/api/codexApi";
import { loadCodexAuth, saveCodexAuth, clearCodexAuth } from "@/lib/storage";
import { pickAndReadCodexAuth } from "@/lib/fileReader";
import type { CodexUsageResponse, CodexAuth } from "@/types/codex";
type Status = "idle" | "loading" | "success" | "error";
export function useCodexUsage() {
const [auth, setAuth] = useState<CodexAuth | null>(null);
const [usage, setUsage] = useState<CodexUsageResponse | null>(null);
const [status, setStatus] = useState<Status>("idle");
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadCodexAuth().then((stored) => {
if (stored) setAuth(stored);
});
}, []);
const importAuthFile = useCallback(async () => {
try {
const parsed = await pickAndReadCodexAuth();
await saveCodexAuth(parsed);
setAuth(parsed);
setError(null);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR";
if (msg !== "PICKER_CANCELLED") setError(msg);
}
}, []);
const refresh = useCallback(async () => {
if (!auth) return;
setStatus("loading");
setError(null);
try {
const data = await fetchCodexUsage(auth);
setUsage(data);
setStatus("success");
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR";
setError(msg);
setStatus("error");
if (msg === "TOKEN_EXPIRED") {
await clearCodexAuth();
setAuth(null);
}
}
}, [auth]);
useEffect(() => {
if (auth) void refresh();
}, [auth]);
const clearAuth = useCallback(async () => {
await clearCodexAuth();
setAuth(null);
setUsage(null);
setStatus("idle");
setError(null);
}, []);
return { auth, usage, status, error, importAuthFile, refresh, clearAuth };
}