From d0f868ffa57ebd8ef396a9519fd4634f0a30eebf Mon Sep 17 00:00:00 2001 From: gpatruno Date: Sat, 18 Jul 2026 17:40:39 +0200 Subject: [PATCH] admin --- README.md | 2 +- client/index.html | 2 +- client/src/admin/AdminCategories.jsx | 192 +++++++++++++++++++++++++++ client/src/admin/AdminDashboard.jsx | 2 +- client/src/admin/AdminItems.jsx | 6 +- client/src/admin/AdminLayout.jsx | 3 +- client/src/api.js | 6 + client/src/components/Layout.jsx | 4 +- client/src/index.css | 36 +++++ client/src/pages/Admin.jsx | 6 +- client/src/pages/Login.jsx | 2 +- client/src/pages/ProfilePage.jsx | 2 +- client/src/pages/Register.jsx | 2 +- client/src/userPrefs.js | 2 +- server/prisma/schema.prisma | 6 + server/src/achievements.js | 4 +- server/src/categories.js | 55 +++++++- server/src/index.js | 2 + server/src/routes/admin.js | 100 ++++++++++++-- 19 files changed, 406 insertions(+), 28 deletions(-) create mode 100644 client/src/admin/AdminCategories.jsx diff --git a/README.md b/README.md index d4a9fbc..5aa3139 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CaseForge +# CaseOrion CS-style case opening site with virtual credits, inventory, multiplayer item betting, shop, leaderboard, and an admin panel for cases / drops / rates. diff --git a/client/index.html b/client/index.html index 041d80d..26843be 100644 --- a/client/index.html +++ b/client/index.html @@ -12,7 +12,7 @@ href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" /> - CaseForge + CaseOrion
diff --git a/client/src/admin/AdminCategories.jsx b/client/src/admin/AdminCategories.jsx new file mode 100644 index 0000000..c9fba4a --- /dev/null +++ b/client/src/admin/AdminCategories.jsx @@ -0,0 +1,192 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { api } from '../api'; + +export default function AdminCategories() { + const [categories, setCategories] = useState([]); + const [error, setError] = useState(''); + const [message, setMessage] = useState(''); + const [query, setQuery] = useState(''); + const [name, setName] = useState(''); + const [editingId, setEditingId] = useState(null); + const [editName, setEditName] = useState(''); + const [busy, setBusy] = useState(false); + + const load = useCallback(async () => { + const data = await api.adminCategories(); + setCategories(data.categories || []); + }, []); + + useEffect(() => { + load().catch((err) => setError(err.message)); + }, [load]); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q) return categories; + return categories.filter((c) => c.name.toLowerCase().includes(q)); + }, [categories, query]); + + return ( +
+
+
+

Categories

+

+ {categories.length} categories · rename updates items · delete only when empty +

+
+ setQuery(e.target.value)} + /> +
+ + {error &&
{error}
} + {message &&
{message}
} + +
+

Create category

+
{ + e.preventDefault(); + setError(''); + setMessage(''); + setBusy(true); + try { + await api.createCategory({ name }); + setName(''); + setMessage('Category created.'); + await load(); + } catch (err) { + setError(err.message); + } finally { + setBusy(false); + } + }} + > + + +
+
+ +
+

All categories

+ {filtered.length === 0 ? ( +

No categories match.

+ ) : ( +
    + {filtered.map((cat) => ( +
  • + {editingId === cat.id ? ( +
    { + e.preventDefault(); + setError(''); + setMessage(''); + setBusy(true); + try { + await api.updateCategory(cat.id, { name: editName }); + setEditingId(null); + setEditName(''); + setMessage('Category renamed.'); + await load(); + } catch (err) { + setError(err.message); + } finally { + setBusy(false); + } + }} + > + setEditName(e.target.value)} + required + autoFocus + /> + + +
    + ) : ( + <> +
    + {cat.name} + + {cat.itemCount} item{cat.itemCount === 1 ? '' : 's'} + +
    +
    + + +
    + + )} +
  • + ))} +
+ )} +
+
+ ); +} diff --git a/client/src/admin/AdminDashboard.jsx b/client/src/admin/AdminDashboard.jsx index 261f72f..27b1e90 100644 --- a/client/src/admin/AdminDashboard.jsx +++ b/client/src/admin/AdminDashboard.jsx @@ -31,7 +31,7 @@ export default function AdminDashboard() {

Overview

-

Live snapshot of CaseForge.

+

Live snapshot of CaseOrion.

diff --git a/client/src/admin/AdminItems.jsx b/client/src/admin/AdminItems.jsx index e333421..97d8ccb 100644 --- a/client/src/admin/AdminItems.jsx +++ b/client/src/admin/AdminItems.jsx @@ -51,7 +51,11 @@ export default function AdminItems() { ]); setItems(i.items); setRarities(r.rarities); - setCategories(c.categories || []); + setCategories( + (c.names || c.categories || []).map((entry) => + typeof entry === 'string' ? entry : entry.name + ) + ); setItemForm((f) => ({ ...f, rarity: f.rarity || r.rarities[0] || 'MilSpec', diff --git a/client/src/admin/AdminLayout.jsx b/client/src/admin/AdminLayout.jsx index 6609d43..e97a7da 100644 --- a/client/src/admin/AdminLayout.jsx +++ b/client/src/admin/AdminLayout.jsx @@ -6,6 +6,7 @@ const NAV = [ { to: '/admin/players', label: 'Players' }, { to: '/admin/cases', label: 'Cases' }, { to: '/admin/items', label: 'Items' }, + { to: '/admin/categories', label: 'Categories' }, { to: '/admin/drops', label: 'Drops' }, ]; @@ -17,7 +18,7 @@ export default function AdminLayout() {