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:
TheOnlyMace
2026-07-22 19:44:55 +02:00
parent f1d74f922d
commit 75ac91eecc
49 changed files with 2393 additions and 320 deletions

View File

@@ -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>