Refactor admin UI for consolidated navigation and improved moderation workflow

- Updated Sidebar component to consolidate navigation items under new categories: Moderation, Content, and Operations.
- Removed outdated pages and integrated their functionalities into tabs under Players, Activity, Content, and Settings.
- Introduced new Activity and Content pages to house Audit, Player Logs, Community Board, and Loading Tips with tabbed navigation.
- Created a unified Moderation page combining Players, Bans, and Reports with tabbed navigation.
- Refactored individual pages (e.g., Audit, Bans, CommunityBoard, etc.) to support embedded mode for better integration within modals.
- Added Tabs component for consistent tab navigation across multiple pages.
- Updated README to reflect changes in navigation and settings structure.
- Removed Grants page as it is not yet implemented.
This commit is contained in:
Alexa
2026-06-02 10:49:35 +03:00
parent 24c8a920e4
commit 7535cd823f
21 changed files with 400 additions and 249 deletions
+24 -13
View File
@@ -10,8 +10,10 @@ import { useToast } from '../components/Toast';
import { Empty } from '../components/Empty';
import { PlayerAvatar } from '../components/PlayerAvatar';
import { RefreshCw, Search } from '../components/Icons';
import { GiftPanel } from './Gift';
import { PasswordResetCard } from './Passwords';
export function Players() {
export function Players({ embedded }: { embedded?: boolean } = {}) {
const [params] = useSearchParams();
const [query, setQuery] = useState(params.get('query') ?? '');
const [rows, setRows] = useState<Player[] | null>(null);
@@ -45,18 +47,24 @@ export function Players() {
if (next !== query) setQuery(next);
}, [params, query]);
const refreshBtn = (
<button onClick={() => search(query)} className="btn-secondary text-xs" disabled={loading}>
<RefreshCw className={loading ? 'animate-spin' : ''} />
Refresh
</button>
);
return (
<div>
<PageHeader
title="Players"
blurb="Search and moderate every account in the database."
actions={
<button onClick={() => search(query)} className="btn-secondary text-xs" disabled={loading}>
<RefreshCw className={loading ? 'animate-spin' : ''} />
Refresh
</button>
}
/>
{embedded ? (
<div className="flex justify-end mb-3">{refreshBtn}</div>
) : (
<PageHeader
title="Players"
blurb="Search and moderate every account in the database."
actions={refreshBtn}
/>
)}
<div className="card overflow-hidden">
<div className="border-b border-ink-800 p-3 flex items-center gap-2">
@@ -153,7 +161,7 @@ export function Players() {
function PlayerDetail({ id, onClose, onChanged }: { id: number; onClose: () => void; onChanged: () => void }) {
const [data, setData] = useState<PlayerDetail | null>(null);
const [err, setErr] = useState<string | null>(null);
const [tab, setTab] = useState<'overview' | 'mod' | 'grants' | 'profile'>('overview');
const [tab, setTab] = useState<'overview' | 'mod' | 'grants' | 'gift' | 'profile'>('overview');
const reload = () => {
setErr(null);
@@ -196,7 +204,7 @@ function PlayerDetail({ id, onClose, onChanged }: { id: number; onClose: () => v
</div>
<div className="flex border-b border-ink-800 text-sm">
{(['overview', 'mod', 'grants', 'profile'] as const).map(t => (
{(['overview', 'mod', 'grants', 'gift', 'profile'] as const).map(t => (
<button
key={t}
onClick={() => setTab(t)}
@@ -210,6 +218,7 @@ function PlayerDetail({ id, onClose, onChanged }: { id: number; onClose: () => v
{tab === 'overview' && <OverviewTab data={data} />}
{tab === 'mod' && <ModerationTab data={data} onChanged={onChanged} />}
{tab === 'grants' && <GrantsTab data={data} onChanged={reload} />}
{tab === 'gift' && <GiftPanel playerId={data.id} />}
{tab === 'profile' && <ProfileTab data={data} onChanged={reload} />}
</div>
)}
@@ -576,6 +585,8 @@ function ProfileTab({ data, onChanged }: { data: PlayerDetail; onChanged: () =>
className="btn-danger text-xs"
>Reset to starter</button>
</div>
<PasswordResetCard playerId={data.id} username={data.username} />
</div>
);
}