import { useEffect, useState } from 'react'; 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 }; 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 [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] = await Promise.all([ api('/users/me'), api('/accounts/me'), api('/accounts/me/ledger'), api('/api-keys'), ]); setUser(u); setAccount(a); setLedger(l); setKeys(k); } 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 (

Loading…

); } return (

Account

{user &&

{user.email}

}
{error && (

{error}

)} {user && !user.emailVerified && (

Your email isn't verified yet. The welcome credit lands once you do.

)} {account && (

Balance

{formatXbzz(account.availableXbzzWei)}{' '} xBZZ

{account.availableUsd !== null && (

≈ {formatUsd(account.availableUsd)} USD

)} {BigInt(account.reservedXbzzWei) > 0n && (

{formatXbzz(account.reservedXbzzWei)} xBZZ reserved on in-flight jobs.

)}
)}

API Keys

setNewKeyName(e.target.value)} className='flex-1 rounded-md border border-neutral-300 px-3 py-2 text-sm shadow-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900' />
{keys.length === 0 ? (

No keys yet.

) : (
    {keys.map((k) => (
  • {k.name || '(unnamed)'}

    Created {new Date(k.createdAt).toLocaleDateString()} {k.lastUsedAt ? ` · last used ${new Date(k.lastUsedAt).toLocaleDateString()}` : ' · never used'}

  • ))}
)}

Recent activity

{ledger.length === 0 ? (

No ledger entries yet.

) : (
    {ledger.slice(0, 20).map((entry) => { const delta = BigInt(entry.deltaXbzzWei); const positive = delta > 0n; return (
  • {entry.kind}

    {new Date(entry.createdAt).toLocaleString()} {entry.note ? ` · ${entry.note}` : ''}

    {positive ? '+' : ''} {formatXbzz(entry.deltaXbzzWei)}

  • ); })}
)}
{createdKey && (
setCreatedKey(null)} >
e.stopPropagation()} >

Save your API key

This is the only time you'll see it. Store it somewhere safe.

              {createdKey.key}
            
)}
); }