Add new features and enhancements to the WebUI and dashboard components
- Updated package.json to include new dependencies: `@radix-ui/react-dialog` and `cmdk`. - Enhanced global styles in globals.css for improved visual depth in dark mode. - Refactored dashboard page components to improve layout and accessibility, including new icons and responsive design adjustments. - Implemented suspense handling in commands page for better loading states. - Improved sidebar navigation with new icons and mobile responsiveness. - Added FieldAnchor components across various forms for better accessibility and structure. - Enhanced commands manager with search parameter handling for improved user experience. - Updated multiple module forms to include FieldAnchor for consistent layout and accessibility.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { AutomodConfigDashboard } from '@nexumi/shared';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -44,19 +45,22 @@ export function AutomodForm({ guildId, initialValue }: AutomodFormProps) {
|
||||
<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>
|
||||
<FieldAnchor id="field-automod-enabled">
|
||||
<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>
|
||||
<Switch
|
||||
checked={value.enabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldAnchor id="field-anti-raid">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.automod.antiRaidTitle')}</CardTitle>
|
||||
@@ -97,7 +101,9 @@ export function AutomodForm({ guildId, initialValue }: AutomodFormProps) {
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.automod.antiRaidActionHint')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-anti-nuke">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.automod.antiNukeTitle')}</CardTitle>
|
||||
@@ -146,18 +152,21 @@ export function AutomodForm({ guildId, initialValue }: AutomodFormProps) {
|
||||
/>
|
||||
</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>
|
||||
<FieldAnchor id="field-lockdown">
|
||||
<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>
|
||||
<Switch
|
||||
checked={value.lockdownActive}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, lockdownActive: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
)}
|
||||
</SettingsForm>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { BirthdayConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -48,28 +49,38 @@ export function BirthdaysForm({ guildId, initialValue }: BirthdaysFormProps) {
|
||||
<CardDescription>{t('modulePages.birthdays.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-birthdays-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.birthdays.enabledLabel')}</Label>
|
||||
<Switch checked={value.enabled} onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<FieldAnchor id="field-birthdays-channel">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={channelId}>{t('modulePages.birthdays.channelId')}</Label>
|
||||
<Input id={channelId} value={value.channelId} onChange={(event) => setValue((prev) => ({ ...prev, channelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-birthdays-role">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={roleId}>{t('modulePages.birthdays.roleId')}</Label>
|
||||
<Input id={roleId} value={value.roleId} onChange={(event) => setValue((prev) => ({ ...prev, roleId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-birthdays-timezone">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={timezoneId}>{t('modulePages.birthdays.timezone')}</Label>
|
||||
<Input id={timezoneId} value={value.timezone} onChange={(event) => setValue((prev) => ({ ...prev, timezone: event.target.value }))} placeholder="Europe/Berlin" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
<FieldAnchor id="field-birthdays-message">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.birthdays.message')}</Label>
|
||||
<Textarea value={value.message ?? ''} onChange={(event) => setValue((prev) => ({ ...prev, message: event.target.value || null }))} placeholder={t('modulePages.birthdays.messagePlaceholder')} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { CaseDashboard } from '@nexumi/shared';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -48,6 +49,7 @@ export function CasesTable({ guildId, initialCases }: CasesTableProps) {
|
||||
}, [action, guildId, search, t]);
|
||||
|
||||
return (
|
||||
<FieldAnchor id="field-mod-cases">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.moderation.casesTitle')}</CardTitle>
|
||||
@@ -126,5 +128,6 @@ export function CasesTable({ guildId, initialCases }: CasesTableProps) {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import type { CommandOverrideDashboard } from '@nexumi/shared';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -32,9 +33,17 @@ interface CommandsManagerProps {
|
||||
|
||||
export function CommandsManager({ guildId, initialCommands }: CommandsManagerProps) {
|
||||
const t = useTranslations();
|
||||
const searchParams = useSearchParams();
|
||||
const [commands, setCommands] = useState<LocalOverride[]>(initialCommands);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const q = searchParams.get('q');
|
||||
if (q) {
|
||||
setSearch(q);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const query = search.trim().toLowerCase();
|
||||
if (!query) {
|
||||
@@ -64,7 +73,9 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
cooldownSeconds: entry.cooldownSeconds
|
||||
})
|
||||
});
|
||||
const body = (await response.json().catch(() => null)) as (CommandOverrideDashboard & { error?: string }) | null;
|
||||
const body = (await response.json().catch(() => null)) as
|
||||
| (CommandOverrideDashboard & { error?: string })
|
||||
| null;
|
||||
if (!response.ok || !body) {
|
||||
toast.error(body?.error ?? t('common.saveError'));
|
||||
return;
|
||||
@@ -81,7 +92,9 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
async function handleReset(entry: LocalOverride) {
|
||||
patchLocal(entry.commandName, { saving: true });
|
||||
try {
|
||||
const response = await fetch(`/api/guilds/${guildId}/commands/${entry.commandName}`, { method: 'DELETE' });
|
||||
const response = await fetch(`/api/guilds/${guildId}/commands/${entry.commandName}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const body = (await response.json().catch(() => null)) as CommandOverrideDashboard | null;
|
||||
if (!response.ok || !body) {
|
||||
toast.error(t('common.saveError'));
|
||||
@@ -147,7 +160,9 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.commands.allowedRoleIds')}</p>
|
||||
<Input
|
||||
value={toCsv(entry.allowedRoleIds)}
|
||||
onChange={(event) => patchLocal(entry.commandName, { allowedRoleIds: fromCsv(event.target.value) })}
|
||||
onChange={(event) =>
|
||||
patchLocal(entry.commandName, { allowedRoleIds: fromCsv(event.target.value) })
|
||||
}
|
||||
placeholder={t('modulePages.commands.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
@@ -155,7 +170,9 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.commands.deniedRoleIds')}</p>
|
||||
<Input
|
||||
value={toCsv(entry.deniedRoleIds)}
|
||||
onChange={(event) => patchLocal(entry.commandName, { deniedRoleIds: fromCsv(event.target.value) })}
|
||||
onChange={(event) =>
|
||||
patchLocal(entry.commandName, { deniedRoleIds: fromCsv(event.target.value) })
|
||||
}
|
||||
placeholder={t('modulePages.commands.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
@@ -164,7 +181,9 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
<Input
|
||||
value={toCsv(entry.allowedChannelIds)}
|
||||
onChange={(event) =>
|
||||
patchLocal(entry.commandName, { allowedChannelIds: fromCsv(event.target.value) })
|
||||
patchLocal(entry.commandName, {
|
||||
allowedChannelIds: fromCsv(event.target.value)
|
||||
})
|
||||
}
|
||||
placeholder={t('modulePages.commands.idsPlaceholder')}
|
||||
/>
|
||||
@@ -174,18 +193,32 @@ export function CommandsManager({ guildId, initialCommands }: CommandsManagerPro
|
||||
<Input
|
||||
value={toCsv(entry.deniedChannelIds)}
|
||||
onChange={(event) =>
|
||||
patchLocal(entry.commandName, { deniedChannelIds: fromCsv(event.target.value) })
|
||||
patchLocal(entry.commandName, {
|
||||
deniedChannelIds: fromCsv(event.target.value)
|
||||
})
|
||||
}
|
||||
placeholder={t('modulePages.commands.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="ghost" size="sm" disabled={entry.saving} onClick={() => handleReset(entry)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={entry.saving}
|
||||
onClick={() => handleReset(entry)}
|
||||
>
|
||||
<RotateCcw className="size-4" />
|
||||
{t('modulePages.commands.reset')}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" disabled={entry.saving} onClick={() => handleSave(entry)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={entry.saving}
|
||||
onClick={() => handleSave(entry)}
|
||||
>
|
||||
{entry.saving ? t('common.saving') : t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { EconomyConfigDashboard } from '@nexumi/shared';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -44,6 +45,7 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
<CardDescription>{t('modulePages.economy.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-economy-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.economy.enabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -51,7 +53,9 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-economy-currency-name">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.currencyName')}</Label>
|
||||
<Input
|
||||
@@ -59,6 +63,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, currencyName: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-currency-symbol">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.currencySymbol')}</Label>
|
||||
<Input
|
||||
@@ -66,6 +72,7 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, currencySymbol: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -75,6 +82,7 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
<CardTitle>{t('modulePages.economy.amountsTitle')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-economy-daily">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.dailyAmount')}</Label>
|
||||
<Input
|
||||
@@ -84,6 +92,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, dailyAmount: Number(event.target.value) || 0 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-weekly">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.weeklyAmount')}</Label>
|
||||
<Input
|
||||
@@ -93,6 +103,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, weeklyAmount: Number(event.target.value) || 0 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-work-min">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.workMin')}</Label>
|
||||
<Input
|
||||
@@ -102,6 +114,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, workMin: Number(event.target.value) || 0 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-work-max">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.workMax')}</Label>
|
||||
<Input
|
||||
@@ -111,6 +125,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, workMax: Number(event.target.value) || 0 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-work-cooldown">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.workCooldownSeconds')}</Label>
|
||||
<Input
|
||||
@@ -122,6 +138,8 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-economy-starting">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.economy.startingBalance')}</Label>
|
||||
<Input
|
||||
@@ -133,6 +151,7 @@ export function EconomyForm({ guildId, initialValue }: EconomyFormProps) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { SocialFeedDashboard } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -152,6 +153,7 @@ export function FeedsManager({ guildId, initialFeeds }: FeedsManagerProps) {
|
||||
</div>
|
||||
))}
|
||||
|
||||
<FieldAnchor id="field-feeds-add">
|
||||
<div className="space-y-3 rounded-md border border-dashed border-border p-4">
|
||||
<p className="text-sm font-medium">{t('modulePages.feeds.addFeed')}</p>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
@@ -208,6 +210,7 @@ export function FeedsManager({ guildId, initialFeeds }: FeedsManagerProps) {
|
||||
{t('modulePages.feeds.addFeed')}
|
||||
</Button>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { FunConfigDashboard } from '@nexumi/shared';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -42,6 +43,7 @@ export function FunForm({ guildId, initialValue }: FunFormProps) {
|
||||
<CardDescription>{t('modulePages.fun.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-fun-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.fun.enabledLabel')}</Label>
|
||||
@@ -52,6 +54,8 @@ export function FunForm({ guildId, initialValue }: FunFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-fun-media">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.fun.mediaEnabledLabel')}</Label>
|
||||
@@ -62,6 +66,7 @@ export function FunForm({ guildId, initialValue }: FunFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, mediaEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { GiveawayDashboard } from '@nexumi/shared';
|
||||
import { Pause, Play, Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -116,6 +117,7 @@ export function GiveawaysManager({ guildId, initialGiveaways }: GiveawaysManager
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<FieldAnchor id="field-giveaways-create">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.giveaways.createTitle')}</CardTitle>
|
||||
@@ -187,6 +189,9 @@ export function GiveawaysManager({ guildId, initialGiveaways }: GiveawaysManager
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-giveaways-list">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -281,6 +286,7 @@ export function GiveawaysManager({ guildId, initialGiveaways }: GiveawaysManager
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { GuildBackupDashboard } from '@nexumi/shared';
|
||||
import { Download, Info, Plus, RotateCcw, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -130,6 +131,7 @@ export function GuildBackupManager({ guildId, initialBackups, isOwner }: GuildBa
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldAnchor id="field-backup">
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -211,5 +213,6 @@ export function GuildBackupManager({ guildId, initialBackups, isOwner }: GuildBa
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { LevelingConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -126,6 +127,7 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
<CardDescription>{t('modulePages.leveling.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-leveling-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.leveling.enabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -133,8 +135,10 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<FieldAnchor id="field-leveling-text-min">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.textXpMin')}</Label>
|
||||
<Input
|
||||
@@ -144,6 +148,8 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, textXpMin: Number(event.target.value) || 1 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leveling-text-max">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.textXpMax')}</Label>
|
||||
<Input
|
||||
@@ -153,6 +159,8 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, textXpMax: Number(event.target.value) || 1 }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leveling-cooldown">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.textCooldownSeconds')}</Label>
|
||||
<Input
|
||||
@@ -164,8 +172,10 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
|
||||
<FieldAnchor id="field-leveling-voice">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.voiceXpPerMinute')}</Label>
|
||||
<Input
|
||||
@@ -178,7 +188,9 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-leveling-no-xp-channels">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={noXpChannelsId}>{t('modulePages.leveling.noXpChannels')}</Label>
|
||||
<Textarea
|
||||
@@ -188,6 +200,8 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
placeholder={t('modulePages.logging.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leveling-no-xp-roles">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={noXpRolesId}>{t('modulePages.leveling.noXpRoles')}</Label>
|
||||
<Textarea
|
||||
@@ -197,8 +211,10 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
placeholder={t('modulePages.logging.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-leveling-role-multipliers">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.roleMultipliers')}</Label>
|
||||
<Textarea
|
||||
@@ -210,6 +226,8 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.leveling.multiplierHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leveling-channel-multipliers">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.channelMultipliers')}</Label>
|
||||
<Textarea
|
||||
@@ -221,10 +239,12 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.leveling.multiplierHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldAnchor id="field-leveling-levelup">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.leveling.levelUpTitle')}</CardTitle>
|
||||
@@ -266,6 +286,7 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
placeholder="Congrats {user}, you reached level {level}!"
|
||||
/>
|
||||
</div>
|
||||
<FieldAnchor id="field-leveling-stack">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.leveling.stackRewards')}</Label>
|
||||
<Switch
|
||||
@@ -273,9 +294,12 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, stackRewards: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-leveling-rank-card">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.leveling.rankCardTitle')}</CardTitle>
|
||||
@@ -313,6 +337,7 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
)}
|
||||
</SettingsForm>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { LogChannelMapping, LogEventTypeDashboard, LoggingConfigDashboard } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -129,6 +130,7 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
<CardDescription>{t('modulePages.logging.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-logging-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.logging.enabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -136,6 +138,8 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-logging-ignore-bots">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.logging.ignoreBotsLabel')}</Label>
|
||||
<Switch
|
||||
@@ -143,6 +147,8 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, ignoreBots: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-logging-ignored-channels">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={ignoredChannelsId}>{t('modulePages.logging.ignoredChannelsLabel')}</Label>
|
||||
<Textarea
|
||||
@@ -153,6 +159,8 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.logging.idsHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-logging-ignored-roles">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={ignoredRolesId}>{t('modulePages.logging.ignoredRolesLabel')}</Label>
|
||||
<Textarea
|
||||
@@ -163,6 +171,8 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.logging.idsHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-logging-retention">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="logging-retention">{t('modulePages.logging.retentionLabel')}</Label>
|
||||
<Input
|
||||
@@ -178,9 +188,11 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.logging.retentionHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldAnchor id="field-logging-channels">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.logging.channelsTitle')}</CardTitle>
|
||||
@@ -268,6 +280,7 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
)}
|
||||
</SettingsForm>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { EscalationAction, EscalationRuleDashboard, ModerationDashboard } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -160,71 +161,75 @@ export function ModerationForm({ guildId, initialValue }: ModerationFormProps) {
|
||||
<CardDescription>{t('modulePages.moderation.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.moderation.enabledLabel')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.moderation.enabledHint')}</p>
|
||||
<FieldAnchor id="field-mod-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.moderation.enabledLabel')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.moderation.enabledHint')}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={value.moderationEnabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, moderationEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
checked={value.moderationEnabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, moderationEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.moderation.escalationsTitle')}</CardTitle>
|
||||
<CardDescription>{t('modulePages.moderation.escalationsDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{value.escalations.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.moderation.noEscalations')}</p>
|
||||
)}
|
||||
{value.escalations.map((rule) => (
|
||||
<EscalationRow
|
||||
key={rule.clientKey}
|
||||
rule={rule}
|
||||
onChange={(updated) =>
|
||||
<FieldAnchor id="field-mod-escalations">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.moderation.escalationsTitle')}</CardTitle>
|
||||
<CardDescription>{t('modulePages.moderation.escalationsDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{value.escalations.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.moderation.noEscalations')}</p>
|
||||
)}
|
||||
{value.escalations.map((rule) => (
|
||||
<EscalationRow
|
||||
key={rule.clientKey}
|
||||
rule={rule}
|
||||
onChange={(updated) =>
|
||||
setValue((prev) => ({
|
||||
...prev,
|
||||
escalations: prev.escalations.map((entry) => (entry.clientKey === rule.clientKey ? updated : entry))
|
||||
}))
|
||||
}
|
||||
onRemove={() =>
|
||||
setValue((prev) => ({
|
||||
...prev,
|
||||
escalations: prev.escalations.filter((entry) => entry.clientKey !== rule.clientKey)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
setValue((prev) => ({
|
||||
...prev,
|
||||
escalations: prev.escalations.map((entry) => (entry.clientKey === rule.clientKey ? updated : entry))
|
||||
escalations: [
|
||||
...prev.escalations,
|
||||
{
|
||||
clientKey: `new-${Date.now()}-${prev.escalations.length}`,
|
||||
warnCount: prev.escalations.length + 1,
|
||||
action: 'TIMEOUT',
|
||||
durationMs: 3_600_000,
|
||||
reason: null,
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
onRemove={() =>
|
||||
setValue((prev) => ({
|
||||
...prev,
|
||||
escalations: prev.escalations.filter((entry) => entry.clientKey !== rule.clientKey)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
setValue((prev) => ({
|
||||
...prev,
|
||||
escalations: [
|
||||
...prev.escalations,
|
||||
{
|
||||
clientKey: `new-${Date.now()}-${prev.escalations.length}`,
|
||||
warnCount: prev.escalations.length + 1,
|
||||
action: 'TIMEOUT',
|
||||
durationMs: 3_600_000,
|
||||
reason: null,
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
{t('modulePages.moderation.addEscalation')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
{t('modulePages.moderation.addEscalation')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
)}
|
||||
</SettingsForm>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ScheduledMessageDashboard } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -97,6 +98,7 @@ export function SchedulerManager({ guildId, initialSchedules }: SchedulerManager
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<FieldAnchor id="field-scheduler-create">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.scheduler.createTitle')}</CardTitle>
|
||||
@@ -153,6 +155,7 @@ export function SchedulerManager({ guildId, initialSchedules }: SchedulerManager
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { SelfRoleBehavior, SelfRoleEntry, SelfRoleMode, SelfRolePanelDashbo
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -153,6 +154,7 @@ export function SelfRolesManager({ guildId, initialPanels }: SelfRolesManagerPro
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<FieldAnchor id="field-selfroles-create">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.selfroles.createTitle')}</CardTitle>
|
||||
@@ -219,6 +221,9 @@ export function SelfRolesManager({ guildId, initialPanels }: SelfRolesManagerPro
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-selfroles-list">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -313,6 +318,7 @@ export function SelfRolesManager({ guildId, initialPanels }: SelfRolesManagerPro
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { StarboardConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -83,33 +84,45 @@ export function StarboardForm({ guildId, initialValue }: StarboardFormProps) {
|
||||
<CardDescription>{t('modulePages.starboard.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-starboard-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.starboard.enabledLabel')}</Label>
|
||||
<Switch checked={value.enabled} onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<FieldAnchor id="field-starboard-channel">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={channelId}>{t('modulePages.starboard.channelId')}</Label>
|
||||
<Input id={channelId} value={value.channelId} onChange={(event) => setValue((prev) => ({ ...prev, channelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-starboard-emoji">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={emojiId}>{t('modulePages.starboard.emoji')}</Label>
|
||||
<Input id={emojiId} value={value.emoji} onChange={(event) => setValue((prev) => ({ ...prev, emoji: event.target.value }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-starboard-threshold">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={thresholdId}>{t('modulePages.starboard.threshold')}</Label>
|
||||
<Input id={thresholdId} type="number" min={1} value={value.threshold} onChange={(event) => setValue((prev) => ({ ...prev, threshold: Number(event.target.value) || 1 }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
<FieldAnchor id="field-starboard-self">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.starboard.allowSelfStar')}</Label>
|
||||
<Switch checked={value.allowSelfStar} onCheckedChange={(allowSelfStar) => setValue((prev) => ({ ...prev, allowSelfStar }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-starboard-ignored">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={ignoredId}>{t('modulePages.starboard.ignoredChannelIds')}</Label>
|
||||
<Textarea id={ignoredId} value={value.ignoredChannelIdsText} onChange={(event) => setValue((prev) => ({ ...prev, ignoredChannelIdsText: event.target.value }))} />
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.starboard.idsHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { StatsConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -50,31 +51,39 @@ export function StatsForm({ guildId, initialValue }: StatsFormProps) {
|
||||
<CardDescription>{t('modulePages.stats.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-stats-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.stats.enabledLabel')}</Label>
|
||||
<Switch checked={value.enabled} onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-stats-members">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={membersId}>{t('modulePages.stats.membersChannelId')}</Label>
|
||||
<Input id={membersId} value={value.membersChannelId} onChange={(event) => setValue((prev) => ({ ...prev, membersChannelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={membersTemplateId}>{t('modulePages.stats.membersTemplate')}</Label>
|
||||
<Input id={membersTemplateId} value={value.membersTemplate} onChange={(event) => setValue((prev) => ({ ...prev, membersTemplate: event.target.value }))} />
|
||||
</div>
|
||||
<FieldAnchor id="field-stats-online">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={onlineId}>{t('modulePages.stats.onlineChannelId')}</Label>
|
||||
<Input id={onlineId} value={value.onlineChannelId} onChange={(event) => setValue((prev) => ({ ...prev, onlineChannelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={onlineTemplateId}>{t('modulePages.stats.onlineTemplate')}</Label>
|
||||
<Input id={onlineTemplateId} value={value.onlineTemplate} onChange={(event) => setValue((prev) => ({ ...prev, onlineTemplate: event.target.value }))} />
|
||||
</div>
|
||||
<FieldAnchor id="field-stats-boosts">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={boostsId}>{t('modulePages.stats.boostsChannelId')}</Label>
|
||||
<Input id={boostsId} value={value.boostsChannelId} onChange={(event) => setValue((prev) => ({ ...prev, boostsChannelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={boostsTemplateId}>{t('modulePages.stats.boostsTemplate')}</Label>
|
||||
<Input id={boostsTemplateId} value={value.boostsTemplate} onChange={(event) => setValue((prev) => ({ ...prev, boostsTemplate: event.target.value }))} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { SuggestionConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -53,24 +54,33 @@ export function SuggestionsConfigForm({ guildId, initialValue }: SuggestionsConf
|
||||
<CardDescription>{t('modulePages.suggestions.configDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-suggestions-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.suggestions.enabledLabel')}</Label>
|
||||
<Switch checked={value.enabled} onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<FieldAnchor id="field-suggestions-open">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={openId}>{t('modulePages.suggestions.openChannelId')}</Label>
|
||||
<Input id={openId} value={value.openChannelId} onChange={(event) => setValue((prev) => ({ ...prev, openChannelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-suggestions-approved">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={approvedId}>{t('modulePages.suggestions.approvedChannelId')}</Label>
|
||||
<Input id={approvedId} value={value.approvedChannelId} onChange={(event) => setValue((prev) => ({ ...prev, approvedChannelId: event.target.value.trim() }))} placeholder={t('common.optional')} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-suggestions-denied">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={deniedId}>{t('modulePages.suggestions.deniedChannelId')}</Label>
|
||||
<Input id={deniedId} value={value.deniedChannelId} onChange={(event) => setValue((prev) => ({ ...prev, deniedChannelId: event.target.value.trim() }))} placeholder={t('common.optional')} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
<FieldAnchor id="field-suggestions-thread">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.suggestions.createThread')}</Label>
|
||||
@@ -78,6 +88,7 @@ export function SuggestionsConfigForm({ guildId, initialValue }: SuggestionsConf
|
||||
</div>
|
||||
<Switch checked={value.createThread} onCheckedChange={(createThread) => setValue((prev) => ({ ...prev, createThread }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { SuggestionAction, SuggestionDashboard } from '@nexumi/shared';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Badge, type BadgeProps } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -82,6 +83,7 @@ export function SuggestionsListManager({ guildId, initialSuggestions }: Suggesti
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldAnchor id="field-suggestions-list">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
@@ -174,5 +176,6 @@ export function SuggestionsListManager({ guildId, initialSuggestions }: Suggesti
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TagDashboard, TagResponseType } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -144,6 +145,7 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<FieldAnchor id="field-tags-create">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.tags.createTitle')}</CardTitle>
|
||||
@@ -195,6 +197,9 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-tags-list">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -256,6 +261,7 @@ export function TagsManager({ guildId, initialTags }: TagsManagerProps) {
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { TempVoiceConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -48,27 +49,37 @@ export function TempVoiceForm({ guildId, initialValue }: TempVoiceFormProps) {
|
||||
<CardDescription>{t('modulePages.tempvoice.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-tempvoice-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.tempvoice.enabledLabel')}</Label>
|
||||
<Switch checked={value.enabled} onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-tempvoice-hub">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={hubId}>{t('modulePages.tempvoice.hubChannelId')}</Label>
|
||||
<Input id={hubId} value={value.hubChannelId} onChange={(event) => setValue((prev) => ({ ...prev, hubChannelId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-tempvoice-category">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={categoryId}>{t('modulePages.tempvoice.categoryId')}</Label>
|
||||
<Input id={categoryId} value={value.categoryId} onChange={(event) => setValue((prev) => ({ ...prev, categoryId: event.target.value.trim() }))} placeholder="123456789012345678" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-tempvoice-name">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={nameId}>{t('modulePages.tempvoice.defaultName')}</Label>
|
||||
<Input id={nameId} value={value.defaultName} onChange={(event) => setValue((prev) => ({ ...prev, defaultName: event.target.value }))} placeholder="{user}'s Channel" />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-tempvoice-limit">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={limitId}>{t('modulePages.tempvoice.defaultLimit')}</Label>
|
||||
<Input id={limitId} type="number" min={0} max={99} value={value.defaultLimit} onChange={(event) => setValue((prev) => ({ ...prev, defaultLimit: Number(event.target.value) || 0 }))} />
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.tempvoice.defaultLimitHint')}</p>
|
||||
</CardContent>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TicketCategoryDashboard } from '@nexumi/shared';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -120,6 +121,7 @@ export function TicketCategoriesManager({ guildId, initialCategories }: TicketCa
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldAnchor id="field-tickets-categories">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.tickets.categoriesTitle')}</CardTitle>
|
||||
@@ -235,5 +237,6 @@ export function TicketCategoriesManager({ guildId, initialCategories }: TicketCa
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FieldAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { TicketConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -51,6 +52,7 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
<CardDescription>{t('modulePages.tickets.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-tickets-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.tickets.enabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -58,8 +60,10 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
onCheckedChange={(enabled) => setValue((prev) => ({ ...prev, enabled }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-tickets-mode">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.tickets.mode')}</Label>
|
||||
<Select value={value.mode} onValueChange={(mode) => setValue((prev) => ({ ...prev, mode: mode as TicketConfigDashboard['mode'] }))}>
|
||||
@@ -72,7 +76,9 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-tickets-category">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={categoryId}>{t('modulePages.tickets.categoryId')}</Label>
|
||||
<Input
|
||||
@@ -82,7 +88,9 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-tickets-log">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={logChannelId}>{t('modulePages.tickets.logChannelId')}</Label>
|
||||
<Input
|
||||
@@ -92,7 +100,9 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-tickets-inactivity">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={inactivityId}>{t('modulePages.tickets.inactivityHours')}</Label>
|
||||
<Input
|
||||
@@ -105,8 +115,10 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
|
||||
<FieldAnchor id="field-tickets-transcript">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.tickets.transcriptDm')}</Label>
|
||||
@@ -117,7 +129,9 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
onCheckedChange={(transcriptDm) => setValue((prev) => ({ ...prev, transcriptDm }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<FieldAnchor id="field-tickets-rating">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.tickets.ratingEnabled')}</Label>
|
||||
<Switch
|
||||
@@ -125,6 +139,7 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
onCheckedChange={(ratingEnabled) => setValue((prev) => ({ ...prev, ratingEnabled }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { VerificationConfigDashboard } from '@nexumi/shared';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -47,6 +48,7 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
<CardDescription>{t('modulePages.verification.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-verify-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.verification.enabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -54,8 +56,10 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-verify-mode">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.mode')}</Label>
|
||||
<Select
|
||||
@@ -73,6 +77,8 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-verify-fail">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.failAction')}</Label>
|
||||
<Select
|
||||
@@ -91,9 +97,11 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<FieldAnchor id="field-verify-channel">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.channelId')}</Label>
|
||||
<Input
|
||||
@@ -102,6 +110,8 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-verify-role">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.verifiedRoleId')}</Label>
|
||||
<Input
|
||||
@@ -110,6 +120,8 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-verify-unverified">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.unverifiedRoleId')}</Label>
|
||||
<Input
|
||||
@@ -118,8 +130,10 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
|
||||
<FieldAnchor id="field-verify-age">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.verification.minAccountAgeDays')}</Label>
|
||||
<Input
|
||||
@@ -132,6 +146,7 @@ export function VerificationForm({ guildId, initialValue }: VerificationFormProp
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { WelcomeConfigDashboard } from '@nexumi/shared';
|
||||
import { useId } from 'react';
|
||||
import { FieldAnchor } from '@/components/layout/field-anchor';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -116,6 +117,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
<CardDescription>{t('modulePages.welcome.welcomeDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-welcome-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.welcome.welcomeEnabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -123,7 +125,9 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, welcomeEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-welcome-channel">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.welcomeChannelId')}</Label>
|
||||
<Input
|
||||
@@ -132,6 +136,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-type">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.welcomeType')}</Label>
|
||||
<Select
|
||||
@@ -150,7 +156,9 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
<FieldAnchor id="field-welcome-content">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.welcomeContent')}</Label>
|
||||
<Textarea
|
||||
@@ -160,6 +168,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.welcome.placeholdersHint')}</p>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-embed">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.welcomeEmbed')}</Label>
|
||||
<Textarea
|
||||
@@ -170,6 +180,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder='{ "title": "Welcome!", "color": 6591981 }'
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-dm">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.welcome.welcomeDmEnabled')}</Label>
|
||||
@@ -184,6 +196,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder={t('modulePages.welcome.dmContentPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-user-autoroles">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={userAutorolesId}>{t('modulePages.welcome.userAutoroles')}</Label>
|
||||
<Textarea
|
||||
@@ -193,6 +207,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder={t('modulePages.logging.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-bot-autoroles">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={botAutorolesId}>{t('modulePages.welcome.botAutoroles')}</Label>
|
||||
<Textarea
|
||||
@@ -202,7 +218,9 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder={t('modulePages.logging.idsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<FieldAnchor id="field-welcome-image-title">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.imageTitle')}</Label>
|
||||
<Input
|
||||
@@ -210,6 +228,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, imageTitle: event.target.value || null }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-welcome-image-subtitle">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.imageSubtitle')}</Label>
|
||||
<Input
|
||||
@@ -217,6 +237,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, imageSubtitle: event.target.value || null }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -227,6 +248,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
<CardDescription>{t('modulePages.welcome.leaveDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<FieldAnchor id="field-leave-enabled">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.welcome.leaveEnabledLabel')}</Label>
|
||||
<Switch
|
||||
@@ -234,6 +256,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, leaveEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leave-channel">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.leaveChannelId')}</Label>
|
||||
<Input
|
||||
@@ -242,6 +266,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder="123456789012345678"
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leave-content">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.leaveContent')}</Label>
|
||||
<Textarea
|
||||
@@ -250,6 +276,8 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder={t('modulePages.welcome.contentPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
<FieldAnchor id="field-leave-embed">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.welcome.leaveEmbed')}</Label>
|
||||
<Textarea
|
||||
@@ -260,6 +288,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
||||
placeholder='{ "title": "Goodbye", "color": 15158332 }'
|
||||
/>
|
||||
</div>
|
||||
</FieldAnchor>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user