diff --git a/apps/studio/src/features/editing-experience/components/ComplexEditorStateDrawer.tsx b/apps/studio/src/features/editing-experience/components/ComplexEditorStateDrawer.tsx index b543749ebb..f8495e9282 100644 --- a/apps/studio/src/features/editing-experience/components/ComplexEditorStateDrawer.tsx +++ b/apps/studio/src/features/editing-experience/components/ComplexEditorStateDrawer.tsx @@ -10,6 +10,7 @@ import { import { Button, IconButton } from "@opengovsg/design-system-react" import { getComponentSchema } from "@opengovsg/isomer-components" import Ajv from "ajv" +import _ from "lodash" import { BiDollar, BiTrash, BiX } from "react-icons/bi" import { useEditorDrawerContext } from "~/contexts/EditorDrawerContext" @@ -17,11 +18,22 @@ import { useQueryParse } from "~/hooks/useQueryParse" import { editPageSchema } from "~/pages/sites/[siteId]/pages/[pageId]" import { trpc } from "~/utils/trpc" import { DeleteBlockModal } from "./DeleteBlockModal" +import { DiscardChangesModal } from "./DiscardChangesModal" import FormBuilder from "./form-builder/FormBuilder" const ajv = new Ajv({ strict: false, logger: false }) export default function ComplexEditorStateDrawer(): JSX.Element { + const { + isOpen: isDeleteBlockModalOpen, + onOpen: onDeleteBlockModalOpen, + onClose: onDeleteBlockModalClose, + } = useDisclosure() + const { + isOpen: isDiscardChangesModalOpen, + onOpen: onDiscardChangesModalOpen, + onClose: onDiscardChangesModalClose, + } = useDisclosure() const { setDrawerState, currActiveIdx, @@ -30,6 +42,7 @@ export default function ComplexEditorStateDrawer(): JSX.Element { previewPageState, setPreviewPageState, } = useEditorDrawerContext() + const { pageId, siteId } = useQueryParse(editPageSchema) const [{ content: pageContent }] = trpc.page.readPageAndBlob.useSuspenseQuery( { siteId, pageId }, @@ -41,11 +54,6 @@ export default function ComplexEditorStateDrawer(): JSX.Element { await utils.page.readPageAndBlob.invalidate({ pageId, siteId }) }, }) - const { - isOpen: isDeleteBlockModalOpen, - onOpen: onDeleteBlockModalOpen, - onClose: onDeleteBlockModalClose, - } = useDisclosure() if ( currActiveIdx === -1 || @@ -80,6 +88,12 @@ export default function ComplexEditorStateDrawer(): JSX.Element { setDrawerState({ state: "root" }) } + const handleDiscardChanges = () => { + setPreviewPageState(savedPageState) + onDiscardChangesModalClose() + setDrawerState({ state: "root" }) + } + const handleChange = (data: IsomerComponent) => { const updatedBlocks = Array.from(previewPageState.content) updatedBlocks[currActiveIdx] = data @@ -99,6 +113,12 @@ export default function ComplexEditorStateDrawer(): JSX.Element { onDelete={handleDeleteBlock} /> + + { - setPreviewPageState(savedPageState) - setDrawerState({ state: "root" }) + if (!_.isEqual(previewPageState, savedPageState)) { + onDiscardChangesModalOpen() + } else { + handleDiscardChanges() + } }} aria-label="Close drawer" /> diff --git a/apps/studio/src/features/editing-experience/components/DiscardChangesModal/DiscardChangesModal.tsx b/apps/studio/src/features/editing-experience/components/DiscardChangesModal/DiscardChangesModal.tsx new file mode 100644 index 0000000000..3491d81654 --- /dev/null +++ b/apps/studio/src/features/editing-experience/components/DiscardChangesModal/DiscardChangesModal.tsx @@ -0,0 +1,50 @@ +import { + HStack, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, +} from "@chakra-ui/react" +import { Button, ModalCloseButton } from "@opengovsg/design-system-react" + +interface DiscardChangesModalProps { + isOpen: boolean + onClose: () => void + onDiscard: () => void +} + +export const DiscardChangesModal = ({ + isOpen, + onClose, + onDiscard, +}: DiscardChangesModalProps): JSX.Element => { + return ( + + + + + Are you sure you want to discard your changes? + + + + + All edits will be lost. + + + + + + + + + + + ) +} diff --git a/apps/studio/src/features/editing-experience/components/DiscardChangesModal/index.ts b/apps/studio/src/features/editing-experience/components/DiscardChangesModal/index.ts new file mode 100644 index 0000000000..04702f8143 --- /dev/null +++ b/apps/studio/src/features/editing-experience/components/DiscardChangesModal/index.ts @@ -0,0 +1 @@ +export * from "./DiscardChangesModal" diff --git a/apps/studio/src/features/editing-experience/components/MetadataEditorStateDrawer.tsx b/apps/studio/src/features/editing-experience/components/MetadataEditorStateDrawer.tsx index 4736c84bda..75cb1b1f52 100644 --- a/apps/studio/src/features/editing-experience/components/MetadataEditorStateDrawer.tsx +++ b/apps/studio/src/features/editing-experience/components/MetadataEditorStateDrawer.tsx @@ -1,20 +1,43 @@ import type { IsomerSchema, schema } from "@opengovsg/isomer-components" import type { Static } from "@sinclair/typebox" -import { Box, Flex, Heading, HStack, Icon, IconButton } from "@chakra-ui/react" +import { + Box, + Flex, + Heading, + HStack, + Icon, + IconButton, + useDisclosure, +} from "@chakra-ui/react" import { Button } from "@opengovsg/design-system-react" import { getLayoutMetadataSchema } from "@opengovsg/isomer-components" import Ajv from "ajv" +import _ from "lodash" import { BiDollar, BiX } from "react-icons/bi" import { useEditorDrawerContext } from "~/contexts/EditorDrawerContext" import { useQueryParse } from "~/hooks/useQueryParse" import { editPageSchema } from "~/pages/sites/[siteId]/pages/[pageId]" import { trpc } from "~/utils/trpc" +import { DiscardChangesModal } from "./DiscardChangesModal" import FormBuilder from "./form-builder/FormBuilder" const ajv = new Ajv({ strict: false, logger: false }) export default function MetadataEditorStateDrawer(): JSX.Element { + const { + isOpen: isDiscardChangesModalOpen, + onOpen: onDiscardChangesModalOpen, + onClose: onDiscardChangesModalClose, + } = useDisclosure() + const { + setDrawerState, + savedPageState, + setSavedPageState, + previewPageState, + setPreviewPageState, + } = useEditorDrawerContext() + const { pageId, siteId } = useQueryParse(editPageSchema) const utils = trpc.useUtils() const [{ content: pageContent }] = trpc.page.readPageAndBlob.useSuspenseQuery( @@ -25,13 +48,6 @@ export default function MetadataEditorStateDrawer(): JSX.Element { await utils.page.readPageAndBlob.invalidate({ pageId, siteId }) }, }) - const { - setDrawerState, - savedPageState, - setSavedPageState, - previewPageState, - setPreviewPageState, - } = useEditorDrawerContext() if (!previewPageState) { return <> @@ -50,8 +66,20 @@ export default function MetadataEditorStateDrawer(): JSX.Element { setPreviewPageState(newPageState) } + const handleDiscardChanges = () => { + setPreviewPageState(savedPageState) + onDiscardChangesModalClose() + setDrawerState({ state: "root" }) + } + return ( <> + + { - setPreviewPageState(savedPageState) - setDrawerState({ state: "root" }) + if (!_.isEqual(previewPageState, savedPageState)) { + onDiscardChangesModalOpen() + } else { + handleDiscardChanges() + } }} aria-label="Close drawer" /> diff --git a/apps/studio/src/features/editing-experience/components/RootStateDrawer.tsx b/apps/studio/src/features/editing-experience/components/RootStateDrawer.tsx index f712e33e96..dd3ef938d9 100644 --- a/apps/studio/src/features/editing-experience/components/RootStateDrawer.tsx +++ b/apps/studio/src/features/editing-experience/components/RootStateDrawer.tsx @@ -57,7 +57,7 @@ export default function RootStateDrawer() { const from = result.source.index const to = result.destination.index - const contentLength = savedPageState?.content.length ?? 0 + const contentLength = savedPageState.content.length ?? 0 if (from >= contentLength || to >= contentLength || from < 0 || to < 0) return diff --git a/apps/studio/src/features/editing-experience/components/TipTapComponent.tsx b/apps/studio/src/features/editing-experience/components/TipTapComponent.tsx index da48111241..51f067ed4f 100644 --- a/apps/studio/src/features/editing-experience/components/TipTapComponent.tsx +++ b/apps/studio/src/features/editing-experience/components/TipTapComponent.tsx @@ -10,6 +10,7 @@ import { VStack, } from "@chakra-ui/react" import { Button, IconButton } from "@opengovsg/design-system-react" +import _ from "lodash" import { BiText, BiTrash, BiX } from "react-icons/bi" import { PROSE_COMPONENT_NAME } from "~/constants/formBuilder" @@ -18,6 +19,7 @@ import { useQueryParse } from "~/hooks/useQueryParse" import { editPageSchema } from "~/pages/sites/[siteId]/pages/[pageId]" import { trpc } from "~/utils/trpc" import { DeleteBlockModal } from "./DeleteBlockModal" +import { DiscardChangesModal } from "./DiscardChangesModal" import { TiptapEditor } from "./form-builder/renderers/TipTapEditor" interface TipTapComponentProps { @@ -25,6 +27,16 @@ interface TipTapComponentProps { } function TipTapComponent({ content }: TipTapComponentProps) { + const { + isOpen: isDeleteBlockModalOpen, + onOpen: onDeleteBlockModalOpen, + onClose: onDeleteBlockModalClose, + } = useDisclosure() + const { + isOpen: isDiscardChangesModalOpen, + onOpen: onDiscardChangesModalOpen, + onClose: onDiscardChangesModalClose, + } = useDisclosure() const { savedPageState, setDrawerState, @@ -33,11 +45,6 @@ function TipTapComponent({ content }: TipTapComponentProps) { setPreviewPageState, currActiveIdx, } = useEditorDrawerContext() - const { - isOpen: isDeleteBlockModalOpen, - onOpen: onDeleteBlockModalOpen, - onClose: onDeleteBlockModalClose, - } = useDisclosure() const { pageId, siteId } = useQueryParse(editPageSchema) @@ -67,6 +74,12 @@ function TipTapComponent({ content }: TipTapComponentProps) { setDrawerState({ state: "root" }) } + const handleDiscardChanges = () => { + setPreviewPageState(savedPageState) + onDiscardChangesModalClose() + setDrawerState({ state: "root" }) + } + const utils = trpc.useUtils() const { mutate } = trpc.page.updatePageBlob.useMutation({ @@ -88,6 +101,12 @@ function TipTapComponent({ content }: TipTapComponentProps) { onDelete={handleDeleteBlock} /> + + } onClick={() => { - setDrawerState({ state: "root" }) - setPreviewPageState(savedPageState) + if (!_.isEqual(previewPageState, savedPageState)) { + onDiscardChangesModalOpen() + } else { + handleDiscardChanges() + } }} />