/** * AI Credits Admin Panel * * Admin UI for managing AI credits: * - View any user's balance * - View transaction history (ledger) * - Manually adjust credits (ADD/DEDUCT) * - Reconcile reports */ import { createSignal, createResource, createMemo, Show, For, Suspense } from "solid-js"; import { useAuth } from "~/lib/auth"; const API = '/api'; // Types interface Wallet { available_credits: number; monthly_credits_total: number; monthly_credits_used: number; purchased_credits_total: number; purchased_credits_used: number; bonus_credits_total: number; bonus_credits_used: number; reserved_credits: number; locked_credits: number; daily_actions_used: number; daily_credits_used: number; } interface LedgerEntry { id: string; entry_type: string; credits: number; balance_after: number; description: string | null; idempotency_key: string | null; created_at: string; } interface AdjustRequest { user_id: string; amount: number; type: 'ADD' | 'DEDUCT'; reason: string; } // API Functions async function fetchUserBalance(userId: string): Promise { const token = sessionStorage.getItem('nxtgauge_access_token'); const res = await fetch(`${API}/admin/ai-credits/balance?userId=${encodeURIComponent(userId)}`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, credentials: 'include', }); if (!res.ok) throw new Error(await res.text()); return res.json(); } async function fetchUserLedger(userId: string, page: number = 1, limit: number = 20): Promise<{ data: LedgerEntry[]; total: number }> { const token = sessionStorage.getItem('nxtgauge_access_token'); const res = await fetch( `${API}/admin/ai-credits/ledger?userId=${encodeURIComponent(userId)}&page=${page}&limit=${limit}`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, credentials: 'include', } ); if (!res.ok) throw new Error(await res.text()); return res.json(); } async function adjustCredits(request: AdjustRequest): Promise<{ wallet: Wallet; success: boolean }> { const token = sessionStorage.getItem('nxtgauge_access_token'); const res = await fetch(`${API}/admin/ai-credits/adjust`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(request), }); if (!res.ok) throw new Error(await res.text()); return res.json(); } export default function AiCreditsAdmin() { const auth = useAuth(); const [activeTab, setActiveTab] = createSignal<'balance' | 'ledger' | 'adjust' | 'reconcile'>('balance'); // Balance tab state const [userIdInput, setUserIdInput] = createSignal(''); const [balanceData, setBalanceData] = createSignal(null); const [balanceError, setBalanceError] = createSignal(''); const [balanceLoading, setBalanceLoading] = createSignal(false); // Ledger tab state const [ledgerUserId, setLedgerUserId] = createSignal(''); const [ledgerPage, setLedgerPage] = createSignal(1); const [ledgerData, setLedgerData] = createSignal<{ data: LedgerEntry[]; total: number } | null>(null); const [ledgerError, setLedgerError] = createSignal(''); const [ledgerLoading, setLedgerLoading] = createSignal(false); // Adjust tab state const [adjustForm, setAdjustForm] = createSignal({ user_id: '', amount: 100, type: 'ADD', reason: '', }); const [adjustResult, setAdjustResult] = createSignal<{ wallet: Wallet; success: boolean } | null>(null); const [adjustError, setAdjustError] = createSignal(''); const [adjustLoading, setAdjustLoading] = createSignal(false); // Reconcile tab state const [reconcileFrom, setReconcileFrom] = createSignal(''); const [reconcileTo, setReconcileTo] = createSignal(''); const handleFetchBalance = async () => { if (!userIdInput()) { setBalanceError('Please enter a User ID'); return; } setBalanceLoading(true); setBalanceError(''); try { const data = await fetchUserBalance(userIdInput()); setBalanceData(data); } catch (e: any) { setBalanceError(e.message || 'Failed to fetch balance'); } finally { setBalanceLoading(false); } }; const handleFetchLedger = async () => { if (!ledgerUserId()) { setLedgerError('Please enter a User ID'); return; } setLedgerLoading(true); setLedgerError(''); try { const data = await fetchUserLedger(ledgerUserId(), ledgerPage()); setLedgerData(data); } catch (e: any) { setLedgerError(e.message || 'Failed to fetch ledger'); } finally { setLedgerLoading(false); } }; const handleAdjust = async () => { if (!adjustForm().user_id || !adjustForm().amount || !adjustForm().reason) { setAdjustError('Please fill in all fields'); return; } if (adjustForm().amount <= 0) { setAdjustError('Amount must be positive'); return; } if (adjustForm().reason.trim().length < 5) { setAdjustError('Please provide a detailed reason (at least 5 characters)'); return; } setAdjustLoading(true); setAdjustError(''); try { const result = await adjustCredits(adjustForm()); setAdjustResult(result); } catch (e: any) { setAdjustError(e.message || 'Failed to adjust credits'); } finally { setAdjustLoading(false); } }; const formatCurrency = (credits: number) => { return credits.toLocaleString(); }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString(); }; return (

AI Credits Admin

Manage AI credits for users across the platform

{/* Tab Navigation */}
{[ { key: 'balance', label: 'View Balance' }, { key: 'ledger', label: 'Transaction History' }, { key: 'adjust', label: 'Adjust Credits' }, { key: 'reconcile', label: 'Reconcile' }, ].map(tab => ( ))}
{/* Balance Tab */}
setUserIdInput(e.currentTarget.value)} placeholder="Enter User ID (UUID)" style={{ flex: '1', padding: '10px 14px', 'font-size': '14px', border: '1px solid #D1D5DB', 'border-radius': '6px', }} />
{balanceError()}
{(wallet) => (
Available Credits
{formatCurrency(wallet().available_credits)}
Monthly Credits
{formatCurrency(wallet().monthly_credits_total - wallet().monthly_credits_used)} / {formatCurrency(wallet().monthly_credits_total)}
Used: {formatCurrency(wallet().monthly_credits_used)}
Purchased Credits
{formatCurrency(wallet().purchased_credits_total - wallet().purchased_credits_used)} / {formatCurrency(wallet().purchased_credits_total)}
Used: {formatCurrency(wallet().purchased_credits_used)}
Bonus Credits
{formatCurrency(wallet().bonus_credits_total - wallet().bonus_credits_used)} / {formatCurrency(wallet().bonus_credits_total)}
Used: {formatCurrency(wallet().bonus_credits_used)}
Reserved
{formatCurrency(wallet().reserved_credits)}
Locked
{formatCurrency(wallet().locked_credits)}
)}
{/* Ledger Tab */}
setLedgerUserId(e.currentTarget.value)} placeholder="Enter User ID (UUID)" style={{ flex: '1', padding: '10px 14px', 'font-size': '14px', border: '1px solid #D1D5DB', 'border-radius': '6px', }} />
{ledgerError()}
{(data) => (
Showing {data().data.length} of {data().total} entries
{(entry) => ( )}
Date Type Credits Balance After Description
{formatDate(entry.created_at)} {entry.entry_type} {entry.credits > 0 ? '+' : ''}{formatCurrency(entry.credits)} {formatCurrency(entry.balance_after)} {entry.description || '-'}
Page {ledgerPage()}
)}
{/* Adjust Tab */}
setAdjustForm(f => ({ ...f, user_id: e.currentTarget.value }))} placeholder="Enter User ID (UUID)" style={{ width: '100%', padding: '10px 14px', 'font-size': '14px', border: '1px solid #D1D5DB', 'border-radius': '6px', }} />
setAdjustForm(f => ({ ...f, amount: parseInt(e.currentTarget.value) || 0 }))} style={{ width: '100%', padding: '10px 14px', 'font-size': '14px', border: '1px solid #D1D5DB', 'border-radius': '6px', }} />