diff --git a/admin-spa/src/routes/Appearance/Pages/components/CreatePageModal.tsx b/admin-spa/src/routes/Appearance/Pages/components/CreatePageModal.tsx index 107cf7f..71e490c 100644 --- a/admin-spa/src/routes/Appearance/Pages/components/CreatePageModal.tsx +++ b/admin-spa/src/routes/Appearance/Pages/components/CreatePageModal.tsx @@ -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 ( @@ -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} /> @@ -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} />

{__('URL will be: ')}{siteUrl}/{slug || 'page-slug'} @@ -161,14 +192,21 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod -