forked from syntaxbullet/aurorabot
feat: Implement new settings pages and refactor application layout and navigation with new components and hooks.
This commit is contained in:
183
web/src/hooks/use-settings.ts
Normal file
183
web/src/hooks/use-settings.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { toast } from "sonner";
|
||||
|
||||
// Sentinel value for "none" selection
|
||||
export const NONE_VALUE = "__none__";
|
||||
|
||||
// Schema definition matching backend config
|
||||
const bigIntStringSchema = z.coerce.string()
|
||||
.refine((val) => /^\d+$/.test(val), { message: "Must be a valid integer" });
|
||||
|
||||
export const formSchema = z.object({
|
||||
leveling: z.object({
|
||||
base: z.number(),
|
||||
exponent: z.number(),
|
||||
chat: z.object({
|
||||
cooldownMs: z.number(),
|
||||
minXp: z.number(),
|
||||
maxXp: z.number(),
|
||||
})
|
||||
}),
|
||||
economy: z.object({
|
||||
daily: z.object({
|
||||
amount: bigIntStringSchema,
|
||||
streakBonus: bigIntStringSchema,
|
||||
weeklyBonus: bigIntStringSchema,
|
||||
cooldownMs: z.number(),
|
||||
}),
|
||||
transfers: z.object({
|
||||
allowSelfTransfer: z.boolean(),
|
||||
minAmount: bigIntStringSchema,
|
||||
}),
|
||||
exam: z.object({
|
||||
multMin: z.number(),
|
||||
multMax: z.number(),
|
||||
})
|
||||
}),
|
||||
inventory: z.object({
|
||||
maxStackSize: bigIntStringSchema,
|
||||
maxSlots: z.number(),
|
||||
}),
|
||||
commands: z.record(z.string(), z.boolean()).optional(),
|
||||
lootdrop: z.object({
|
||||
activityWindowMs: z.number(),
|
||||
minMessages: z.number(),
|
||||
spawnChance: z.number(),
|
||||
cooldownMs: z.number(),
|
||||
reward: z.object({
|
||||
min: z.number(),
|
||||
max: z.number(),
|
||||
currency: z.string(),
|
||||
})
|
||||
}),
|
||||
studentRole: z.string().optional(),
|
||||
visitorRole: z.string().optional(),
|
||||
colorRoles: z.array(z.string()).default([]),
|
||||
welcomeChannelId: z.string().optional(),
|
||||
welcomeMessage: z.string().optional(),
|
||||
feedbackChannelId: z.string().optional(),
|
||||
terminal: z.object({
|
||||
channelId: z.string(),
|
||||
messageId: z.string()
|
||||
}).optional(),
|
||||
moderation: z.object({
|
||||
prune: z.object({
|
||||
maxAmount: z.number(),
|
||||
confirmThreshold: z.number(),
|
||||
batchSize: z.number(),
|
||||
batchDelayMs: z.number(),
|
||||
}),
|
||||
cases: z.object({
|
||||
dmOnWarn: z.boolean(),
|
||||
logChannelId: z.string().optional(),
|
||||
autoTimeoutThreshold: z.number().optional()
|
||||
})
|
||||
}),
|
||||
trivia: z.object({
|
||||
entryFee: bigIntStringSchema,
|
||||
rewardMultiplier: z.number(),
|
||||
timeoutSeconds: z.number(),
|
||||
cooldownMs: z.number(),
|
||||
categories: z.array(z.number()).default([]),
|
||||
difficulty: z.enum(['easy', 'medium', 'hard', 'random']),
|
||||
}).optional(),
|
||||
system: z.record(z.string(), z.any()).optional(),
|
||||
});
|
||||
|
||||
export type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export interface ConfigMeta {
|
||||
roles: { id: string, name: string, color: string }[];
|
||||
channels: { id: string, name: string, type: number }[];
|
||||
commands: { name: string, category: string }[];
|
||||
}
|
||||
|
||||
export const toSelectValue = (v: string | undefined | null) => v || NONE_VALUE;
|
||||
export const fromSelectValue = (v: string) => v === NONE_VALUE ? "" : v;
|
||||
|
||||
export function useSettings() {
|
||||
const [meta, setMeta] = useState<ConfigMeta | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema) as any,
|
||||
defaultValues: {
|
||||
economy: {
|
||||
daily: { amount: "0", streakBonus: "0", weeklyBonus: "0", cooldownMs: 0 },
|
||||
transfers: { minAmount: "0", allowSelfTransfer: false },
|
||||
exam: { multMin: 1, multMax: 1 }
|
||||
},
|
||||
leveling: { base: 100, exponent: 1.5, chat: { minXp: 10, maxXp: 20, cooldownMs: 60000 } },
|
||||
inventory: { maxStackSize: "1", maxSlots: 10 },
|
||||
moderation: {
|
||||
prune: { maxAmount: 100, confirmThreshold: 50, batchSize: 100, batchDelayMs: 1000 },
|
||||
cases: { dmOnWarn: true }
|
||||
},
|
||||
lootdrop: {
|
||||
spawnChance: 0.05,
|
||||
minMessages: 10,
|
||||
cooldownMs: 300000,
|
||||
activityWindowMs: 600000,
|
||||
reward: { min: 100, max: 500, currency: "AU" }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const loadSettings = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [config, metaData] = await Promise.all([
|
||||
fetch("/api/settings").then(res => res.json()),
|
||||
fetch("/api/settings/meta").then(res => res.json())
|
||||
]);
|
||||
form.reset(config as any);
|
||||
setMeta(metaData);
|
||||
} catch (err) {
|
||||
toast.error("Failed to load settings");
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, [loadSettings]);
|
||||
|
||||
const saveSettings = async (data: FormValues) => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const response = await fetch("/api/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Failed to save");
|
||||
|
||||
toast.success("Settings saved successfully", {
|
||||
description: "Bot configuration has been updated and reloaded."
|
||||
});
|
||||
// Reload settings to ensure we have the latest state
|
||||
await loadSettings();
|
||||
} catch (error) {
|
||||
toast.error("Failed to save settings");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
meta,
|
||||
loading,
|
||||
isSaving,
|
||||
saveSettings,
|
||||
loadSettings
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user