/** * Admin Dashboard * Login-protected page for managing TRC20 purchases * Features: purchase list, status updates, export, stats */ import { useState, useEffect } from "react"; import { trpc } from "@/lib/trpc"; import { Link } from "wouter"; // ─── Types ──────────────────────────────────────────────────────────────────── interface Purchase { id: number; txHash: string; fromAddress: string; evmAddress: string | null; usdtAmount: number; xicAmount: number; status: "pending" | "confirmed" | "distributed" | "failed"; distributedAt: Date | null; distributeTxHash: string | null; createdAt: Date; } // ─── Status Badge ───────────────────────────────────────────────────────────── function StatusBadge({ status }: { status: Purchase["status"] }) { const config = { pending: { color: "#f0b429", bg: "rgba(240,180,41,0.15)", label: "Pending" }, confirmed: { color: "#00d4ff", bg: "rgba(0,212,255,0.15)", label: "Confirmed" }, distributed: { color: "#00e676", bg: "rgba(0,230,118,0.15)", label: "Distributed" }, failed: { color: "#ff5252", bg: "rgba(255,82,82,0.15)", label: "Failed" }, }; const cfg = config[status] || config.pending; return ( {cfg.label} ); } // ─── Login Form ─────────────────────────────────────────────────────────────── function LoginForm({ onLogin }: { onLogin: (token: string) => void }) { const [password, setPassword] = useState(""); const [error, setError] = useState(""); const loginMutation = trpc.admin.login.useMutation({ onSuccess: (data) => { onLogin(data.token); }, onError: (err) => { setError(err.message); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(""); loginMutation.mutate({ password }); }; return (
🔐

Admin Dashboard

NAC XIC Presale Management

setPassword(e.target.value)} placeholder="Enter admin password" className="w-full px-4 py-3 rounded-xl text-sm" style={{ background: "rgba(255,255,255,0.05)", border: error ? "1px solid rgba(255,82,82,0.5)" : "1px solid rgba(255,255,255,0.1)", color: "white", outline: "none", }} autoFocus /> {error &&

{error}

}
← Back to Presale
); } // ─── Main D// ─── Settings Panel ─────────────────────────────────────────────── function SettingsPanel({ token }: { token: string }) { const { data: configData, refetch: refetchConfig, isLoading } = trpc.admin.getConfig.useQuery({ token }); const [editValues, setEditValues] = useState>({}); const [savingKey, setSavingKey] = useState(null); const [savedKeys, setSavedKeys] = useState>(new Set()); const [telegramBotToken, setTelegramBotToken] = useState(""); const [telegramChatId, setTelegramChatId] = useState(""); const [telegramStatus, setTelegramStatus] = useState<"idle" | "testing" | "success" | "error">("idle"); const [telegramError, setTelegramError] = useState(""); // ── Presale Active/Paused Toggle ────────────────────────────────────────── const isPresaleLive = (configData?.find(c => c.key === "presaleStatus")?.value ?? "live") === "live"; const [togglingPresale, setTogglingPresale] = useState(false); const setConfigMutation = trpc.admin.setConfig.useMutation({ onSuccess: (_, vars) => { setSavedKeys(prev => { const s = new Set(Array.from(prev)); s.add(vars.key); return s; }); setSavingKey(null); setTogglingPresale(false); refetchConfig(); setTimeout(() => setSavedKeys(prev => { const n = new Set(Array.from(prev)); n.delete(vars.key); return n; }), 2000); }, onError: (err) => { setSavingKey(null); setTogglingPresale(false); alert(`Save failed: ${err.message}`); }, }); const handleTogglePresale = () => { const newStatus = isPresaleLive ? "paused" : "live"; setTogglingPresale(true); setConfigMutation.mutate({ token, key: "presaleStatus", value: newStatus }); }; const testTelegramMutation = trpc.admin.testTelegram.useMutation({ onSuccess: () => { setTelegramStatus("success"); refetchConfig(); }, onError: (err) => { setTelegramStatus("error"); setTelegramError(err.message); }, }); // Initialize edit values from config useEffect(() => { if (configData) { const vals: Record = {}; configData.forEach(c => { vals[c.key] = c.value; }); setEditValues(vals); // Pre-fill Telegram fields const botToken = configData.find(c => c.key === "telegramBotToken")?.value || ""; const chatId = configData.find(c => c.key === "telegramChatId")?.value || ""; if (botToken) setTelegramBotToken(botToken); if (chatId) setTelegramChatId(chatId); } }, [configData]); const handleSave = (key: string) => { setSavingKey(key); setConfigMutation.mutate({ token, key, value: editValues[key] || "" }); }; const handleTestTelegram = () => { if (!telegramBotToken || !telegramChatId) { setTelegramStatus("error"); setTelegramError("Please enter both Bot Token and Chat ID"); return; } setTelegramStatus("testing"); setTelegramError(""); testTelegramMutation.mutate({ token, botToken: telegramBotToken, chatId: telegramChatId }); }; // Group configs by category const presaleKeys = ["presaleEndDate", "tokenPrice", "hardCap", "listingPrice", "totalSupply", "maxPurchaseUsdt", "presaleStatus"]; const contentKeys = ["heroTitle", "heroSubtitle", "tronReceivingAddress"]; const telegramKeys = ["telegramBotToken", "telegramChatId"]; const renderConfigRow = (cfg: { key: string; value: string; label: string; description: string; type: string; updatedAt: Date | null }) => (
{cfg.label} {cfg.type}

{cfg.description}

{cfg.type === "text" && cfg.key !== "heroSubtitle" ? ( setEditValues(prev => ({ ...prev, [cfg.key]: e.target.value }))} className="w-full px-3 py-2 rounded-lg text-sm" style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "white", outline: "none" }} /> ) : cfg.key === "heroSubtitle" ? (