feat: Implement new settings pages and refactor application layout and navigation with new components and hooks.
This commit is contained in:
338
web/src/pages/settings/Systems.tsx
Normal file
338
web/src/pages/settings/Systems.tsx
Normal file
@@ -0,0 +1,338 @@
|
||||
import React from "react";
|
||||
import { useSettingsForm } from "./SettingsLayout";
|
||||
import { FormField, FormItem, FormLabel, FormControl, FormDescription } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { CreditCard, Shield } from "lucide-react";
|
||||
import { fromSelectValue, toSelectValue, NONE_VALUE } from "@/hooks/use-settings";
|
||||
|
||||
export function SystemsSettings() {
|
||||
const { form, meta } = useSettingsForm();
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in slide-up duration-500">
|
||||
<Accordion type="multiple" className="w-full space-y-4" defaultValue={["lootdrop", "moderation"]}>
|
||||
<AccordionItem value="lootdrop" className="border border-border/40 rounded-xl bg-card/30 px-4 transition-all data-[state=open]:border-primary/20 data-[state=open]:bg-card/50">
|
||||
<AccordionTrigger className="hover:no-underline py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-indigo-500/10 flex items-center justify-center text-indigo-500">
|
||||
<CreditCard className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="font-bold">Loot Drops</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="space-y-4 pb-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.spawnChance"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Spawn Chance (0-1)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" step="0.01" min="0" max="1" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.minMessages"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Min Messages</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted/30 p-4 rounded-lg space-y-3">
|
||||
<h4 className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Rewards</h4>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.reward.min"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel className="text-xs">Min</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="h-9 text-sm" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.reward.max"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel className="text-xs">Max</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="h-9 text-sm" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.reward.currency"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel className="text-xs">Currency</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="AU" className="h-9 text-sm" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.cooldownMs"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cooldown (ms)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lootdrop.activityWindowMs"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Activity Window (ms)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="trivia" className="border border-border/40 rounded-xl bg-card/30 px-4 transition-all data-[state=open]:border-primary/20 data-[state=open]:bg-card/50">
|
||||
<AccordionTrigger className="hover:no-underline py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-500 text-sm">
|
||||
🎯
|
||||
</div>
|
||||
<span className="font-bold">Trivia</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="space-y-4 pb-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trivia.entryFee"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Entry Fee (AU)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="text" className="bg-background/50" placeholder="50" />
|
||||
</FormControl>
|
||||
<FormDescription className="text-xs">Cost to play</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trivia.rewardMultiplier"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Reward Multiplier</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" step="0.1" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
<FormDescription className="text-xs">multiplier</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trivia.timeoutSeconds"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Timeout (seconds)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trivia.cooldownMs"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cooldown (ms)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trivia.difficulty"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Difficulty</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-background/50">
|
||||
<SelectValue placeholder="Select difficulty" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="easy">Easy</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="hard">Hard</SelectItem>
|
||||
<SelectItem value="random">Random</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="moderation" className="border border-border/40 rounded-xl bg-card/30 px-4 transition-all data-[state=open]:border-primary/20 data-[state=open]:bg-card/50">
|
||||
<AccordionTrigger className="hover:no-underline py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-red-500/10 flex items-center justify-center text-red-500">
|
||||
<Shield className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="font-bold">Moderation</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="space-y-6 pb-4">
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-bold text-sm text-foreground/80 uppercase tracking-wider">Case Management</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.cases.dmOnWarn"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border border-border/50 bg-background/50 p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-sm font-medium">DM on Warm</FormLabel>
|
||||
<FormDescription className="text-xs">Notify via DM</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.cases.logChannelId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="glass-card p-4 rounded-xl border border-border/50">
|
||||
<FormLabel className="text-sm">Log Channel</FormLabel>
|
||||
<Select onValueChange={v => field.onChange(fromSelectValue(v))} value={toSelectValue(field.value || null)}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-background/50 h-9">
|
||||
<SelectValue placeholder="Select a channel" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value={NONE_VALUE}>None</SelectItem>
|
||||
{meta?.channels.filter(c => c.type === 0).map(c => (
|
||||
<SelectItem key={c.id} value={c.id}>#{c.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.cases.autoTimeoutThreshold"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Auto Timeout Threshold</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" min="0" className="bg-background/50" onChange={e => field.onChange(e.target.value ? Number(e.target.value) : undefined)} />
|
||||
</FormControl>
|
||||
<FormDescription className="text-xs">Warnings before auto-timeout.</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-bold text-sm text-foreground/80 uppercase tracking-wider">Message Pruning</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.prune.maxAmount"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs">Max Amount</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50 h-9" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.prune.confirmThreshold"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs">Confirm Threshold</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50 h-9" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.prune.batchSize"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs">Batch Size</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50 h-9" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="moderation.prune.batchDelayMs"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs">Batch Delay (ms)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" className="bg-background/50 h-9" onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user