mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-27 07:58:45 -07:00
feat: Refactor admin UI pages to support embedded mode and improve navigation
- Updated PlayerLogs, Players, Reports, Rooms, Settings, SignupCodes, and other pages to accept an optional `embedded` prop for better integration in various contexts. - Introduced a new Activity page consolidating audit logs and player request logs with tab navigation. - Added Content page for community board and loading tips with tab navigation. - Created Moderation page to unify player directory, bans, and reports under a single interface. - Implemented SettingsHome page to manage server settings and signup codes with tab navigation. - Enhanced the Join page to include password creation for new accounts. - Added global friends feature in Settings to allow all accounts to be friends without database entries. - Introduced RelationshipQueries service to manage effective friend resolution based on global friends toggle. - Added WireDates for consistent date handling in serialized data. - Created reusable Tabs component for consistent tab navigation across various pages.
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard, Users, Building2, Ban, Flag, ScrollText, Activity,
|
||||
Megaphone, Upload, ShoppingBag, Gift, KeyRound, Wrench, MessageSquare, HardDrive, Settings, X,
|
||||
LayoutDashboard, Users, Building2, Activity,
|
||||
Megaphone, MessageSquare, HardDrive, Settings, X,
|
||||
} from './Icons';
|
||||
|
||||
// Consolidated navigation. Per-player moderation (bans, grants, gifts,
|
||||
// password resets) lives in the player detail modal; the old Bans /
|
||||
// Reports / Audit / Player-logs / Community / Loading-tips / Gift /
|
||||
// Passwords / Signup-codes pages are now tabs under Players, Activity,
|
||||
// Content, and Settings. Import room is a header action on the Rooms
|
||||
// page. Old routes still resolve via redirects in App.tsx.
|
||||
const groups: Array<{ title: string; items: Array<{ to: string; label: string; icon: React.ReactNode; new?: boolean }>}> = [
|
||||
{
|
||||
title: 'Overview',
|
||||
@@ -13,39 +19,25 @@ const groups: Array<{ title: string; items: Array<{ to: string; label: string; i
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'People',
|
||||
title: 'Moderation',
|
||||
items: [
|
||||
{ to: '/players', label: 'Players', icon: <Users /> },
|
||||
{ to: '/bans', label: 'Bans', icon: <Ban /> },
|
||||
{ to: '/reports', label: 'Reports', icon: <Flag /> },
|
||||
{ to: '/audit', label: 'Audit log', icon: <ScrollText /> },
|
||||
{ to: '/logs', label: 'Player logs', icon: <Activity /> },
|
||||
{ to: '/players', label: 'Players', icon: <Users /> },
|
||||
{ to: '/activity', label: 'Activity', icon: <Activity /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Content',
|
||||
items: [
|
||||
// Rooms is the unified view — clicking a row opens /rooms/:id
|
||||
// which carries the old RR-Originals edit modal, live Photon
|
||||
// instance list, recent visitors, ownership management, and
|
||||
// per-room leaderboard channels in one tabbed page.
|
||||
{ to: '/rooms', label: 'Rooms', icon: <Building2 /> },
|
||||
{ to: '/import-room', label: 'Import room', icon: <Upload /> },
|
||||
{ to: '/store', label: 'Store catalog', icon: <ShoppingBag /> },
|
||||
{ to: '/community', label: 'Community board', icon: <MessageSquare /> },
|
||||
{ to: '/loading-tips', label: 'Loading screen tips', icon: <MessageSquare /> },
|
||||
{ to: '/rooms', label: 'Rooms', icon: <Building2 /> },
|
||||
{ to: '/content', label: 'Content', icon: <MessageSquare /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Tools',
|
||||
title: 'Operations',
|
||||
items: [
|
||||
{ to: '/broadcast', label: 'Broadcast', icon: <Megaphone /> },
|
||||
{ to: '/gift', label: 'Gift items', icon: <Gift />, new: true },
|
||||
{ to: '/passwords', label: 'Reset passwords', icon: <KeyRound />, new: true },
|
||||
{ to: '/grants', label: 'Grant maker pen', icon: <Wrench />, new: true },
|
||||
{ to: '/storage', label: 'Storage backfill', icon: <HardDrive />, new: true },
|
||||
{ to: '/signup-codes', label: 'Signup codes', icon: <KeyRound />, new: true },
|
||||
{ to: '/settings', label: 'Server settings', icon: <Settings />, new: true },
|
||||
{ to: '/broadcast', label: 'Broadcast', icon: <Megaphone /> },
|
||||
{ to: '/storage', label: 'Storage', icon: <HardDrive /> },
|
||||
{ to: '/settings', label: 'Settings', icon: <Settings /> },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
// Shared horizontal tab strip — the same underlined-button style the
|
||||
// per-player modal and the old Bans page already used inline, pulled
|
||||
// out so the consolidated hub pages (Players / Activity / Content /
|
||||
// Settings) all render their sub-tabs identically.
|
||||
export function Tabs<T extends string>({
|
||||
tabs,
|
||||
active,
|
||||
onChange,
|
||||
}: {
|
||||
tabs: ReadonlyArray<{ key: T; label: string }>;
|
||||
active: T;
|
||||
onChange: (key: T) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex border-b border-ink-800 text-sm mb-4 overflow-x-auto">
|
||||
{tabs.map(t => (
|
||||
<button
|
||||
key={t.key}
|
||||
onClick={() => onChange(t.key)}
|
||||
className={`px-4 py-2 -mb-px border-b-2 whitespace-nowrap ${
|
||||
active === t.key
|
||||
? 'border-brand-500 text-ink-50'
|
||||
: 'border-transparent text-ink-300 hover:text-ink-100'
|
||||
}`}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Binds the active hub tab to a `?tab=` query param so the back-compat
|
||||
// redirects (e.g. /bans → /players?tab=bans) land on the right sub-tab
|
||||
// and the choice survives a refresh / shared link. The default tab
|
||||
// drops the param entirely to keep URLs clean.
|
||||
export function useTabParam<T extends string>(def: T, valid: readonly T[]): [T, (t: T) => void] {
|
||||
const [params, setParams] = useSearchParams();
|
||||
const raw = params.get('tab');
|
||||
const active = (valid as readonly string[]).includes(raw ?? '') ? (raw as T) : def;
|
||||
const set = (t: T) => {
|
||||
const next = new URLSearchParams(params);
|
||||
if (t === def) next.delete('tab');
|
||||
else next.set('tab', t);
|
||||
setParams(next, { replace: true });
|
||||
};
|
||||
return [active, set];
|
||||
}
|
||||
Reference in New Issue
Block a user