import { useEffect, useState } from 'react'; import { Nav } from '~/components/nav'; const API_URL = import.meta.env.VITE_API_URL ?? 'https://api.t4t-gateway.com'; const WEI_PER_XBZZ = 10n ** 18n; type User = { id: string; email: string; role: string; emailVerified: boolean }; type Account = { id: string; status: string; balanceXbzzWei: string; reservedXbzzWei: string; availableXbzzWei: string; balanceUsd: number | null; availableUsd: number | null; }; type LedgerEntry = { id: string; kind: string; deltaXbzzWei: string; createdAt: string; gatewayJobId: string | null; topupId: string | null; note: string | null; }; type ApiKey = { id: string; name: string; createdAt: string; lastUsedAt: string | null; scopeMaxXbzzPerDay: string | null; modelAllowlist: string[]; }; type NewApiKey = { id: string; name: string; key: string; createdAt: string }; type Job = { id: string; status: string; modelId: string; provider: string; estimatedMaxXbzzWei: string; actualXbzzWei: string | null; promptTokens: number | null; completionTokens: number | null; createdAt: string; ackedAt: string | null; deliveredAt: string | null; claimedAt: string | null; errorMessage: string | null; }; export function meta() { return [{ title: 'Account · t4t Gateway' }]; } function formatXbzz(weiStr: string): string { const wei = BigInt(weiStr); const whole = wei / WEI_PER_XBZZ; const frac = wei % WEI_PER_XBZZ; const fracStr = frac.toString().padStart(18, '0').slice(0, 4); return `${whole.toString()}.${fracStr}`; } function formatUsd(cents: number | null): string { if (cents === null) return '—'; return `$${(cents / 100).toFixed(2)}`; } async function api(path: string, init: RequestInit = {}): Promise { const token = typeof window !== 'undefined' ? localStorage.getItem('jwt') : null; if (!token) throw new Error('Not authenticated'); const res = await fetch(`${API_URL}${path}`, { ...init, headers: { ...(init.headers ?? {}), Authorization: `Bearer ${token}`, ...(init.body ? { 'Content-Type': 'application/json' } : {}), }, }); if (res.status === 401) { localStorage.removeItem('jwt'); window.location.href = '/login'; throw new Error('Unauthorized'); } const body = await res.json(); if (!res.ok) throw new Error(body?.message ?? body?.error ?? `Request failed (${res.status})`); return body as T; } export default function Account() { const [user, setUser] = useState(null); const [account, setAccount] = useState(null); const [ledger, setLedger] = useState([]); const [keys, setKeys] = useState([]); const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [newKeyName, setNewKeyName] = useState(''); const [creatingKey, setCreatingKey] = useState(false); const [createdKey, setCreatedKey] = useState(null); const [copied, setCopied] = useState(false); useEffect(() => { if (typeof window === 'undefined') return; if (!localStorage.getItem('jwt')) { window.location.href = '/login'; return; } (async () => { try { const [u, a, l, k, j] = await Promise.all([ api('/users/me'), api('/accounts/me'), api('/accounts/me/ledger'), api('/api-keys'), api('/v1/jobs?limit=50'), ]); setUser(u); setAccount(a); setLedger(l); setKeys(k); setJobs(j); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } })(); }, []); async function createKey(e: React.FormEvent) { e.preventDefault(); setCreatingKey(true); try { const created = await api('/api-keys', { method: 'POST', body: JSON.stringify({ name: newKeyName }), }); setCreatedKey(created); setNewKeyName(''); const k = await api('/api-keys'); setKeys(k); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setCreatingKey(false); } } async function revokeKey(id: string) { if (!confirm('Revoke this API key? Existing requests using it will start failing.')) return; try { await api(`/api-keys/${id}`, { method: 'DELETE' }); setKeys(keys.filter((k) => k.id !== id)); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } } function signOut() { localStorage.removeItem('jwt'); window.location.href = '/'; } async function copyKey(key: string) { await navigator.clipboard.writeText(key); setCopied(true); setTimeout(() => setCopied(false), 2000); } if (loading) { return ( <>