Skip to content

Commit

Permalink
Display confirmation dialog if user has unsaved changes
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminJohnson2204 committed May 28, 2024
1 parent 129f479 commit 357d6bf
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 14 deletions.
1 change: 0 additions & 1 deletion frontend/src/app/staff/furnitureItems/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export default function furnitureItemTemplate() {

const handleBeginEditing = (category: string) => {
setEditingCategory(category);
console.log(category);
};

const handleFinishEditing = () => {
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/app/vsr/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ICreateVSRFormInput, IVSRFormInput } from "@/components/VSRForm/VSRForm
import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators";
import { ListDetail, SingleDetail } from "@/components/VSRIndividual";
import styles from "@/app/vsr/page.module.css";
import { useDirtyForm } from "@/hooks/useDirtyForm";

enum VSRFormError {
CANNOT_RETRIEVE_FURNITURE_NO_INTERNET,
Expand All @@ -56,11 +57,13 @@ const VeteranServiceRequest: React.FC = () => {
register,
handleSubmit,
control,
formState: { errors, isValid },
formState: { errors, isValid, dirtyFields },
watch,
reset,
} = formProps;

useDirtyForm({ isDirty: Object.keys(dirtyFields).length > 0 });

/**
* Internal state for fields that are complicated and cannot be controlled with a
* named form field alone (e.g. there is a multiple choice and a text field for "Other")
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/components/FurnitureRequest/EditTemplate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ConfirmDeleteModal } from "@/components/shared/ConfirmDeleteModal";
import { Button } from "@/components/shared/Button";
import { NotificationBanner } from "@/components/shared/NotificationBanner";
import { ConfirmDiscardEditsModal } from "@/components/shared/ConfirmDiscardEditsModal";
import { useDirtyForm } from "@/hooks/useDirtyForm";

enum FurnitureItemAction {
NONE,
Expand Down Expand Up @@ -178,6 +179,13 @@ export const EditTemplate = ({

const canSelectAnotherItem = isEditing && !isAddingNewItem && !editingItemId;

const hasUnsavedChanges =
(isAddingNewItem && (itemName !== "" || allowMultiple)) ||
(editingItemId !== null &&
(itemName !== getFurnitureItemById(editingItemId)?.name ||
allowMultiple !== getFurnitureItemById(editingItemId)?.allowMultiple));
useDirtyForm({ isDirty: hasUnsavedChanges });

return (
<>
<div className={`${styles.column} ${isEditing ? styles.boxShadow : ""}`}>
Expand Down Expand Up @@ -264,12 +272,7 @@ export const EditTemplate = ({
variant="error"
outlined
onClick={() => {
if (
(isAddingNewItem && (itemName || allowMultiple)) ||
(editingItemId &&
(itemName !== getFurnitureItemById(editingItemId)?.name ||
allowMultiple !== getFurnitureItemById(editingItemId)?.allowMultiple))
) {
if (hasUnsavedChanges) {
setDiscardEditsConfirmationModalOpen(true);
} else {
onFinishEditing();
Expand Down
30 changes: 24 additions & 6 deletions frontend/src/components/VSRIndividual/VSRIndividualPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { useMediaQuery } from "@mui/material";
import styles from "@/components/VSRIndividual/VSRIndividualPage/styles.module.css";
import { ConfirmDiscardEditsModal } from "@/components/shared/ConfirmDiscardEditsModal";
import { ADMIN_ROLE } from "@/constants/roles";
import { useDirtyForm } from "@/hooks/useDirtyForm";

enum VSRIndividualError {
CANNOT_RETRIEVE_FURNITURE_NO_INTERNET,
Expand All @@ -60,7 +61,14 @@ export const VSRIndividualPage = () => {
const [isEditing, setIsEditing] = useState(false);

const formProps = useForm<IEditVSRFormInput>();
const { handleSubmit } = formProps;
const {
handleSubmit,
formState: { dirtyFields },
reset,
} = formProps;

const isDirty = Object.keys(dirtyFields).length > 0;
useDirtyForm({ isDirty });

const [updateStatusSuccessNotificationOpen, setUpdateStatusSuccessNotificationOpen] =
useState(false);
Expand Down Expand Up @@ -148,6 +156,7 @@ export const VSRIndividualPage = () => {

// Handle success/error
if (response.success) {
reset();
setIsEditing(false);
setVSR(response.data);
setEditSuccessNotificationOpen(true);
Expand Down Expand Up @@ -277,6 +286,12 @@ export const VSRIndividualPage = () => {
setLoadingDownload(false);
};

const discardChanges = () => {
reset();
fetchVSR();
setIsEditing(false);
};

/**
* Conditionally renders the "Approve" button on the page, if the VSR's status is "Received"
*/
Expand Down Expand Up @@ -312,7 +327,13 @@ export const VSRIndividualPage = () => {
iconAlt="Close"
text="Discard Changes"
hideTextOnMobile
onClick={() => setDiscardEditsConfirmationModalOpen(true)}
onClick={() => {
if (isDirty) {
setDiscardEditsConfirmationModalOpen(true);
} else {
discardChanges();
}
}}
/>
<Button
variant="primary"
Expand Down Expand Up @@ -665,10 +686,7 @@ export const VSRIndividualPage = () => {
<ConfirmDiscardEditsModal
isOpen={discardEditsConfirmationModalOpen}
onClose={() => setDiscardEditsConfirmationModalOpen(false)}
onDiscardChanges={() => {
fetchVSR();
setIsEditing(false);
}}
onDiscardChanges={discardChanges}
/>

{/* Modals & notifications for saving changes to VSR */}
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/hooks/useDirtyForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect } from "react";

interface UseDirtyFormProps {
isDirty: boolean;
}

/**
* A hook that conditionally marks the page as dirty so the browser will display
* a confirmation dialog when the user tries to leave or reload it.
*/
export const useDirtyForm = ({ isDirty }: UseDirtyFormProps) => {
/**
* Returning true from this event handler tells the browser to display a confirmation
* dialog telling the user they have unsaved changes.
*/
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (!isDirty) {
return false;
}

e.preventDefault();
e.returnValue = true;
return true;
};

useEffect(() => {
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [isDirty]);
};

0 comments on commit 357d6bf

Please sign in to comment.