Prevent multiple agent creation and improve mutation handling

Co-authored-by: markokraemer.mail <markokraemer.mail@gmail.com>
This commit is contained in:
Cursor Agent 2025-07-17 10:27:31 +00:00
parent 4657c1495f
commit 6b2602157e
3 changed files with 48 additions and 17 deletions

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { usePathname, useRouter, useSearchParams } from 'next/navigation';
@ -82,6 +82,7 @@ export default function AgentsPage() {
const [publishDialog, setPublishDialog] = useState<PublishDialogData | null>(null); const [publishDialog, setPublishDialog] = useState<PublishDialogData | null>(null);
const [publishTags, setPublishTags] = useState<string[]>([]); const [publishTags, setPublishTags] = useState<string[]>([]);
const [publishingAgentId, setPublishingAgentId] = useState<string | null>(null); const [publishingAgentId, setPublishingAgentId] = useState<string | null>(null);
const [isCreatingNewAgent, setIsCreatingNewAgent] = useState(false);
const activeTab = useMemo(() => { const activeTab = useMemo(() => {
return searchParams.get('tab') || 'my-agents'; return searchParams.get('tab') || 'my-agents';
@ -250,9 +251,20 @@ export default function AgentsPage() {
setEditDialogOpen(true); setEditDialogOpen(true);
}; };
const handleCreateNewAgent = () => { const handleCreateNewAgent = useCallback(() => {
createNewAgentMutation.mutate(); if (isCreatingNewAgent || createNewAgentMutation.isPending) {
}; return; // Prevent multiple clicks
}
setIsCreatingNewAgent(true);
createNewAgentMutation.mutate(undefined, {
onSettled: () => {
// Reset the debounce state after mutation completes (success or error)
setTimeout(() => setIsCreatingNewAgent(false), 1000);
}
});
}, [isCreatingNewAgent, createNewAgentMutation]);
const handleInstallClick = (item: MarketplaceTemplate, e?: React.MouseEvent) => { const handleInstallClick = (item: MarketplaceTemplate, e?: React.MouseEvent) => {
if (e) { if (e) {

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@ -48,6 +48,7 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
const [searchQuery, setSearchQuery] = useState<string>(''); const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedCategory, setSelectedCategory] = useState<string>('All'); const [selectedCategory, setSelectedCategory] = useState<string>('All');
const [formData, setFormData] = useState<AgentCreateRequest>(initialFormData); const [formData, setFormData] = useState<AgentCreateRequest>(initialFormData);
const [isSubmitting, setIsSubmitting] = useState(false);
const createAgentMutation = useCreateAgent(); const createAgentMutation = useCreateAgent();
useEffect(() => { useEffect(() => {
@ -109,19 +110,24 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
return tools; return tools;
}; };
const handleSubmit = async () => { const handleSubmit = useCallback(async () => {
if (!formData.name.trim()) { if (!formData.name.trim() || isSubmitting || createAgentMutation.isPending) {
return; return;
} }
setIsSubmitting(true);
try { try {
await createAgentMutation.mutateAsync(formData); await createAgentMutation.mutateAsync(formData);
onOpenChange(false); onOpenChange(false);
onAgentCreated?.(); onAgentCreated?.();
} catch (error) { } catch (error) {
console.error('Error creating agent:', error); console.error('Error creating agent:', error);
} finally {
// Reset the debounce state after a delay
setTimeout(() => setIsSubmitting(false), 1000);
} }
}; }, [formData, isSubmitting, createAgentMutation, onOpenChange, onAgentCreated]);
const handleCancel = () => { const handleCancel = () => {
onOpenChange(false); onOpenChange(false);
@ -306,16 +312,16 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
<Button <Button
variant="outline" variant="outline"
onClick={handleCancel} onClick={handleCancel}
disabled={createAgentMutation.isPending} disabled={createAgentMutation.isPending || isSubmitting}
className="px-6" className="px-6"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
disabled={createAgentMutation.isPending || !formData.name.trim()} disabled={createAgentMutation.isPending || isSubmitting || !formData.name.trim()}
> >
{createAgentMutation.isPending ? ( {createAgentMutation.isPending || isSubmitting ? (
<> <>
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
Creating Agent Creating Agent

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect, useCallback } from 'react';
import { Settings, ChevronRight, Bot, Presentation, FileSpreadsheet, Search, Plus, User, Check, ChevronDown } from 'lucide-react'; import { Settings, ChevronRight, Bot, Presentation, FileSpreadsheet, Search, Plus, User, Check, ChevronDown } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -61,6 +61,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1); const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [isCreatingAgent, setIsCreatingAgent] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const router = useRouter(); const router = useRouter();
@ -169,10 +170,21 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
router.push('/agents'); router.push('/agents');
}; };
const handleCreateAgent = () => { const handleCreateAgent = useCallback(() => {
if (isCreatingAgent || createNewAgentMutation.isPending) {
return; // Prevent multiple clicks
}
setIsCreatingAgent(true);
setIsOpen(false); setIsOpen(false);
createNewAgentMutation.mutate();
}; createNewAgentMutation.mutate(undefined, {
onSettled: () => {
// Reset the debounce state after mutation completes (success or error)
setTimeout(() => setIsCreatingAgent(false), 1000);
}
});
}, [isCreatingAgent, createNewAgentMutation]);
const renderAgentItem = (agent: any, index: number) => { const renderAgentItem = (agent: any, index: number) => {
const isSelected = agent.id === selectedAgentId; const isSelected = agent.id === selectedAgentId;
@ -339,10 +351,11 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={handleCreateAgent} onClick={handleCreateAgent}
className="text-xs flex items-center gap-2 rounded-xl hover:bg-accent/40 transition-all duration-200 text-muted-foreground hover:text-foreground px-4 py-2" disabled={isCreatingAgent || createNewAgentMutation.isPending}
className="text-xs flex items-center gap-2 rounded-xl hover:bg-accent/40 transition-all duration-200 text-muted-foreground hover:text-foreground px-4 py-2 disabled:opacity-50"
> >
<Plus className="h-3.5 w-3.5" /> <Plus className="h-3.5 w-3.5" />
Create Agent {isCreatingAgent || createNewAgentMutation.isPending ? 'Creating...' : 'Create Agent'}
</Button> </Button>
</div> </div>
</div> </div>