fix: Prevent double submission in Create Page dialog
- Add ref-based double submission protection (isSubmittingRef) - Extract handleSubmit function with isPending checks - Add loading spinner during submission - Disable inputs during submission - Suppress error toast for duplicate prevention errors
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { api } from '@/lib/api';
|
||||
import { __ } from '@/lib/i18n';
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { toast } from 'sonner';
|
||||
import { FileText, Layout } from 'lucide-react';
|
||||
import { FileText, Layout, Loader2 } from 'lucide-react';
|
||||
|
||||
interface PageItem {
|
||||
id?: number;
|
||||
@@ -36,18 +36,30 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
const [title, setTitle] = useState('');
|
||||
const [slug, setSlug] = useState('');
|
||||
|
||||
// Prevent double submission
|
||||
const isSubmittingRef = useRef(false);
|
||||
|
||||
// Get site URL from WordPress config
|
||||
const siteUrl = window.WNW_CONFIG?.siteUrl?.replace(/\/$/, '') || window.location.origin;
|
||||
|
||||
// Create page mutation
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (pageType === 'page') {
|
||||
const response = await api.post('/pages', { title, slug });
|
||||
return response.data;
|
||||
mutationFn: async (data: { title: string; slug: string }) => {
|
||||
// Guard against double submission
|
||||
if (isSubmittingRef.current) {
|
||||
throw new Error('Request already in progress');
|
||||
}
|
||||
isSubmittingRef.current = true;
|
||||
|
||||
try {
|
||||
const response = await api.post('/pages', { title: data.title, slug: data.slug });
|
||||
return response.data;
|
||||
} finally {
|
||||
// Reset after a delay to prevent race conditions
|
||||
setTimeout(() => {
|
||||
isSubmittingRef.current = false;
|
||||
}, 500);
|
||||
}
|
||||
// For templates, we don't create them - they're auto-created for each CPT
|
||||
return null;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data?.page) {
|
||||
@@ -64,6 +76,10 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
// Don't show error for duplicate prevention
|
||||
if (error?.message === 'Request already in progress') {
|
||||
return;
|
||||
}
|
||||
// Extract error message from the response
|
||||
const message = error?.response?.data?.message ||
|
||||
error?.message ||
|
||||
@@ -82,15 +98,28 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
}
|
||||
};
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = () => {
|
||||
if (createMutation.isPending || isSubmittingRef.current) {
|
||||
return;
|
||||
}
|
||||
if (pageType === 'page' && title && slug) {
|
||||
createMutation.mutate({ title, slug });
|
||||
}
|
||||
};
|
||||
|
||||
// Reset form when modal closes
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setTitle('');
|
||||
setSlug('');
|
||||
setPageType('page');
|
||||
isSubmittingRef.current = false;
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const isDisabled = pageType === 'page' && (!title || !slug) || createMutation.isPending || isSubmittingRef.current;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
@@ -141,6 +170,7 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
value={title}
|
||||
onChange={(e) => handleTitleChange(e.target.value)}
|
||||
placeholder={__('e.g., About Us')}
|
||||
disabled={createMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -151,6 +181,7 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
value={slug}
|
||||
onChange={(e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''))}
|
||||
placeholder={__('e.g., about-us')}
|
||||
disabled={createMutation.isPending}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{__('URL will be: ')}<span className="font-mono text-primary">{siteUrl}/{slug || 'page-slug'}</span>
|
||||
@@ -161,14 +192,21 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={createMutation.isPending}>
|
||||
{__('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => createMutation.mutate()}
|
||||
disabled={pageType === 'page' && (!title || !slug) || createMutation.isPending}
|
||||
onClick={handleSubmit}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{createMutation.isPending ? __('Creating...') : __('Create Page')}
|
||||
{createMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
{__('Creating...')}
|
||||
</>
|
||||
) : (
|
||||
__('Create Page')
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user