- Added OWNER_USER_IDS to .env.example for specifying global bot owners. - Introduced new models in the Prisma schema: CommandOverride, OwnerTeamMember, GlobalUserBlacklist, GuildBlacklist, FeatureFlag, BotPresenceConfig, ChangelogEntry, and OwnerAuditLog to support advanced bot management features. - Updated env.ts to preprocess OWNER_USER_IDS for better handling of global bot owner IDs. - Modified ModulePlaceholderPage to ensure proper routing for remaining dashboard modules.
166 lines
7.0 KiB
TypeScript
166 lines
7.0 KiB
TypeScript
'use client';
|
|
|
|
import type { AutomodConfigDashboard } from '@nexumi/shared';
|
|
import { useTranslations } from '@/components/locale-provider';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import type { SettingsSaveResult } from '@/components/settings/settings-form';
|
|
import { SettingsForm } from '@/components/settings/settings-form';
|
|
|
|
async function saveAutomod(guildId: string, value: AutomodConfigDashboard): Promise<SettingsSaveResult> {
|
|
try {
|
|
const response = await fetch(`/api/guilds/${guildId}/automod`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(value)
|
|
});
|
|
if (!response.ok) {
|
|
const body = (await response.json().catch(() => null)) as { error?: string } | null;
|
|
return { ok: false, error: body?.error ?? `Request failed with status ${response.status}` };
|
|
}
|
|
return { ok: true };
|
|
} catch {
|
|
return { ok: false, error: 'Network error' };
|
|
}
|
|
}
|
|
|
|
interface AutomodFormProps {
|
|
guildId: string;
|
|
initialValue: AutomodConfigDashboard;
|
|
}
|
|
|
|
export function AutomodForm({ guildId, initialValue }: AutomodFormProps) {
|
|
const t = useTranslations();
|
|
|
|
return (
|
|
<SettingsForm<AutomodConfigDashboard> initialValue={initialValue} onSave={(value) => saveAutomod(guildId, value)}>
|
|
{({ value, setValue }) => (
|
|
<div className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('modulePages.automod.title')}</CardTitle>
|
|
<CardDescription>{t('modulePages.automod.description')}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<div className="space-y-0.5">
|
|
<Label>{t('modulePages.automod.enabledLabel')}</Label>
|
|
<p className="text-sm text-muted-foreground">{t('modulePages.automod.enabledHint')}</p>
|
|
</div>
|
|
<Switch
|
|
checked={value.enabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('modulePages.automod.antiRaidTitle')}</CardTitle>
|
|
<CardDescription>{t('modulePages.automod.antiRaidDescription')}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<Label>{t('modulePages.automod.antiRaidEnabled')}</Label>
|
|
<Switch
|
|
checked={value.antiRaidEnabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, antiRaidEnabled: checked }))}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.automod.antiRaidJoinThreshold')}</Label>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
value={value.antiRaidJoinThreshold}
|
|
onChange={(event) =>
|
|
setValue((prev) => ({ ...prev, antiRaidJoinThreshold: Number(event.target.value) || 1 }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.automod.antiRaidWindowSeconds')}</Label>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
value={value.antiRaidWindowSeconds}
|
|
onChange={(event) =>
|
|
setValue((prev) => ({ ...prev, antiRaidWindowSeconds: Number(event.target.value) || 1 }))
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{t('modulePages.automod.antiRaidActionHint')}</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('modulePages.automod.antiNukeTitle')}</CardTitle>
|
|
<CardDescription>{t('modulePages.automod.antiNukeDescription')}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<Label>{t('modulePages.automod.antiNukeEnabled')}</Label>
|
|
<Switch
|
|
checked={value.antiNukeEnabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, antiNukeEnabled: checked }))}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-4 sm:grid-cols-3">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.automod.antiNukeBanThreshold')}</Label>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
value={value.antiNukeBanThreshold}
|
|
onChange={(event) =>
|
|
setValue((prev) => ({ ...prev, antiNukeBanThreshold: Number(event.target.value) || 1 }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.automod.antiNukeChannelThreshold')}</Label>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
value={value.antiNukeChannelThreshold}
|
|
onChange={(event) =>
|
|
setValue((prev) => ({ ...prev, antiNukeChannelThreshold: Number(event.target.value) || 1 }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.automod.antiNukeWindowSeconds')}</Label>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
value={value.antiNukeWindowSeconds}
|
|
onChange={(event) =>
|
|
setValue((prev) => ({ ...prev, antiNukeWindowSeconds: Number(event.target.value) || 1 }))
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<div className="space-y-0.5">
|
|
<Label>{t('modulePages.automod.lockdownActive')}</Label>
|
|
<p className="text-sm text-muted-foreground">{t('modulePages.automod.lockdownActiveHint')}</p>
|
|
</div>
|
|
<Switch
|
|
checked={value.lockdownActive}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, lockdownActive: checked }))}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
</SettingsForm>
|
|
);
|
|
}
|