import { createSignal, createResource, Show, For } from 'solid-js'; const API = ''; type LedgerEntry = { id: string; transactionType: 'ADD' | 'DEDUCT'; amount: number; referenceId?: string; expiresAt?: string; createdAt?: string; }; type ReconcileRow = { userId: string; expectedBalance: number; actualBalance: number; discrepancy: number; }; export default function CreditPage() { const [activeTab, setActiveTab] = createSignal<'ledger' | 'adjust' | 'reconcile'>('ledger'); // Balance & Ledger tab state const [userId, setUserId] = createSignal(''); const [searchedUserId, setSearchedUserId] = createSignal(''); const [searchTrigger, setSearchTrigger] = createSignal(0); const [balance, setBalance] = createSignal(null); const [ledger, setLedger] = createSignal([]); const [searchLoading, setSearchLoading] = createSignal(false); const [searchError, setSearchError] = createSignal(''); // Reward/Deduct tab state const [adjUserId, setAdjUserId] = createSignal(''); const [adjAmount, setAdjAmount] = createSignal(1); const [adjType, setAdjType] = createSignal<'ADD' | 'DEDUCT'>('ADD'); const [adjReason, setAdjReason] = createSignal(''); const [adjRefId, setAdjRefId] = createSignal(''); const [adjLoading, setAdjLoading] = createSignal(false); const [adjSuccess, setAdjSuccess] = createSignal(''); const [adjError, setAdjError] = createSignal(''); // Reconcile tab state const [reconFrom, setReconFrom] = createSignal(''); const [reconTo, setReconTo] = createSignal(''); const [reconLoading, setReconLoading] = createSignal(false); const [reconResults, setReconResults] = createSignal(null); const [reconError, setReconError] = createSignal(''); const handleSearch = async () => { const uid = userId().trim(); if (!uid) return; setSearchLoading(true); setSearchError(''); setBalance(null); setLedger([]); setSearchedUserId(uid); try { const [balRes, ledRes] = await Promise.all([ fetch(`${API}/api/admin/credits/balance?userId=${encodeURIComponent(uid)}`), fetch(`${API}/api/admin/credits/ledger?userId=${encodeURIComponent(uid)}`), ]); if (!balRes.ok || !ledRes.ok) throw new Error('Failed to fetch'); const balData = await balRes.json(); const ledData = await ledRes.json(); setBalance(balData.balance ?? 0); setLedger(Array.isArray(ledData.entries) ? ledData.entries : []); } catch { setSearchError('Failed to fetch TraceCoin data for this user ID.'); setBalance(null); setLedger([]); } finally { setSearchLoading(false); } }; const handleAdjust = async (e: Event) => { e.preventDefault(); setAdjLoading(true); setAdjSuccess(''); setAdjError(''); try { const res = await fetch(`${API}/api/admin/credits/adjust`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: adjUserId(), amount: adjAmount(), type: adjType(), reason: adjReason(), reference_id: adjRefId() || undefined, }), }); if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error((d as any).message || 'Failed to adjust credits'); } setAdjSuccess('Credit adjusted successfully!'); setAdjUserId(''); setAdjAmount(1); setAdjType('ADD'); setAdjReason(''); setAdjRefId(''); } catch (err: any) { setAdjError(err.message || 'Failed to adjust credits'); } finally { setAdjLoading(false); } }; const handleReconcile = async (e: Event) => { e.preventDefault(); setReconLoading(true); setReconError(''); setReconResults(null); try { const res = await fetch( `${API}/api/admin/credits/reconcile?from=${encodeURIComponent(reconFrom())}&to=${encodeURIComponent(reconTo())}` ); if (!res.ok) throw new Error('Failed to reconcile'); const data = await res.json(); setReconResults(Array.isArray(data.results) ? data.results : []); } catch (err: any) { setReconError(err.message || 'Failed to reconcile'); } finally { setReconLoading(false); } }; const tabs: { key: 'ledger' | 'adjust' | 'reconcile'; label: string }[] = [ { key: 'ledger', label: 'Balance & Ledger' }, { key: 'adjust', label: 'Reward / Deduct' }, { key: 'reconcile', label: 'Reconcile' }, ]; return (

Credit Management

Audit TraceCoin balances and adjust credits

{/* Tabs */}
{(tab) => ( )}
{/* Balance & Ledger Tab */}

Search Account Balance

setUserId(e.currentTarget.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} class="rounded-lg border border-gray-200 px-3 py-2 text-sm" style="flex:1" />
{searchError()}

Current Balance

{balance()} TraceCoins

User: {searchedUserId()}

TraceCoin Ledger

No transactions found for this account.

0}>
{(entry) => ( )}
Type Amount Ref ID Expires At Date
{entry.transactionType} {entry.transactionType === 'ADD' ? '+' : '-'}{entry.amount} {entry.referenceId ? entry.referenceId.substring(0, 18) : '—'} {entry.expiresAt ? new Date(entry.expiresAt).toLocaleDateString() : '—'} {entry.createdAt ? new Date(entry.createdAt).toLocaleString() : '—'}
{/* Reward / Deduct Tab */}

Reward or Adjust TraceCoins

Use this to reward TraceCoins for a valid support case, process a refund, or correct a balance manually.

{adjSuccess()}
{adjError()}
setAdjUserId(e.currentTarget.value)} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />
setAdjAmount(Number(e.currentTarget.value))} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />
setAdjReason(e.currentTarget.value)} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />
setAdjRefId(e.currentTarget.value)} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />
{/* Reconcile Tab */}

Ledger Reconciliation

{reconError()}
setReconFrom(e.currentTarget.value)} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />
setReconTo(e.currentTarget.value)} style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box" />

No discrepancies found.

0}>
{(row) => ( )}
User ID Expected Balance Actual Balance Discrepancy
{row.userId} {row.expectedBalance} {row.actualBalance} {row.discrepancy}
); }