From e378e1a44d593a12ba992546b14003dc5111cda7 Mon Sep 17 00:00:00 2001 From: arpita-1111 Date: Sun, 21 Jun 2026 23:34:58 +0530 Subject: [PATCH 1/2] feat: add unsaved changes warning when leaving CreatePage with filled form --- FRONTEND/src/pages/CreatePage.jsx | 68 ++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/FRONTEND/src/pages/CreatePage.jsx b/FRONTEND/src/pages/CreatePage.jsx index a001aef..3f378b8 100644 --- a/FRONTEND/src/pages/CreatePage.jsx +++ b/FRONTEND/src/pages/CreatePage.jsx @@ -6,9 +6,11 @@ import { } from '@chakra-ui/react'; import React, { useEffect, useRef, useState } from 'react'; import { FaChevronDown, FaChevronUp, FaInfoCircle } from 'react-icons/fa'; +import { useNavigate } from 'react-router-dom'; const CreatePage = () => { const { t } = useTranslation(); + const navigate = useNavigate(); const [newProduct, setNewProduct] = useState({ name: "", price: "", @@ -25,11 +27,39 @@ const CreatePage = () => { const [preview, setPreview] = useState(null); const [extraImageInput, setExtraImageInput] = useState(""); const [showExtraDetails, setShowExtraDetails] = useState(false); + const [isDirty, setIsDirty] = useState(false); const fileInputRef = useRef(null); const toast = useToast(); const { createProduct, isSubmitting } = useProductStore(); + useEffect(() => { + const handleBeforeUnload = (e) => { + if (isDirty) { + e.preventDefault(); + e.returnValue = ''; + } + }; + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }, [isDirty]); + + useEffect(() => { + const handleClick = (e) => { + const link = e.target.closest('a'); + if (link && isDirty) { + e.preventDefault(); + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?'); + if (confirmed) { + setIsDirty(false); + navigate(link.getAttribute('href')); + } + } + }; + document.addEventListener('click', handleClick, true); + return () => document.removeEventListener('click', handleClick, true); + }, [isDirty, navigate]); + useEffect(() => { const url = preview; return () => { @@ -42,6 +72,7 @@ const CreatePage = () => { if (!file) return; setNewProduct({ ...newProduct, imageFile: file, image: "" }); setPreview(URL.createObjectURL(file)); + setIsDirty(true); }; const handleAddProduct = async () => { @@ -57,10 +88,16 @@ const CreatePage = () => { setPreview(null); setExtraImageInput(""); setShowExtraDetails(false); + setIsDirty(false); if (fileInputRef.current) fileInputRef.current.value = ""; } }; + const handleChange = (field, value) => { + setNewProduct((prev) => ({ ...prev, [field]: value })); + setIsDirty(true); + }; + const borderColor = useColorModeValue("gray.200", "gray.600"); const toggleBg = useColorModeValue("blue.50", "blue.900"); const infoColor = useColorModeValue("gray.700", "gray.300"); @@ -92,7 +129,7 @@ const CreatePage = () => { name="name" aria-label="Product Name" value={newProduct.name} - onChange={(e) => setNewProduct({ ...newProduct, name: e.target.value })} + onChange={(e) => handleChange("name", e.target.value)} size="lg" /> { type="number" aria-label="Price" value={newProduct.price} - onChange={(e) => setNewProduct({ ...newProduct, price: e.target.value })} + onChange={(e) => handleChange("price", e.target.value)} size="lg" /> @@ -125,7 +162,8 @@ const CreatePage = () => { aria-label="Image URL" value={newProduct.image} onChange={(e) => { - setNewProduct({ ...newProduct, image: e.target.value, imageFile: null }); + handleChange("image", e.target.value); + setNewProduct((prev) => ({ ...prev, imageFile: null })); setPreview(e.target.value || null); if (fileInputRef.current) fileInputRef.current.value = ""; }} @@ -160,7 +198,7 @@ const CreatePage = () => { isDisabled={!extraImageInput.trim() || newProduct.images.length >= 4} onClick={() => { if (extraImageInput.trim()) { - setNewProduct({ ...newProduct, images: [...newProduct.images, extraImageInput.trim()] }); + handleChange("images", [...newProduct.images, extraImageInput.trim()]); setExtraImageInput(""); } }} @@ -172,7 +210,7 @@ const CreatePage = () => { {url} ))} @@ -211,7 +249,7 @@ const CreatePage = () => { name="description" aria-label="Product Description" value={newProduct.description} - onChange={(e) => setNewProduct({ ...newProduct, description: e.target.value })} + onChange={(e) => handleChange("description", e.target.value)} rows={4} resize="vertical" /> @@ -221,7 +259,7 @@ const CreatePage = () => { name="category" aria-label="Select Category" value={newProduct.category} - onChange={(e) => setNewProduct({ ...newProduct, category: e.target.value })} + onChange={(e) => handleChange("category", e.target.value)} > @@ -239,7 +277,7 @@ const CreatePage = () => { name="brand" aria-label="Brand" value={newProduct.brand} - onChange={(e) => setNewProduct({ ...newProduct, brand: e.target.value })} + onChange={(e) => handleChange("brand", e.target.value)} /> { min="0" aria-label="Stock Quantity" value={newProduct.stock} - onChange={(e) => setNewProduct({ ...newProduct, stock: e.target.value })} + onChange={(e) => handleChange("stock", e.target.value)} /> { step="0.01" aria-label="Original Price" value={newProduct.originalPrice} - onChange={(e) => setNewProduct({ ...newProduct, originalPrice: e.target.value })} + onChange={(e) => handleChange("originalPrice", e.target.value)} /> { max="100" aria-label="Discount Percentage" value={newProduct.discount} - onChange={(e) => setNewProduct({ ...newProduct, discount: e.target.value })} + onChange={(e) => handleChange("discount", e.target.value)} /> - - @@ -243,7 +235,6 @@ const CreatePage = () => { Adding extra details helps customers make informed decisions and improves product visibility. -