unsaveChangeWarning hooks
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* هوک برای هشدار به کاربر هنگام خروج از صفحه با تغییرات ذخیره نشده
|
||||||
|
* این هوک از رفرش، بستن تب، و navigation داخلی (دکمه برگشت، لینکها) جلوگیری میکند
|
||||||
|
* @param isDirty - آیا فرم تغییر کرده است؟
|
||||||
|
* @param message - پیام هشدار (اختیاری)
|
||||||
|
*/
|
||||||
|
export const useUnsavedChangesWarning = (
|
||||||
|
isDirty: boolean,
|
||||||
|
message: string = "تغییرات شما ذخیره نشدهاند. آیا مطمئن هستید که میخواهید این صفحه را ترک کنید؟"
|
||||||
|
) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const currentPath = useRef(location.pathname);
|
||||||
|
const isNavigatingRef = useRef(false);
|
||||||
|
|
||||||
|
// برای جلوگیری از بستن/رفرش مرورگر
|
||||||
|
useEffect(() => {
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
if (isDirty) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = "";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDirty) {
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
};
|
||||||
|
}, [isDirty]);
|
||||||
|
|
||||||
|
// برای جلوگیری از navigation داخلی (دکمه برگشت مرورگر)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDirty) {
|
||||||
|
currentPath.current = location.pathname;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePopState = () => {
|
||||||
|
if (isDirty && !isNavigatingRef.current) {
|
||||||
|
const confirmLeave = window.confirm(message);
|
||||||
|
|
||||||
|
if (!confirmLeave) {
|
||||||
|
// اگر کاربر لغو کرد، به صفحه قبلی برگرد
|
||||||
|
window.history.pushState(null, "", currentPath.current);
|
||||||
|
} else {
|
||||||
|
// اگر کاربر تایید کرد، اجازه navigation بده
|
||||||
|
currentPath.current = location.pathname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// یک state جدید به history اضافه کن تا بتونیم popstate رو catch کنیم
|
||||||
|
window.history.pushState(null, "", location.pathname);
|
||||||
|
|
||||||
|
window.addEventListener("popstate", handlePopState);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handlePopState);
|
||||||
|
};
|
||||||
|
}, [isDirty, message, location.pathname]);
|
||||||
|
|
||||||
|
// Update current path when location changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDirty) {
|
||||||
|
currentPath.current = location.pathname;
|
||||||
|
}
|
||||||
|
}, [location.pathname, isDirty]);
|
||||||
|
};
|
||||||
@@ -16,11 +16,13 @@ import { toast } from 'react-toastify'
|
|||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useUnsavedChangesWarning } from '../../hooks/useUnsavedChangesWarning'
|
||||||
const CreateBlog: FC = () => {
|
const CreateBlog: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [file, setFile] = useState<File>()
|
const [file, setFile] = useState<File>()
|
||||||
const [audioFile, setAudioFile] = useState<File>()
|
const [audioFile, setAudioFile] = useState<File>()
|
||||||
|
const [isSaved, setIsSaved] = useState(false)
|
||||||
const singleSlug = useSingleUpload()
|
const singleSlug = useSingleUpload()
|
||||||
const audioUpload = useSingleUpload()
|
const audioUpload = useSingleUpload()
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ const CreateBlog: FC = () => {
|
|||||||
|
|
||||||
createBlog.mutate(values, {
|
createBlog.mutate(values, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
setIsSaved(true)
|
||||||
toast.success(t('success'))
|
toast.success(t('success'))
|
||||||
navigate(Pages.blog.list)
|
navigate(Pages.blog.list)
|
||||||
},
|
},
|
||||||
@@ -95,6 +98,8 @@ const CreateBlog: FC = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// استفاده از hook برای هشدار تغییرات ذخیره نشده
|
||||||
|
useUnsavedChangesWarning(formik.dirty && !isSaved)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ import { toast } from 'react-toastify'
|
|||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
|
import { useUnsavedChangesWarning } from '../../hooks/useUnsavedChangesWarning'
|
||||||
const UpdateBlog: FC = () => {
|
const UpdateBlog: FC = () => {
|
||||||
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [file, setFile] = useState<File>()
|
const [file, setFile] = useState<File>()
|
||||||
const [audioFile, setAudioFile] = useState<File>()
|
const [audioFile, setAudioFile] = useState<File>()
|
||||||
|
const [isSaved, setIsSaved] = useState(false)
|
||||||
const singleSlug = useSingleUpload()
|
const singleSlug = useSingleUpload()
|
||||||
const audioUpload = useSingleUpload()
|
const audioUpload = useSingleUpload()
|
||||||
const getBlogDetail = useGetBlogDetail(id || '')
|
const getBlogDetail = useGetBlogDetail(id || '')
|
||||||
@@ -84,6 +86,7 @@ const UpdateBlog: FC = () => {
|
|||||||
|
|
||||||
updateBlog.mutate({ id: id || '', params: values }, {
|
updateBlog.mutate({ id: id || '', params: values }, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
setIsSaved(true)
|
||||||
toast.success(t('success'))
|
toast.success(t('success'))
|
||||||
navigate(Pages.blog.list)
|
navigate(Pages.blog.list)
|
||||||
},
|
},
|
||||||
@@ -102,6 +105,9 @@ const UpdateBlog: FC = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getBlogDetail.data])
|
}, [getBlogDetail.data])
|
||||||
|
|
||||||
|
// استفاده از hook برای هشدار تغییرات ذخیره نشده
|
||||||
|
useUnsavedChangesWarning(formik.dirty && !isSaved)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
|
|||||||
Reference in New Issue
Block a user