diff --git a/apps/webui/src/app/dashboard/[guildId]/commands/page.tsx b/apps/webui/src/app/dashboard/[guildId]/commands/page.tsx
index 01ac972..68d7f21 100644
--- a/apps/webui/src/app/dashboard/[guildId]/commands/page.tsx
+++ b/apps/webui/src/app/dashboard/[guildId]/commands/page.tsx
@@ -1,6 +1,6 @@
import { Suspense } from 'react';
import { CommandsManager } from '@/components/modules/commands-manager';
-import { listGuildChannelsForDashboard, listGuildRolesForDashboard } from '@/lib/discord-guild-resources';
+import { listGuildChannelsForDashboard } from '@/lib/discord-guild-resources';
import { getLocale, t } from '@/lib/i18n';
import { getCommandGlobalRules } from '@/lib/module-configs/command-global-rules';
import { listCommandOverridesDashboard } from '@/lib/module-configs/commands';
@@ -11,12 +11,11 @@ interface CommandsPageProps {
export default async function CommandsPage({ params }: CommandsPageProps) {
const { guildId } = await params;
- const [locale, commands, globalRules, channels, roles] = await Promise.all([
+ const [locale, commands, globalRules, channels] = await Promise.all([
getLocale(),
listCommandOverridesDashboard(guildId),
getCommandGlobalRules(guildId),
- listGuildChannelsForDashboard(guildId).catch(() => []),
- listGuildRolesForDashboard(guildId).catch(() => [])
+ listGuildChannelsForDashboard(guildId).catch(() => [])
]);
return (
@@ -31,7 +30,6 @@ export default async function CommandsPage({ params }: CommandsPageProps) {
initialCommands={commands}
initialGlobalRules={globalRules}
channels={channels}
- roles={roles}
/>
diff --git a/apps/webui/src/components/modules/birthdays-form.tsx b/apps/webui/src/components/modules/birthdays-form.tsx
index c1f3dde..cccadb4 100644
--- a/apps/webui/src/components/modules/birthdays-form.tsx
+++ b/apps/webui/src/components/modules/birthdays-form.tsx
@@ -5,6 +5,8 @@ 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 { DiscordChannelSelect } from '@/components/ui/discord-channel-select';
+import { DiscordRoleSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
@@ -36,8 +38,6 @@ interface BirthdaysFormProps {
export function BirthdaysForm({ guildId, initialValue }: BirthdaysFormProps) {
const t = useTranslations();
- const channelId = useId();
- const roleId = useId();
const timezoneId = useId();
return (
@@ -58,14 +58,22 @@ export function BirthdaysForm({ guildId, initialValue }: BirthdaysFormProps) {
-
- setValue((prev) => ({ ...prev, channelId: event.target.value.trim() }))} placeholder="123456789012345678" />
+
+ setValue((prev) => ({ ...prev, channelId }))}
+ />
-
- setValue((prev) => ({ ...prev, roleId: event.target.value.trim() }))} placeholder="123456789012345678" />
+
+ setValue((prev) => ({ ...prev, roleId }))}
+ />
diff --git a/apps/webui/src/components/modules/commands-manager.tsx b/apps/webui/src/components/modules/commands-manager.tsx
index b9f4866..8d45084 100644
--- a/apps/webui/src/components/modules/commands-manager.tsx
+++ b/apps/webui/src/components/modules/commands-manager.tsx
@@ -3,8 +3,7 @@
import type {
CommandGlobalRules,
CommandOverrideDashboard,
- DiscordChannelOption,
- DiscordRoleOption
+ DiscordChannelOption
} from '@nexumi/shared';
import { RotateCcw } from 'lucide-react';
import { useSearchParams } from 'next/navigation';
@@ -14,6 +13,7 @@ import { useTranslations } from '@/components/locale-provider';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { DiscordResourceMultiSelect } from '@/components/ui/discord-resource-multi-select';
+import { DiscordRoleMultiSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
@@ -26,15 +26,13 @@ interface CommandsManagerProps {
initialCommands: CommandOverrideDashboard[];
initialGlobalRules: CommandGlobalRules;
channels: DiscordChannelOption[];
- roles: DiscordRoleOption[];
}
export function CommandsManager({
guildId,
initialCommands,
initialGlobalRules,
- channels,
- roles
+ channels
}: CommandsManagerProps) {
const t = useTranslations();
const searchParams = useSearchParams();
@@ -48,10 +46,6 @@ export function CommandsManager({
() => channels.map((channel) => ({ id: channel.id, name: channel.name, prefix: '#' })),
[channels]
);
- const roleOptions = useMemo(
- () => roles.map((role) => ({ id: role.id, name: role.name, prefix: '@' })),
- [roles]
- );
useEffect(() => {
const q = searchParams.get('q');
@@ -250,8 +244,8 @@ export function CommandsManager({
{t('modulePages.commands.allowedRoleIds')}
-
patchLocal(entry.commandName, { allowedRoleIds })}
placeholder={t('modulePages.commands.roleSearchPlaceholder')}
@@ -260,8 +254,8 @@ export function CommandsManager({
{t('modulePages.commands.deniedRoleIds')}
-
patchLocal(entry.commandName, { deniedRoleIds })}
placeholder={t('modulePages.commands.roleSearchPlaceholder')}
diff --git a/apps/webui/src/components/modules/feeds-manager.tsx b/apps/webui/src/components/modules/feeds-manager.tsx
index b4a55bd..a8ba0d0 100644
--- a/apps/webui/src/components/modules/feeds-manager.tsx
+++ b/apps/webui/src/components/modules/feeds-manager.tsx
@@ -8,6 +8,8 @@ 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';
+import { DiscordChannelSelect } from '@/components/ui/discord-channel-select';
+import { DiscordRoleSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
@@ -182,17 +184,18 @@ export function FeedsManager({ guildId, initialFeeds }: FeedsManagerProps) {
- setDraft((prev) => ({ ...prev, channelId: event.target.value }))}
- placeholder="123456789012345678"
+ onChange={(channelId) => setDraft((prev) => ({ ...prev, channelId }))}
/>
- setDraft((prev) => ({ ...prev, rolePingId: event.target.value }))}
+ onChange={(rolePingId) => setDraft((prev) => ({ ...prev, rolePingId }))}
placeholder={t('common.optional')}
/>
diff --git a/apps/webui/src/components/modules/giveaways-manager.tsx b/apps/webui/src/components/modules/giveaways-manager.tsx
index 788418b..2d285b5 100644
--- a/apps/webui/src/components/modules/giveaways-manager.tsx
+++ b/apps/webui/src/components/modules/giveaways-manager.tsx
@@ -9,6 +9,8 @@ import { useTranslations } from '@/components/locale-provider';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { DiscordChannelSelect } from '@/components/ui/discord-channel-select';
+import { DiscordRoleSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -127,10 +129,10 @@ export function GiveawaysManager({ guildId, initialGiveaways }: GiveawaysManager
- setDraft((prev) => ({ ...prev, channelId: event.target.value }))}
- placeholder="123456789012345678"
+ onChange={(channelId) => setDraft((prev) => ({ ...prev, channelId }))}
/>
@@ -156,9 +158,10 @@ export function GiveawaysManager({ guildId, initialGiveaways }: GiveawaysManager
- setDraft((prev) => ({ ...prev, requiredRoleId: event.target.value }))}
+ onChange={(requiredRoleId) => setDraft((prev) => ({ ...prev, requiredRoleId }))}
placeholder={t('common.optional')}
/>
diff --git a/apps/webui/src/components/modules/leveling-form.tsx b/apps/webui/src/components/modules/leveling-form.tsx
index e537e3c..3aab9a2 100644
--- a/apps/webui/src/components/modules/leveling-form.tsx
+++ b/apps/webui/src/components/modules/leveling-form.tsx
@@ -1,10 +1,11 @@
'use client';
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 { DiscordChannelMultiSelect, DiscordChannelSelect } from '@/components/ui/discord-channel-select';
+import { DiscordRoleMultiSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
@@ -14,24 +15,11 @@ import type { SettingsSaveResult } from '@/components/settings/settings-form';
import { SettingsForm } from '@/components/settings/settings-form';
interface LocalLevelingValue
- extends Omit
{
- noXpChannelIdsText: string;
- noXpRoleIdsText: string;
+ extends Omit {
roleMultipliersText: string;
channelMultipliersText: string;
}
-function toCsv(ids: string[]): string {
- return ids.join(', ');
-}
-
-function fromCsv(text: string): string[] {
- return text
- .split(',')
- .map((entry) => entry.trim())
- .filter((entry) => entry.length > 0);
-}
-
function toMultiplierText(map: Record): string {
return Object.entries(map)
.map(([id, multiplier]) => `${id}:${multiplier}`)
@@ -58,11 +46,9 @@ function fromMultiplierText(text: string): { value: Record; erro
}
function toLocal(config: LevelingConfigDashboard): LocalLevelingValue {
- const { noXpChannelIds, noXpRoleIds, roleMultipliers, channelMultipliers, ...rest } = config;
+ const { roleMultipliers, channelMultipliers, ...rest } = config;
return {
...rest,
- noXpChannelIdsText: toCsv(noXpChannelIds),
- noXpRoleIdsText: toCsv(noXpRoleIds),
roleMultipliersText: toMultiplierText(roleMultipliers),
channelMultipliersText: toMultiplierText(channelMultipliers)
};
@@ -78,12 +64,10 @@ async function saveLeveling(guildId: string, value: LocalLevelingValue): Promise
return { ok: false, error: channelMultipliers.error };
}
- const { noXpChannelIdsText, noXpRoleIdsText, roleMultipliersText: _roleMultipliersText, channelMultipliersText: _channelMultipliersText, ...rest } = value;
+ const { roleMultipliersText: _roleMultipliersText, channelMultipliersText: _channelMultipliersText, ...rest } = value;
const payload: LevelingConfigDashboard = {
...rest,
- noXpChannelIds: fromCsv(noXpChannelIdsText),
- noXpRoleIds: fromCsv(noXpRoleIdsText),
roleMultipliers: roleMultipliers.value,
channelMultipliers: channelMultipliers.value
};
@@ -111,8 +95,6 @@ interface LevelingFormProps {
export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
const t = useTranslations();
- const noXpChannelsId = useId();
- const noXpRolesId = useId();
return (
@@ -192,23 +174,21 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
-
-
-
-
@@ -271,10 +251,10 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
- setValue((prev) => ({ ...prev, levelUpChannelId: event.target.value.trim() }))}
- placeholder="123456789012345678"
+ onChange={(levelUpChannelId) => setValue((prev) => ({ ...prev, levelUpChannelId }))}
/>
diff --git a/apps/webui/src/components/modules/logging-form.tsx b/apps/webui/src/components/modules/logging-form.tsx
index aa93e16..9d66051 100644
--- a/apps/webui/src/components/modules/logging-form.tsx
+++ b/apps/webui/src/components/modules/logging-form.tsx
@@ -2,16 +2,16 @@
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';
+import { DiscordChannelMultiSelect, DiscordChannelSelect } from '@/components/ui/discord-channel-select';
+import { DiscordRoleMultiSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
-import { Textarea } from '@/components/ui/textarea';
import type { SettingsSaveResult } from '@/components/settings/settings-form';
import { SettingsForm } from '@/components/settings/settings-form';
@@ -51,29 +51,18 @@ interface LocalLogChannel extends LogChannelMapping {
interface LocalLoggingValue {
enabled: boolean;
ignoreBots: boolean;
- ignoredChannelIdsText: string;
- ignoredRoleIdsText: string;
+ ignoredChannelIds: string[];
+ ignoredRoleIds: string[];
retentionDays: number;
logChannels: LocalLogChannel[];
}
-function toCsv(ids: string[]): string {
- return ids.join(', ');
-}
-
-function fromCsv(text: string): string[] {
- return text
- .split(',')
- .map((entry) => entry.trim())
- .filter((entry) => entry.length > 0);
-}
-
function toLocal(config: LoggingConfigDashboard): LocalLoggingValue {
return {
enabled: config.enabled,
ignoreBots: config.ignoreBots,
- ignoredChannelIdsText: toCsv(config.ignoredChannelIds),
- ignoredRoleIdsText: toCsv(config.ignoredRoleIds),
+ ignoredChannelIds: config.ignoredChannelIds,
+ ignoredRoleIds: config.ignoredRoleIds,
retentionDays: config.retentionDays,
logChannels: config.logChannels.map((mapping, index) => ({ ...mapping, clientKey: `${mapping.eventType}-${index}` }))
};
@@ -83,8 +72,8 @@ function toRemote(value: LocalLoggingValue): LoggingConfigDashboard {
return {
enabled: value.enabled,
ignoreBots: value.ignoreBots,
- ignoredChannelIds: fromCsv(value.ignoredChannelIdsText),
- ignoredRoleIds: fromCsv(value.ignoredRoleIdsText),
+ ignoredChannelIds: value.ignoredChannelIds,
+ ignoredRoleIds: value.ignoredRoleIds,
retentionDays: value.retentionDays,
logChannels: value.logChannels.map(({ clientKey: _clientKey, ...rest }) => rest)
};
@@ -114,8 +103,6 @@ interface LoggingFormProps {
export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
const t = useTranslations();
- const ignoredChannelsId = useId();
- const ignoredRolesId = useId();
return (
@@ -150,26 +137,22 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
-
-
-
-
@@ -233,17 +216,17 @@ export function LoggingForm({ guildId, initialValue }: LoggingFormProps) {
-
+ onChange={(channelId) =>
setValue((prev) => ({
...prev,
logChannels: prev.logChannels.map((entry) =>
- entry.clientKey === mapping.clientKey ? { ...entry, channelId: event.target.value.trim() } : entry
+ entry.clientKey === mapping.clientKey ? { ...entry, channelId } : entry
)
}))
}
- placeholder="123456789012345678"
/>