feat: Info lost when navigating without warning routing form (#18386)

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
Amit Sharma
2025-01-06 12:19:58 +01:00
committed by GitHub
co-authored by Anik Dhabal Babu
parent 2fe4462f62
commit 455572b5f4
5 changed files with 84 additions and 3 deletions
@@ -2899,6 +2899,9 @@
"calibration": "Calibration",
"shortfall": "Shortfall",
"routing_form": "Routing Form",
"go_back_and_save": "Go back and save",
"leave_without_saving": "Leave without saving",
"leave_without_saving_description": "Are you sure you want to leave without saving changes to your routing form?",
"something_unexpected_occurred": "Something unexpected occurred",
"500_error_message": "It's not you, it's us.",
"dry_run_mode_active": "You are doing a test booking.",
@@ -0,0 +1,46 @@
import { useRouter } from "next/navigation";
import type { Dispatch, SetStateAction } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Dialog, DialogContent, Button, DialogFooter } from "@calcom/ui";
interface InfoLostWarningDialog {
isOpenInfoLostDialog: boolean;
setIsOpenInfoLostDialog: Dispatch<SetStateAction<boolean>>;
goToRoute: string;
}
const InfoLostWarningDialog = (props: InfoLostWarningDialog) => {
const { t } = useLocale();
const { isOpenInfoLostDialog, setIsOpenInfoLostDialog, goToRoute } = props;
const router = useRouter();
return (
<Dialog open={isOpenInfoLostDialog} onOpenChange={setIsOpenInfoLostDialog}>
<DialogContent
title={t("leave_without_saving")}
description={`${t("leave_without_saving_description")}`}
Icon="circle-alert"
enableOverflow
type="confirmation">
<DialogFooter className="mt-6">
<Button
onClick={() => {
setIsOpenInfoLostDialog(false);
}}
color="minimal">
{t("go_back_and_save")}
</Button>
<Button
onClick={(e) => {
setIsOpenInfoLostDialog(false);
router.replace(goToRoute);
}}>
{t("leave_without_saving")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export { InfoLostWarningDialog };
@@ -1,13 +1,21 @@
import type { Dispatch, SetStateAction } from "react";
import type { UseFormReturn } from "react-hook-form";
import { HorizontalTabs } from "@calcom/ui";
import type { getSerializableForm } from "../lib/getSerializableForm";
import type { RoutingFormWithResponseCount } from "./SingleForm";
export default function RoutingNavBar({
form,
appUrl,
hookForm,
setShowInfoLostDialog,
}: {
form: Awaited<ReturnType<typeof getSerializableForm>>;
appUrl: string;
hookForm: UseFormReturn<RoutingFormWithResponseCount>;
setShowInfoLostDialog: Dispatch<SetStateAction<boolean>>;
}) {
const tabs = [
{
@@ -17,6 +25,13 @@ export default function RoutingNavBar({
{
name: "Routing",
href: `${appUrl}/route-builder/${form?.id}`,
onClick: () => {
if (hookForm.formState.isDirty) {
setShowInfoLostDialog(true);
} else {
window.location.href = `${appUrl}/route-builder/${form?.id}`;
}
},
},
{
name: "Reporting",
@@ -44,6 +44,7 @@ import { findMatchingRoute } from "../lib/processRoute";
import type { FormResponse, NonRouterRoute, SerializableForm } from "../types/types";
import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions";
import FormInputFields from "./FormInputFields";
import { InfoLostWarningDialog } from "./InfoLostWarningDialog";
import RoutingNavBar from "./RoutingNavBar";
import { getServerSidePropsForSingleFormView } from "./getServerSidePropsSingleForm";
@@ -682,6 +683,7 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
const [isTestPreviewOpen, setIsTestPreviewOpen] = useState(false);
const [skipFirstUpdate, setSkipFirstUpdate] = useState(true);
const [showInfoLostDialog, setShowInfoLostDialog] = useState(false);
const hookForm = useFormContext<RoutingFormWithResponseCount>();
useEffect(() => {
@@ -944,7 +946,12 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
)}
</div>
<div className="border-subtle bg-muted w-full rounded-md border p-8">
<RoutingNavBar appUrl={appUrl} form={form} />
<RoutingNavBar
appUrl={appUrl}
form={form}
hookForm={hookForm}
setShowInfoLostDialog={setShowInfoLostDialog}
/>
<Page hookForm={hookForm} form={form} appUrl={appUrl} />
</div>
</div>
@@ -952,6 +959,13 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
</ShellMain>
</FormActionsProvider>
</Form>
{showInfoLostDialog && (
<InfoLostWarningDialog
goToRoute={`${appUrl}/route-builder/${form?.id}`}
isOpenInfoLostDialog={showInfoLostDialog}
setIsOpenInfoLostDialog={setShowInfoLostDialog}
/>
)}
<TestFormDialog
form={uptoDateForm}
isTestPreviewOpen={isTestPreviewOpen}
@@ -91,7 +91,7 @@ function Field({
}) || [];
const setOptions = (updatedOptions: SelectOption[]) => {
hookForm.setValue(`${hookFieldNamespace}.options`, updatedOptions);
hookForm.setValue(`${hookFieldNamespace}.options`, updatedOptions, { shouldDirty: true });
};
const handleRemoveOptions = (index: number) => {
@@ -206,6 +206,9 @@ function Field({
defaultValue={label || routerField?.label || ""}
required
{...hookForm.register(`${hookFieldNamespace}.label`)}
onChange={(e) => {
hookForm.setValue(`${hookFieldNamespace}.label`, e.target.value, { shouldDirty: true });
}}
/>
</div>
<div className="mb-6 w-full">
@@ -220,7 +223,7 @@ function Field({
// The identifier field will have the same value as the label field until it is changed
value={identifier || routerField?.identifier || label || routerField?.label || ""}
onChange={(e) => {
hookForm.setValue(`${hookFieldNamespace}.identifier`, e.target.value);
hookForm.setValue(`${hookFieldNamespace}.identifier`, e.target.value, { shouldDirty: true });
}}
/>
</div>