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';
import React, { useState, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { toast } from 'sonner';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
@ -82,6 +82,7 @@ export default function AgentsPage() {
const [publishDialog, setPublishDialog] = useState<PublishDialogData | null>(null);
const [publishTags, setPublishTags] = useState<string[]>([]);
const [publishingAgentId, setPublishingAgentId] = useState<string | null>(null);
const [isCreatingNewAgent, setIsCreatingNewAgent] = useState(false);
const activeTab = useMemo(() => {
return searchParams.get('tab') || 'my-agents';
@ -250,9 +251,20 @@ export default function AgentsPage() {
setEditDialogOpen(true);
};
const handleCreateNewAgent = () => {
createNewAgentMutation.mutate();
};
const handleCreateNewAgent = useCallback(() => {
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) => {
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 { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
@ -48,6 +48,7 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedCategory, setSelectedCategory] = useState<string>('All');
const [formData, setFormData] = useState<AgentCreateRequest>(initialFormData);
const [isSubmitting, setIsSubmitting] = useState(false);
const createAgentMutation = useCreateAgent();
useEffect(() => {
@ -109,19 +110,24 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
return tools;
};
const handleSubmit = async () => {
if (!formData.name.trim()) {
const handleSubmit = useCallback(async () => {
if (!formData.name.trim() || isSubmitting || createAgentMutation.isPending) {
return;
}
setIsSubmitting(true);
try {
await createAgentMutation.mutateAsync(formData);
onOpenChange(false);
onAgentCreated?.();
} catch (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 = () => {
onOpenChange(false);
@ -306,16 +312,16 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea
<Button
variant="outline"
onClick={handleCancel}
disabled={createAgentMutation.isPending}
disabled={createAgentMutation.isPending || isSubmitting}
className="px-6"
>
Cancel
</Button>
<Button
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" />
Creating Agent

View File

@ -1,6 +1,6 @@
'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 Image from 'next/image';
import { Button } from '@/components/ui/button';
@ -61,6 +61,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [isCreatingAgent, setIsCreatingAgent] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
@ -169,10 +170,21 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
router.push('/agents');
};
const handleCreateAgent = () => {
const handleCreateAgent = useCallback(() => {
if (isCreatingAgent || createNewAgentMutation.isPending) {
return; // Prevent multiple clicks
}
setIsCreatingAgent(true);
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 isSelected = agent.id === selectedAgentId;
@ -339,10 +351,11 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
variant="ghost"
size="sm"
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" />
Create Agent
{isCreatingAgent || createNewAgentMutation.isPending ? 'Creating...' : 'Create Agent'}
</Button>
</div>
</div>