'use client'; import { useEffect, useState } from 'react'; import { listUsers, createUser, deleteUser } from '@/lib/api'; import PortalLayout from '@/app/layout-portal'; import { paymentService, type UserFullStats, type UploadRecord } from '@/lib/payment'; import { formatWeiToETH, formatBytes } from '@/lib/wallet'; import { Users, Plus, Trash2, UserPlus, Search, Wallet, HardDrive, Upload, Clock, ExternalLink, Copy, CheckCircle, Loader2, XCircle, } from 'lucide-react'; export default function UsersPage() { // ── htpasswd users ── const [users, setUsers] = useState<{ username: string; created: string; active: boolean }[]>([]); const [loading, setLoading] = useState(true); const [showAdd, setShowAdd] = useState(false); const [newUser, setNewUser] = useState(''); const [newPass, setNewPass] = useState(''); // ── Wallet lookup ── const [walletAddr, setWalletAddr] = useState(''); const [walletStats, setWalletStats] = useState(null); const [walletLoading, setWalletLoading] = useState(false); const [walletError, setWalletError] = useState(null); const [copiedCid, setCopiedCid] = useState(null); async function load() { try { const items = await listUsers(); setUsers(items); } catch { /* ignore */ } finally { setLoading(false); } } useEffect(() => { load(); }, []); async function handleCreate() { if (!newUser || !newPass) return; try { await createUser(newUser, newPass); setNewUser(''); setNewPass(''); setShowAdd(false); await load(); } catch { /* ignore */ } } async function handleDelete(username: string) { try { await deleteUser(username); setUsers((u) => u.filter((x) => x.username !== username)); } catch { /* ignore */ } } // ── Wallet lookup ── async function lookupWallet() { const addr = walletAddr.trim(); if (!addr || !addr.startsWith('0x') || addr.length !== 42) { setWalletError('Invalid address (must be 0x... 42 chars)'); return; } setWalletLoading(true); setWalletError(null); setWalletStats(null); try { const stats = await paymentService.getUserUploads(addr as `0x${string}`); setWalletStats(stats); } catch (e: any) { setWalletError(e.message || 'Failed to fetch wallet stats'); } finally { setWalletLoading(false); } } async function copyCid(cid: string) { try { await navigator.clipboard.writeText(cid); setCopiedCid(cid); setTimeout(() => setCopiedCid(null), 2000); } catch { /* ignore */ } } function formatTimestamp(ts: bigint): string { const d = new Date(Number(ts) * 1000); return d.toLocaleDateString() + ' ' + d.toLocaleTimeString(); } return ( {/* ── Header with count badge ── */}

Users

{users.length} IPFS users

{/* ── Add user form ── */} {showAdd && (
setNewUser(e.target.value)} placeholder="Username" className="flex-1 px-3 py-2 rounded-lg bg-surface-900 border border-surface-700 text-sm text-surface-200 placeholder:text-surface-500 focus:outline-none focus:border-brand-500" /> setNewPass(e.target.value)} type="password" placeholder="Password" className="flex-1 px-3 py-2 rounded-lg bg-surface-900 border border-surface-700 text-sm text-surface-200 placeholder:text-surface-500 focus:outline-none focus:border-brand-500" />
)} {/* ── Two-column layout ── */}
{/* Left: htpasswd user list */}
{loading ? (
Loading…
) : users.length === 0 ? (
No users configured
) : (
{users.map((u) => (
{u.username}
Created {u.created}
{u.active && }
))}
)}
{/* Right: Wallet lookup */}

Wallet Lookup

Enter a wallet address to see on-chain upload stats

setWalletAddr(e.target.value)} onKeyDown={e => e.key === 'Enter' && lookupWallet()} placeholder="0x..." className="flex-1 px-3 py-1.5 rounded-lg bg-surface-900 border border-surface-700 text-xs font-mono text-surface-200 placeholder:text-surface-500 focus:outline-none focus:border-brand-500" />
{walletError && (
{walletError}
)}
{/* ── Wallet stats ── */} {walletStats && ( <> {/* Stats cards */}
Total Bytes
{formatBytes(walletStats.totalBytes)}
Uploads
{walletStats.uploadCount}
Free Remaining
{formatBytes(walletStats.remainingFree)}
Address
{walletAddr.slice(0, 6)}...{walletAddr.slice(-4)}
{/* ── Upload history ── */} {walletStats.uploads.length > 0 && (

Upload History ({walletStats.uploads.length})

{[...walletStats.uploads].reverse().map((rec, i) => (
{rec.tokenSymbol} {formatBytes(rec.bytesSize)}
{formatTimestamp(rec.timestamp)}
{rec.cid}
{rec.amountPaid > BigInt(0) && ( {rec.tokenSymbol === 'ETH' ? formatWeiToETH(rec.amountPaid, 6) : rec.amountPaid.toString()} {rec.tokenSymbol} )}
))}
)} {walletStats.uploads.length === 0 && (

No uploads found for this address

)} )}
); }