Refactor channel and role selection in various forms to use Discord components

- Replaced standard input fields with DiscordChannelSelect and DiscordRoleSelect components in the BirthdaysForm, FeedsManager, GiveawaysManager, LevelingForm, LoggingForm, SchedulerManager, SelfRolesManager, StatsForm, SuggestionsConfigForm, TagsManager, TempVoiceForm, TicketConfigForm, and StarboardForm for improved user experience and consistency.
- Updated state management to accommodate new component structures, enhancing data handling for channel and role selections across the application.
This commit is contained in:
TheOnlyMace
2026-07-22 21:23:57 +02:00
parent cf6d071047
commit ed2ea23e87
27 changed files with 749 additions and 322 deletions

View File

@@ -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<LevelingConfigDashboard, 'noXpChannelIds' | 'noXpRoleIds' | 'roleMultipliers' | 'channelMultipliers'> {
noXpChannelIdsText: string;
noXpRoleIdsText: string;
extends Omit<LevelingConfigDashboard, 'roleMultipliers' | 'channelMultipliers'> {
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, number>): string {
return Object.entries(map)
.map(([id, multiplier]) => `${id}:${multiplier}`)
@@ -58,11 +46,9 @@ function fromMultiplierText(text: string): { value: Record<string, number>; 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 (
<SettingsForm<LocalLevelingValue>
@@ -192,23 +174,21 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
<FieldAnchor id="field-leveling-no-xp-channels">
<div className="space-y-2">
<Label htmlFor={noXpChannelsId}>{t('modulePages.leveling.noXpChannels')}</Label>
<Textarea
id={noXpChannelsId}
value={value.noXpChannelIdsText}
onChange={(event) => setValue((prev) => ({ ...prev, noXpChannelIdsText: event.target.value }))}
placeholder={t('modulePages.logging.idsPlaceholder')}
<Label>{t('modulePages.leveling.noXpChannels')}</Label>
<DiscordChannelMultiSelect
guildId={guildId}
value={value.noXpChannelIds}
onChange={(noXpChannelIds) => setValue((prev) => ({ ...prev, noXpChannelIds }))}
/>
</div>
</FieldAnchor>
<FieldAnchor id="field-leveling-no-xp-roles">
<div className="space-y-2">
<Label htmlFor={noXpRolesId}>{t('modulePages.leveling.noXpRoles')}</Label>
<Textarea
id={noXpRolesId}
value={value.noXpRoleIdsText}
onChange={(event) => setValue((prev) => ({ ...prev, noXpRoleIdsText: event.target.value }))}
placeholder={t('modulePages.logging.idsPlaceholder')}
<Label>{t('modulePages.leveling.noXpRoles')}</Label>
<DiscordRoleMultiSelect
guildId={guildId}
value={value.noXpRoleIds}
onChange={(noXpRoleIds) => setValue((prev) => ({ ...prev, noXpRoleIds }))}
/>
</div>
</FieldAnchor>
@@ -271,10 +251,10 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
</div>
<div className="space-y-2">
<Label>{t('modulePages.leveling.levelUpChannelId')}</Label>
<Input
<DiscordChannelSelect
guildId={guildId}
value={value.levelUpChannelId}
onChange={(event) => setValue((prev) => ({ ...prev, levelUpChannelId: event.target.value.trim() }))}
placeholder="123456789012345678"
onChange={(levelUpChannelId) => setValue((prev) => ({ ...prev, levelUpChannelId }))}
/>
</div>
</div>