Refactor leveling form to use Discord multiplier editor and enhance localization
- Replaced text area inputs for role and channel multipliers with DiscordMultiplierEditor components for improved user experience. - Updated state management to handle multiplier entries as objects instead of text, enhancing data integrity. - Enhanced localization messages for multipliers, including error handling and hints for user guidance.
This commit is contained in:
@@ -5,6 +5,12 @@ 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 {
|
||||
DiscordMultiplierEditor,
|
||||
mapToMultiplierEntries,
|
||||
multiplierEntriesToMap,
|
||||
type MultiplierEntry
|
||||
} from '@/components/ui/discord-multiplier-editor';
|
||||
import { DiscordRoleMultiSelect } from '@/components/ui/discord-role-select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -16,55 +22,34 @@ import { SettingsForm } from '@/components/settings/settings-form';
|
||||
|
||||
interface LocalLevelingValue
|
||||
extends Omit<LevelingConfigDashboard, 'roleMultipliers' | 'channelMultipliers'> {
|
||||
roleMultipliersText: string;
|
||||
channelMultipliersText: string;
|
||||
}
|
||||
|
||||
function toMultiplierText(map: Record<string, number>): string {
|
||||
return Object.entries(map)
|
||||
.map(([id, multiplier]) => `${id}:${multiplier}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function fromMultiplierText(text: string): { value: Record<string, number>; error?: string } {
|
||||
const value: Record<string, number> = {};
|
||||
const lines = text
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
|
||||
for (const line of lines) {
|
||||
const [id, multiplierRaw] = line.split(':').map((part) => part.trim());
|
||||
const multiplier = Number(multiplierRaw);
|
||||
if (!id || Number.isNaN(multiplier) || multiplier <= 0) {
|
||||
return { value: {}, error: `Invalid multiplier line: "${line}" (expected id:number)` };
|
||||
}
|
||||
value[id] = multiplier;
|
||||
}
|
||||
|
||||
return { value };
|
||||
roleMultiplierEntries: MultiplierEntry[];
|
||||
channelMultiplierEntries: MultiplierEntry[];
|
||||
}
|
||||
|
||||
function toLocal(config: LevelingConfigDashboard): LocalLevelingValue {
|
||||
const { roleMultipliers, channelMultipliers, ...rest } = config;
|
||||
return {
|
||||
...rest,
|
||||
roleMultipliersText: toMultiplierText(roleMultipliers),
|
||||
channelMultipliersText: toMultiplierText(channelMultipliers)
|
||||
roleMultiplierEntries: mapToMultiplierEntries(roleMultipliers),
|
||||
channelMultiplierEntries: mapToMultiplierEntries(channelMultipliers)
|
||||
};
|
||||
}
|
||||
|
||||
async function saveLeveling(guildId: string, value: LocalLevelingValue): Promise<SettingsSaveResult> {
|
||||
const roleMultipliers = fromMultiplierText(value.roleMultipliersText);
|
||||
async function saveLeveling(guildId: string, value: LocalLevelingValue, t: (key: string) => string): Promise<SettingsSaveResult> {
|
||||
const roleMultipliers = multiplierEntriesToMap(value.roleMultiplierEntries);
|
||||
if (roleMultipliers.error) {
|
||||
return { ok: false, error: roleMultipliers.error };
|
||||
return { ok: false, error: t(`modulePages.leveling.errors.${roleMultipliers.error}`) };
|
||||
}
|
||||
const channelMultipliers = fromMultiplierText(value.channelMultipliersText);
|
||||
const channelMultipliers = multiplierEntriesToMap(value.channelMultiplierEntries);
|
||||
if (channelMultipliers.error) {
|
||||
return { ok: false, error: channelMultipliers.error };
|
||||
return { ok: false, error: t(`modulePages.leveling.errors.${channelMultipliers.error}`) };
|
||||
}
|
||||
|
||||
const { roleMultipliersText: _roleMultipliersText, channelMultipliersText: _channelMultipliersText, ...rest } = value;
|
||||
const {
|
||||
roleMultiplierEntries: _roleMultiplierEntries,
|
||||
channelMultiplierEntries: _channelMultiplierEntries,
|
||||
...rest
|
||||
} = value;
|
||||
|
||||
const payload: LevelingConfigDashboard = {
|
||||
...rest,
|
||||
@@ -99,7 +84,7 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
return (
|
||||
<SettingsForm<LocalLevelingValue>
|
||||
initialValue={toLocal(initialValue)}
|
||||
onSave={(value) => saveLeveling(guildId, value)}
|
||||
onSave={(value) => saveLeveling(guildId, value, t)}
|
||||
>
|
||||
{({ value, setValue }) => (
|
||||
<div className="space-y-6">
|
||||
@@ -197,12 +182,11 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
<FieldAnchor id="field-leveling-role-multipliers">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.roleMultipliers')}</Label>
|
||||
<Textarea
|
||||
className="font-mono"
|
||||
rows={4}
|
||||
value={value.roleMultipliersText}
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, roleMultipliersText: event.target.value }))}
|
||||
placeholder="123456789012345678:1.5"
|
||||
<DiscordMultiplierEditor
|
||||
guildId={guildId}
|
||||
kind="role"
|
||||
value={value.roleMultiplierEntries}
|
||||
onChange={(roleMultiplierEntries) => setValue((prev) => ({ ...prev, roleMultiplierEntries }))}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.leveling.multiplierHint')}</p>
|
||||
</div>
|
||||
@@ -210,12 +194,13 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
<FieldAnchor id="field-leveling-channel-multipliers">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.leveling.channelMultipliers')}</Label>
|
||||
<Textarea
|
||||
className="font-mono"
|
||||
rows={4}
|
||||
value={value.channelMultipliersText}
|
||||
onChange={(event) => setValue((prev) => ({ ...prev, channelMultipliersText: event.target.value }))}
|
||||
placeholder="123456789012345678:2"
|
||||
<DiscordMultiplierEditor
|
||||
guildId={guildId}
|
||||
kind="channel"
|
||||
value={value.channelMultiplierEntries}
|
||||
onChange={(channelMultiplierEntries) =>
|
||||
setValue((prev) => ({ ...prev, channelMultiplierEntries }))
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('modulePages.leveling.multiplierHint')}</p>
|
||||
</div>
|
||||
@@ -243,9 +228,9 @@ export function LevelingForm({ guildId, initialValue }: LevelingFormProps) {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="CHANNEL">{t('modulePages.leveling.mode.CHANNEL')}</SelectItem>
|
||||
<SelectItem value="DM">{t('modulePages.leveling.mode.DM')}</SelectItem>
|
||||
<SelectItem value="OFF">{t('modulePages.leveling.mode.OFF')}</SelectItem>
|
||||
<SelectItem value="CHANNEL">{t('modulePages.leveling.modes.CHANNEL')}</SelectItem>
|
||||
<SelectItem value="DM">{t('modulePages.leveling.modes.DM')}</SelectItem>
|
||||
<SelectItem value="OFF">{t('modulePages.leveling.modes.OFF')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -70,8 +70,8 @@ export function TicketConfigForm({ guildId, initialValue }: TicketConfigFormProp
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="CHANNEL">{t('modulePages.tickets.mode.CHANNEL')}</SelectItem>
|
||||
<SelectItem value="THREAD">{t('modulePages.tickets.mode.THREAD')}</SelectItem>
|
||||
<SelectItem value="CHANNEL">{t('modulePages.tickets.modes.CHANNEL')}</SelectItem>
|
||||
<SelectItem value="THREAD">{t('modulePages.tickets.modes.THREAD')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user