From 471ae3dbc81d13eeea5c1a00bb78115076bbb5e8 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Wed, 1 Jan 2025 21:58:13 +0100 Subject: [PATCH] feat: add toast notifications for experiment save success and error handling --- .../form/experiment-form/src/App.tsx | 76 ++++++++++--------- .../src/components/ExperimentForm.tsx | 13 +++- .../experiment-form/src/components/Toast.tsx | 19 +++++ .../experiment-form/src/components/Toasts.tsx | 14 ++++ .../form/experiment-form/src/utils/store.ts | 27 ++++++- 5 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 backend/experiment/templates/form/experiment-form/src/components/Toast.tsx create mode 100644 backend/experiment/templates/form/experiment-form/src/components/Toasts.tsx diff --git a/backend/experiment/templates/form/experiment-form/src/App.tsx b/backend/experiment/templates/form/experiment-form/src/App.tsx index 0b1aff68b..30743371a 100644 --- a/backend/experiment/templates/form/experiment-form/src/App.tsx +++ b/backend/experiment/templates/form/experiment-form/src/App.tsx @@ -10,6 +10,7 @@ import ExperimentForm from './components/ExperimentForm'; import Login from './components/Login'; import { useState } from 'react'; import { FiLogOut } from 'react-icons/fi'; +import { Toasts } from './components/Toasts'; function App() { const [isCollapsed, setIsCollapsed] = useState(false); @@ -41,47 +42,50 @@ function App() { } return ( - -
- -
-

- MUSCLE forms -

- - } /> - } /> - } /> -
-
-
+ + + ) } diff --git a/backend/experiment/templates/form/experiment-form/src/components/ExperimentForm.tsx b/backend/experiment/templates/form/experiment-form/src/components/ExperimentForm.tsx index eb55d47ef..18099683d 100644 --- a/backend/experiment/templates/form/experiment-form/src/components/ExperimentForm.tsx +++ b/backend/experiment/templates/form/experiment-form/src/components/ExperimentForm.tsx @@ -35,6 +35,7 @@ const ExperimentForm: React.FC = () => { const experiment = useBoundStore(state => state.experiment); const setExperiment = useBoundStore(state => state.setExperiment); const patchExperiment = useBoundStore(state => state.patchExperiment); + const addToast = useBoundStore(state => state.addToast); const [success, setSuccess] = useState(false); const [activeTab, setActiveTab] = useState<'translatedContent' | 'phases'>('translatedContent'); @@ -88,8 +89,18 @@ const ExperimentForm: React.FC = () => { const savedExperiment = await saveExperiment(experiment); setSuccess(true); setExperiment(savedExperiment); + addToast({ + message: "Experiment saved successfully!", + duration: 3000, + level: "info" + }); } catch (err) { - console.error(err); + addToast({ + message: "Failed to save experiment. Please try again.", + duration: 5000, + level: "error" + }); + console.error("Error submitting form:", err); } }; diff --git a/backend/experiment/templates/form/experiment-form/src/components/Toast.tsx b/backend/experiment/templates/form/experiment-form/src/components/Toast.tsx new file mode 100644 index 000000000..b9fc04f73 --- /dev/null +++ b/backend/experiment/templates/form/experiment-form/src/components/Toast.tsx @@ -0,0 +1,19 @@ +import { Toast as ToastType } from '../utils/store'; + +interface ToastProps { + toast: ToastType; +} + +export const Toast: React.FC = ({ toast }) => { + const bgColorClass = { + info: 'bg-gray-800', + warning: 'bg-yellow-600', + error: 'bg-red-600' + }[toast.level]; + + return ( +
+

{toast.message}

+
+ ); +}; diff --git a/backend/experiment/templates/form/experiment-form/src/components/Toasts.tsx b/backend/experiment/templates/form/experiment-form/src/components/Toasts.tsx new file mode 100644 index 000000000..f5899c13a --- /dev/null +++ b/backend/experiment/templates/form/experiment-form/src/components/Toasts.tsx @@ -0,0 +1,14 @@ +import { useBoundStore } from '../utils/store'; +import { Toast } from './Toast'; + +export const Toasts: React.FC = () => { + const toasts = useBoundStore((state) => state.toasts); + + return ( +
+ {toasts.map((toast, index) => ( + + ))} +
+ ); +}; diff --git a/backend/experiment/templates/form/experiment-form/src/utils/store.ts b/backend/experiment/templates/form/experiment-form/src/utils/store.ts index 66a0cdcf8..7c903ff1b 100644 --- a/backend/experiment/templates/form/experiment-form/src/utils/store.ts +++ b/backend/experiment/templates/form/experiment-form/src/utils/store.ts @@ -20,8 +20,33 @@ const createExperimentSlice: StateCreator = (set) => ({ }) }); -export const useBoundStore = create((...args) => ({ +interface Toast { + message: string; + duration: number; + level: "info" | "warning" | "error"; +} + +interface ToastsSlice { + toasts: Toast[]; + addToast: (toast: Toast) => void; +} + +const createToastsSlice: StateCreator = (set) => ({ + toasts: [], + addToast: (toast) => { + // Add toast to the list of toasts, then, based on the toast's duration, remove it after a certain amount of time + set((state) => ({ toasts: [...state.toasts, toast] })); + setTimeout(() => { + set((state) => ({ toasts: state.toasts.filter((t) => t !== toast) })); + }, toast.duration); + }, +}); + +export const useBoundStore = create< + ExperimentSlice & ToastsSlice +>((...args) => ({ ...createExperimentSlice(...args), + ...createToastsSlice(...args), })); export default useBoundStore;