forked from syntaxbullet/aurorabot
feat: add settings page with guild config, game settings, and command toggles
Implements the full admin settings page covering all game settings (leveling, economy, inventory, lootdrops, trivia, moderation, commands) and guild settings (roles, channels, welcome message, moderation, feature overrides). Includes role/channel pickers, trivia category multi-select, and a feature override flag editor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -139,7 +139,7 @@ async function handler(ctx: RouteContext): Promise<Response | null> {
|
|||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
return jsonResponse({ roles, channels, commands });
|
return jsonResponse({ guildId: env.DISCORD_GUILD_ID, roles, channels, commands });
|
||||||
}, "fetch settings meta");
|
}, "fetch settings meta");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useAuth } from "./lib/useAuth";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import Layout, { type Page } from "./components/Layout";
|
import Layout, { type Page } from "./components/Layout";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
|
import Settings from "./pages/Settings";
|
||||||
import PlaceholderPage from "./pages/PlaceholderPage";
|
import PlaceholderPage from "./pages/PlaceholderPage";
|
||||||
|
|
||||||
const placeholders: Record<string, { title: string; description: string }> = {
|
const placeholders: Record<string, { title: string; description: string }> = {
|
||||||
@@ -74,6 +75,8 @@ export default function App() {
|
|||||||
<Layout user={user} logout={logout} currentPage={page} onNavigate={setPage}>
|
<Layout user={user} logout={logout} currentPage={page} onNavigate={setPage}>
|
||||||
{page === "dashboard" ? (
|
{page === "dashboard" ? (
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
|
) : page === "settings" ? (
|
||||||
|
<Settings />
|
||||||
) : (
|
) : (
|
||||||
<PlaceholderPage {...placeholders[page]!} />
|
<PlaceholderPage {...placeholders[page]!} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
192
panel/src/lib/useSettings.ts
Normal file
192
panel/src/lib/useSettings.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { get, post, put } from "./api";
|
||||||
|
|
||||||
|
export interface LevelingConfig {
|
||||||
|
base: number;
|
||||||
|
exponent: number;
|
||||||
|
chat: {
|
||||||
|
cooldownMs: number;
|
||||||
|
minXp: number;
|
||||||
|
maxXp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EconomyConfig {
|
||||||
|
daily: {
|
||||||
|
amount: string;
|
||||||
|
streakBonus: string;
|
||||||
|
weeklyBonus: string;
|
||||||
|
cooldownMs: number;
|
||||||
|
};
|
||||||
|
transfers: {
|
||||||
|
allowSelfTransfer: boolean;
|
||||||
|
minAmount: string;
|
||||||
|
};
|
||||||
|
exam: {
|
||||||
|
multMin: number;
|
||||||
|
multMax: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InventoryConfig {
|
||||||
|
maxStackSize: string;
|
||||||
|
maxSlots: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LootdropConfig {
|
||||||
|
activityWindowMs: number;
|
||||||
|
minMessages: number;
|
||||||
|
spawnChance: number;
|
||||||
|
cooldownMs: number;
|
||||||
|
reward: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
currency: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TriviaConfig {
|
||||||
|
entryFee: string;
|
||||||
|
rewardMultiplier: number;
|
||||||
|
timeoutSeconds: number;
|
||||||
|
cooldownMs: number;
|
||||||
|
categories: number[];
|
||||||
|
difficulty: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModerationConfig {
|
||||||
|
prune: {
|
||||||
|
maxAmount: number;
|
||||||
|
confirmThreshold: number;
|
||||||
|
batchSize: number;
|
||||||
|
batchDelayMs: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameSettings {
|
||||||
|
leveling: LevelingConfig;
|
||||||
|
economy: EconomyConfig;
|
||||||
|
inventory: InventoryConfig;
|
||||||
|
lootdrop: LootdropConfig;
|
||||||
|
trivia: TriviaConfig;
|
||||||
|
moderation: ModerationConfig;
|
||||||
|
commands: Record<string, boolean>;
|
||||||
|
system: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuildSettings {
|
||||||
|
guildId: string;
|
||||||
|
configured: boolean;
|
||||||
|
studentRoleId?: string;
|
||||||
|
visitorRoleId?: string;
|
||||||
|
colorRoleIds?: string[];
|
||||||
|
welcomeChannelId?: string;
|
||||||
|
welcomeMessage?: string;
|
||||||
|
feedbackChannelId?: string;
|
||||||
|
terminalChannelId?: string;
|
||||||
|
terminalMessageId?: string;
|
||||||
|
moderationLogChannelId?: string;
|
||||||
|
moderationDmOnWarn?: boolean;
|
||||||
|
moderationAutoTimeoutThreshold?: number;
|
||||||
|
featureOverrides?: Record<string, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingsMeta {
|
||||||
|
guildId?: string;
|
||||||
|
roles: Array<{ id: string; name: string; color: string }>;
|
||||||
|
channels: Array<{ id: string; name: string; type: number }>;
|
||||||
|
commands: Array<{ name: string; category: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSettings() {
|
||||||
|
const [settings, setSettings] = useState<GameSettings | null>(null);
|
||||||
|
const [guildSettings, setGuildSettings] = useState<GuildSettings | null>(null);
|
||||||
|
const [meta, setMeta] = useState<SettingsMeta | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchSettings = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const [settingsData, metaData] = await Promise.all([
|
||||||
|
get<GameSettings>("/api/settings"),
|
||||||
|
get<SettingsMeta>("/api/settings/meta"),
|
||||||
|
]);
|
||||||
|
setSettings(settingsData);
|
||||||
|
setMeta(metaData);
|
||||||
|
|
||||||
|
// Fetch guild settings if we have a guild ID
|
||||||
|
if (metaData.guildId) {
|
||||||
|
const gs = await get<GuildSettings>(
|
||||||
|
`/api/guilds/${metaData.guildId}/settings`
|
||||||
|
);
|
||||||
|
setGuildSettings(gs);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : "Failed to load settings");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSettings();
|
||||||
|
}, [fetchSettings]);
|
||||||
|
|
||||||
|
const saveSettings = useCallback(
|
||||||
|
async (partial: Record<string, unknown>) => {
|
||||||
|
try {
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
await post("/api/settings", partial);
|
||||||
|
const updated = await get<GameSettings>("/api/settings");
|
||||||
|
setSettings(updated);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : "Failed to save settings");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveGuildSettings = useCallback(
|
||||||
|
async (data: Partial<GuildSettings>) => {
|
||||||
|
if (!meta?.guildId) return false;
|
||||||
|
try {
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
await put(`/api/guilds/${meta.guildId}/settings`, data);
|
||||||
|
const updated = await get<GuildSettings>(
|
||||||
|
`/api/guilds/${meta.guildId}/settings`
|
||||||
|
);
|
||||||
|
setGuildSettings(updated);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
setError(
|
||||||
|
e instanceof Error ? e.message : "Failed to save guild settings"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[meta?.guildId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings,
|
||||||
|
guildSettings,
|
||||||
|
meta,
|
||||||
|
loading,
|
||||||
|
saving,
|
||||||
|
error,
|
||||||
|
saveSettings,
|
||||||
|
saveGuildSettings,
|
||||||
|
refetch: fetchSettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
1411
panel/src/pages/Settings.tsx
Normal file
1411
panel/src/pages/Settings.tsx
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user