/* P.E.S.T Web App — Admin panel (manage projects + token catalog) */ function Admin() { const [tab, setTab] = useState("projects"); return (
Admin

Manage the directory

Curate what everyone sees. Changes flow to all members' pages and dashboards.

{tab === "projects" ? : tab === "tokens" ? : }
); } /* ---------- shared: multi-category chips ---------- */ function CategoryChips({ value, onChange }) { const all = window.PEST_APP.CATEGORIES; const sel = new Set(value || []); const toggle = (c) => { if (sel.has(c)) sel.delete(c); else sel.add(c); onChange(Array.from(sel)); }; return (
{all.map((c) => ( ))}
); } /* ---------- shared: file picker with preview ---------- */ function FilePickField({ file, onFile, currentLogo, label }) { const inpRef = useRef(null); const preview = file ? URL.createObjectURL(file) : (currentLogo ? window.resolveLogo(currentLogo) : null); return (
{preview ? preview : }
{label || "Logo"}
onFile(e.target.files && e.target.files[0])} /> {file && }
); } /* ============================================================ PROJECTS ADMIN ============================================================ */ function ProjectsAdmin() { const store = useStore(); const blank = { name: "", categories: [], desc: "", chains: "", website: "", x: "", discord: "", telegram: "" }; const [f, setF] = useState(blank); const [file, setFile] = useState(null); const [busy, setBusy] = useState(false); const [editing, setEditing] = useState(null); const [q, setQ] = useState(""); const [catFilter, setCatFilter] = useState("All"); const set = (k, v) => setF((x) => ({ ...x, [k]: v })); const add = async () => { if (!f.name.trim()) { toast("Project needs a name", "err"); return; } if (!f.categories.length) { toast("Pick at least one category", "err"); return; } setBusy(true); try { const id = f.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-" + Math.random().toString(36).slice(2, 5); const links = {}; ["website", "x", "discord", "telegram"].forEach((k) => { if (f[k].trim()) links[k] = f[k].trim(); }); let logo = null; if (file) { const r = await window.PEST_APP.api.uploadProjectLogo(file, id); logo = r.path; } const ok = await store.addProject({ id, name: f.name.trim(), categories: f.categories, desc: f.desc.trim(), chains: f.chains.trim(), logo, links, addedAt: Date.now() }); if (ok) { setF(blank); setFile(null); } } catch (e) { toast(e.message || "Upload failed", "err"); } setBusy(false); }; const catsOf = (p) => (p.categories && p.categories.length) ? p.categories : (p.category ? [p.category] : []); const cats = ["All", ...window.PEST_APP.CATEGORIES]; const ql = q.trim().toLowerCase(); const sorted = [...store.projects] .filter((p) => catFilter === "All" || catsOf(p).indexOf(catFilter) !== -1) .filter((p) => !ql || (p.name + " " + (p.desc || "") + " " + catsOf(p).join(" ")).toLowerCase().includes(ql)) .sort((a, b) => b.addedAt - a.addedAt); return (
{/* add form */}
New

Add a project

set("name", e.target.value)} placeholder="Project name" />
set("categories", v)} />