From 49360897072c8251aef094e93393753793b3475a Mon Sep 17 00:00:00 2001 From: Adarsh Singh Date: Sat, 4 Apr 2026 17:42:57 +0530 Subject: [PATCH] feat(ux): add confirmation modal before loading sample template Closes #867 Signed-off-by: Adarsh Singh --- src/components/SampleDropdown.tsx | 80 +++++++++++++++++++++---------- src/index.css | 34 +++++++++++++ 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/components/SampleDropdown.tsx b/src/components/SampleDropdown.tsx index 070aabe8..b916f710 100644 --- a/src/components/SampleDropdown.tsx +++ b/src/components/SampleDropdown.tsx @@ -1,5 +1,5 @@ -import { Button, Dropdown, Space, message, MenuProps } from "antd"; -import { DownOutlined } from "@ant-design/icons"; +import { Button, Dropdown, Space, message, Modal, MenuProps } from "antd"; +import { DownOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; import { useCallback, useMemo, useState } from "react"; import useAppStore from "../store/store"; import { shallow } from "zustand/shallow"; @@ -10,14 +10,19 @@ function SampleDropdown({ }: { setLoading: React.Dispatch>; }): JSX.Element { - const { samples, loadSample } = useStoreWithEqualityFn( - useAppStore, - (state) => ({ - samples: state.samples, - loadSample: state.loadSample as (key: string) => Promise, - }), - shallow - ); + const { samples, loadSample, sampleName, editorValue, editorModelCto, editorAgreementData } = + useStoreWithEqualityFn( + useAppStore, + (state) => ({ + samples: state.samples, + loadSample: state.loadSample as (key: string) => Promise, + sampleName: state.sampleName, + editorValue: state.editorValue, + editorModelCto: state.editorModelCto, + editorAgreementData: state.editorAgreementData, + }), + shallow + ); const [selectedSample, setSelectedSample] = useState(null); @@ -30,28 +35,55 @@ function SampleDropdown({ [samples] ); + const performLoadSample = useCallback( + async (key: string) => { + setLoading(true); + try { + await loadSample(key); + void message.info(`Loaded ${key} sample`); + setSelectedSample(key); + } catch (error) { + void message.error("Failed to load sample"); + } finally { + setLoading(false); + } + }, + [loadSample, setLoading] + ); + const handleMenuClick = useCallback( - async (e: { key: string }) => { + (e: { key: string }) => { if (e.key) { - setLoading(true); - try { - await loadSample(e.key); - void message.info(`Loaded ${e.key} sample`); - setSelectedSample(e.key); - } catch (error) { - void message.error("Failed to load sample"); - } finally { - setLoading(false); + const currentSample = samples.find((s) => s.NAME === sampleName); + const hasUnsavedChanges = + !currentSample || + editorValue !== currentSample.TEMPLATE || + editorModelCto !== currentSample.MODEL || + editorAgreementData !== JSON.stringify(currentSample.DATA, null, 2); + + if (hasUnsavedChanges) { + Modal.confirm({ + title: "Load Sample Template", + icon: , + content: + "Loading a new sample will replace your current Concerto Model, TemplateMark, and JSON Data. Any unsaved changes will be lost. Do you want to continue?", + okText: "Continue", + cancelText: "Cancel", + maskClosable: true, + onOk: () => performLoadSample(e.key), + }); + } else { + void performLoadSample(e.key); } } }, - [loadSample, setLoading] + [performLoadSample, samples, sampleName, editorValue, editorModelCto, editorAgreementData] ); - - + + return ( - void handleMenuClick(e) }} trigger={["click"]}> +