From c65250227784bfacd604d7c22a51311716da1b63 Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Sat, 22 Jun 2024 22:54:03 +0900 Subject: [PATCH] feat: show confirm dialog when editing case or steps --- frontend/messages/en.json | 3 +- frontend/messages/ja.json | 3 +- .../[folderId]/cases/[caseId]/CaseEditor.tsx | 6 ++++ .../[folderId]/cases/[caseId]/page.tsx | 1 + frontend/types/case.ts | 1 + frontend/utils/formGuard.ts | 30 +++++++++++++++++++ 6 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 frontend/utils/formGuard.ts diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 9635709..09d3323 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -216,7 +216,8 @@ "delete_file": "Delete file", "click_to_upload": "Click to upload", "or_drag_and_drop": "or drag and drop", - "max_file_size": "Max. file size" + "max_file_size": "Max. file size", + "are_you_sure_leave": "Are you sure you want to leave the page?" }, "Runs": { "run_list": "Test Run List", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index ecf404e..dd711a9 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -215,7 +215,8 @@ "delete_file": "ファイルを削除", "click_to_upload": "クリックしてアップロード", "or_drag_and_drop": "またはドラッグアンドドロップ", - "max_file_size": "最大ファイルサイズ" + "max_file_size": "最大ファイルサイズ", + "are_you_sure_leave": "ページを離れてもよろしいですか?" }, "Runs": { "run_list": "テストラン一覧", diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx index 0122131..dd540b2 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx @@ -11,6 +11,7 @@ import { fetchCase, updateCase } from '@/utils/caseControl'; import { updateSteps } from './stepControl'; import { fetchCreateAttachments, fetchDownloadAttachment, fetchDeleteAttachment } from './attachmentControl'; import { TokenContext } from '@/utils/TokenProvider'; +import { useFormGuard } from '@/utils/formGuard'; const defaultTestCase = { id: 0, @@ -44,9 +45,12 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca const [isTitleInvalid, setIsTitleInvalid] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [plusCount, setPlusCount] = useState(0); + const [isDirty, setIsDirty] = useState(false); const router = useRouter(); + useFormGuard(isDirty, messages.areYouSureLeave); const onPlusClick = async (newStepNo: number) => { + setIsDirty(true); const newStep: StepType = { id: plusCount, step: '', @@ -86,6 +90,8 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca }; const onDeleteClick = async (stepId: number) => { + setIsDirty(true); + // find deletedStep's stepNo if (testCase.Steps) { const deletedStep = testCase.Steps.find((step) => step.id === stepId); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx index 094eba4..e859a4f 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx @@ -58,6 +58,7 @@ export default function Page({ clickToUpload: t('click_to_upload'), orDragAndDrop: t('or_drag_and_drop'), maxFileSize: t('max_file_size'), + areYouSureLeave: t('are_you_sure_leave'), }; return ( diff --git a/frontend/types/case.ts b/frontend/types/case.ts index ebecad1..dbdfd44 100644 --- a/frontend/types/case.ts +++ b/frontend/types/case.ts @@ -131,6 +131,7 @@ export type CaseMessages = { clickToUpload: string; orDragAndDrop: string; maxFileSize: string; + areYouSureLeave: string; }; export { CaseType, StepType, AttachmentType, CaseTypeCountType, CasePriorityCountType, CasesMessages, CaseMessages }; diff --git a/frontend/utils/formGuard.ts b/frontend/utils/formGuard.ts new file mode 100644 index 0000000..80d4185 --- /dev/null +++ b/frontend/utils/formGuard.ts @@ -0,0 +1,30 @@ +import { useEffect } from 'react'; + +export const useFormGuard = (isDirty: boolean, confirmText: string) => { + useEffect(() => { + console.log(isDirty); + const handleClick = (event: MouseEvent) => { + if (isDirty && event.target instanceof Element && event.target.closest('a:not([target="_blank"]')) { + if (!window.confirm(confirmText)) { + event.preventDefault(); + event.stopPropagation(); + } + } + }; + + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + if (isDirty) { + event.preventDefault(); + return (event.returnValue = ''); + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + window.addEventListener('click', handleClick, true); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + window.removeEventListener('click', handleClick, true); + }; + }, [isDirty]); +};