Implement command cooldown management and enhance command execution flow

- Introduced `applyCommandCooldown` function to manage per-command cooldowns after execution, improving command rate limiting.
- Updated command execution flow to include cooldown application, ensuring users are informed of cooldown status.
- Enhanced error handling in interaction replies, allowing for more graceful error management and user feedback.
- Improved the handling of interaction responses across various commands, ensuring consistent user experience.
- Added permission checks for new commands, ensuring only authorized users can execute specific actions.
This commit is contained in:
TheOnlyMace
2026-07-25 15:37:56 +02:00
parent 57cdf847f5
commit 8513848440
42 changed files with 856 additions and 160 deletions

View File

@@ -38,6 +38,10 @@ function toOptions(channels: DiscordChannelOption[]) {
}));
}
function normalizeSearch(value: string): string {
return value.trim().toLocaleLowerCase();
}
interface ChannelSelectBaseProps {
guildId: string;
kinds?: ChannelKind[];
@@ -62,14 +66,39 @@ export function DiscordChannelSelect({
allowClear = true
}: DiscordChannelSelectProps) {
const t = useTranslations();
const { channels, loading } = useGuildChannels(guildId);
const { channels, loading, error, reload } = useGuildChannels(guildId);
const [open, setOpen] = useState(false);
const [query, setQuery] = useState('');
const options = useMemo(() => toOptions(filterChannels(channels, kinds)), [channels, kinds]);
const selected = options.find((option) => option.id === value);
const filtered = useMemo(() => {
const needle = normalizeSearch(query);
if (!needle) {
return options;
}
return options.filter((option) => {
const haystack = normalizeSearch(`${option.prefix}${option.name} ${option.id}`);
return haystack.includes(needle);
});
}, [options, query]);
const triggerLabel = selected
? `${selected.prefix}${selected.name}`
: value
? `#${value}`
: (placeholder ?? t('modulePages.commands.channelSearchPlaceholder'));
return (
<Popover open={open} onOpenChange={setOpen}>
<Popover
open={open}
onOpenChange={(next) => {
setOpen(next);
if (!next) {
setQuery('');
}
}}
>
<PopoverTrigger asChild>
<Button
type="button"
@@ -79,19 +108,29 @@ export function DiscordChannelSelect({
disabled={disabled || loading}
className="h-9 w-full justify-between px-3 font-normal"
>
<span className={cn('truncate', !selected && 'text-muted-foreground')}>
{selected
? `${selected.prefix}${selected.name}`
: (placeholder ?? t('modulePages.commands.channelSearchPlaceholder'))}
<span className={cn('truncate', !selected && !value && 'text-muted-foreground')}>
{triggerLabel}
</span>
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput placeholder={placeholder ?? t('modulePages.commands.channelSearchPlaceholder')} />
<Command shouldFilter={false}>
<CommandInput
value={query}
onValueChange={setQuery}
placeholder={placeholder ?? t('modulePages.commands.channelSearchPlaceholder')}
/>
<CommandList>
<CommandEmpty>{t('common.noResults')}</CommandEmpty>
<CommandEmpty>
{error ? (
<button type="button" className="underline" onClick={() => reload()}>
{t('common.saveError')}
</button>
) : (
t('common.noResults')
)}
</CommandEmpty>
<CommandGroup>
{allowClear && value ? (
<CommandItem
@@ -105,10 +144,10 @@ export function DiscordChannelSelect({
<span className="text-muted-foreground">{t('common.clearSelection')}</span>
</CommandItem>
) : null}
{options.map((option) => (
{filtered.map((option) => (
<CommandItem
key={option.id}
value={`${option.prefix}${option.name} ${option.id}`}
value={`${option.name} ${option.id}`}
onSelect={() => {
onChange(option.id);
setOpen(false);