Implement overdue giveaway recovery and enhance giveaway command handling
- Added functionality to recover overdue giveaways during bot startup, ensuring that giveaways that have passed their end time are processed correctly. - Introduced `cancelGiveawayJob` to safely remove scheduled jobs, preventing issues with locked jobs. - Updated giveaway command handling to trim IDs and validate existence within the guild context, improving error handling and user feedback. - Enhanced the role picker component to normalize search queries and handle errors more gracefully, improving user experience. - Implemented caching strategies for guild roles to avoid stale or empty responses, ensuring more reliable data retrieval.
This commit is contained in:
@@ -26,6 +26,10 @@ function toOptions(roles: DiscordRoleOption[]) {
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeSearch(value: string): string {
|
||||
return value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
interface RoleSelectBaseProps {
|
||||
guildId: string;
|
||||
disabled?: boolean;
|
||||
@@ -48,14 +52,33 @@ export function DiscordRoleSelect({
|
||||
allowClear = true
|
||||
}: DiscordRoleSelectProps) {
|
||||
const t = useTranslations();
|
||||
const { roles, loading } = useGuildRoles(guildId);
|
||||
const { roles, loading, error, reload } = useGuildRoles(guildId);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const options = useMemo(() => toOptions(roles), [roles]);
|
||||
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]);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
setOpen(next);
|
||||
if (!next) {
|
||||
setQuery('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -74,10 +97,22 @@ export function DiscordRoleSelect({
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder={placeholder ?? t('modulePages.commands.roleSearchPlaceholder')} />
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
placeholder={placeholder ?? t('modulePages.commands.roleSearchPlaceholder')}
|
||||
/>
|
||||
<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
|
||||
@@ -91,10 +126,10 @@ export function DiscordRoleSelect({
|
||||
<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);
|
||||
|
||||
Reference in New Issue
Block a user