From 3274d5ab6bce32033b1a9e7e9f068bed8837ab19 Mon Sep 17 00:00:00 2001
From: Sean Hatfield <seanhatfield5@gmail.com>
Date: Wed, 23 Aug 2023 20:35:34 -0700
Subject: [PATCH] Implemented toast messages for all system settings (#219)

* implemented toast for appearance settings changes

* linting

* implemented toast on system settings changes
---
 .../Modals/Settings/Appearance/index.jsx      | 43 +++----------------
 .../Modals/Settings/LLMSelection/index.jsx    | 15 +++----
 .../Settings/PasswordProtection/index.jsx     | 26 +++--------
 frontend/src/pages/Admin/Appearance/index.jsx | 42 +++---------------
 frontend/src/pages/Admin/System/index.jsx     |  2 +
 5 files changed, 27 insertions(+), 101 deletions(-)

diff --git a/frontend/src/components/Modals/Settings/Appearance/index.jsx b/frontend/src/components/Modals/Settings/Appearance/index.jsx
index 8108e74e6..92f210d59 100644
--- a/frontend/src/components/Modals/Settings/Appearance/index.jsx
+++ b/frontend/src/components/Modals/Settings/Appearance/index.jsx
@@ -5,13 +5,12 @@ import System from "../../../../models/system";
 import EditingChatBubble from "../../../EditingChatBubble";
 import AnythingLLMLight from "../../../../media/logo/anything-llm-light.png";
 import AnythingLLMDark from "../../../../media/logo/anything-llm-dark.png";
+import showToast from "../../../../utils/toast";
 
 export default function Appearance() {
   const { logo: _initLogo } = useLogo();
   const prefersDarkMode = usePrefersDarkMode();
   const [logo, setLogo] = useState("");
-  const [errorMsg, setErrorMsg] = useState("");
-  const [successMsg, setSuccessMsg] = useState("");
   const [hasChanges, setHasChanges] = useState(false);
   const [messages, setMessages] = useState([]);
 
@@ -30,20 +29,6 @@ export default function Appearance() {
     setInitLogo();
   }, [_initLogo]);
 
-  useEffect(() => {
-    if (!!successMsg) {
-      setTimeout(() => {
-        setSuccessMsg("");
-      }, 3_500);
-    }
-
-    if (!!errorMsg) {
-      setTimeout(() => {
-        setErrorMsg("");
-      }, 3_500);
-    }
-  }, [successMsg, errorMsg]);
-
   const handleFileUpload = async (event) => {
     const file = event.target.files[0];
     if (!file) return false;
@@ -53,30 +38,26 @@ export default function Appearance() {
     const { success, error } = await System.uploadLogo(formData);
     if (!success) {
       console.error("Failed to upload logo:", error);
-      setErrorMsg(error);
-      setSuccessMsg("");
+      showToast(`Failed to upload logo: ${error}`, "error");
       return;
     }
 
     const logoURL = await System.fetchLogo();
     setLogo(logoURL);
-    setSuccessMsg("Image uploaded successfully");
-    setErrorMsg("");
+    showToast("Image uploaded successfully.", "success");
   };
 
   const handleRemoveLogo = async () => {
     const { success, error } = await System.removeCustomLogo();
     if (!success) {
       console.error("Failed to remove logo:", error);
-      setErrorMsg(error);
-      setSuccessMsg("");
+      showToast(`Failed to remove logo: ${error}`, "error");
       return;
     }
 
     const logoURL = await System.fetchLogo();
     setLogo(logoURL);
-    setSuccessMsg("Image successfully removed");
-    setErrorMsg("");
+    showToast("Image successfully removed.", "success");
   };
 
   const addMessage = (type) => {
@@ -108,10 +89,10 @@ export default function Appearance() {
   const handleMessageSave = async () => {
     const { success, error } = await System.setWelcomeMessages(messages);
     if (!success) {
-      setErrorMsg(error);
+      showToast(`Failed to update welcome messages: ${error}`, "error");
       return;
     }
-    setSuccessMsg("Successfully updated welcome messages.");
+    showToast("Successfully updated welcome messages.", "success");
     setHasChanges(false);
   };
 
@@ -227,16 +208,6 @@ export default function Appearance() {
               </div>
             )}
           </div>
-          {errorMsg && (
-            <div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
-              {errorMsg}
-            </div>
-          )}
-          {successMsg && (
-            <div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
-              {successMsg}
-            </div>
-          )}
         </div>
       </div>
     </div>
diff --git a/frontend/src/components/Modals/Settings/LLMSelection/index.jsx b/frontend/src/components/Modals/Settings/LLMSelection/index.jsx
index 2cf014352..bc40db3eb 100644
--- a/frontend/src/components/Modals/Settings/LLMSelection/index.jsx
+++ b/frontend/src/components/Modals/Settings/LLMSelection/index.jsx
@@ -3,6 +3,7 @@ import System from "../../../../models/system";
 import OpenAiLogo from "../../../../media/llmprovider/openai.png";
 import AzureOpenAiLogo from "../../../../media/llmprovider/azure.png";
 import AnthropicLogo from "../../../../media/llmprovider/anthropic.png";
+import showToast from "../../../../utils/toast";
 
 const noop = () => false;
 export default function LLMSelection({
@@ -13,7 +14,6 @@ export default function LLMSelection({
   const [hasChanges, setHasChanges] = useState(false);
   const [llmChoice, setLLMChoice] = useState(settings?.LLMProvider || "openai");
   const [saving, setSaving] = useState(false);
-  const [error, setError] = useState(null);
   const canDebug = settings.MultiUserMode
     ? settings?.CanDebug && user?.role === "admin"
     : settings?.CanDebug;
@@ -27,12 +27,15 @@ export default function LLMSelection({
   const handleSubmit = async (e) => {
     e.preventDefault();
     setSaving(true);
-    setError(null);
     const data = {};
     const form = new FormData(e.target);
     for (var [key, value] of form.entries()) data[key] = value;
     const { error } = await System.updateSystem(data);
-    setError(error);
+    if (error) {
+      showToast(`Failed to save LLM settings: ${error}`, "error");
+    } else {
+      showToast("LLM settings saved successfully.", "success");
+    }
     setSaving(false);
     setHasChanges(!!error ? true : false);
   };
@@ -47,12 +50,6 @@ export default function LLMSelection({
           </p>
         </div>
 
-        {!!error && (
-          <div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto">
-            <p className="text-red-800 dark:text-orange-300 text-sm">{error}</p>
-          </div>
-        )}
-
         <form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}>
           <div className="px-6 space-y-6 flex h-full w-full">
             <div className="w-full flex flex-col gap-y-4">
diff --git a/frontend/src/components/Modals/Settings/PasswordProtection/index.jsx b/frontend/src/components/Modals/Settings/PasswordProtection/index.jsx
index 5e6269121..3b1a92dce 100644
--- a/frontend/src/components/Modals/Settings/PasswordProtection/index.jsx
+++ b/frontend/src/components/Modals/Settings/PasswordProtection/index.jsx
@@ -1,6 +1,7 @@
 import React, { useState } from "react";
 import System from "../../../../models/system";
 import { AUTH_TOKEN, AUTH_USER } from "../../../../utils/constants";
+import showToast from "../../../../utils/toast";
 
 const noop = () => false;
 export default function PasswordProtection({
@@ -8,15 +9,11 @@ export default function PasswordProtection({
   settings = {},
 }) {
   const [saving, setSaving] = useState(false);
-  const [success, setSuccess] = useState(false);
-  const [error, setError] = useState(null);
   const [usePassword, setUsePassword] = useState(settings?.RequiresAuth);
 
   const handleSubmit = async (e) => {
     e.preventDefault();
     setSaving(true);
-    setSuccess(false);
-    setError(null);
 
     const form = new FormData(e.target);
     const data = {
@@ -26,17 +23,18 @@ export default function PasswordProtection({
 
     const { success, error } = await System.updateSystemPassword(data);
     if (success) {
-      setSuccess(true);
+      showToast("Your page will refresh in a few seconds.", "success");
       setSaving(false);
       setTimeout(() => {
         window.localStorage.removeItem(AUTH_USER);
         window.localStorage.removeItem(AUTH_TOKEN);
         window.location.reload();
-      }, 2_000);
+      }, 3_000);
       return;
+    } else {
+      showToast(`Failed to update password: ${error}`, "error");
     }
 
-    setError(error);
     setSaving(false);
   };
 
@@ -49,20 +47,6 @@ export default function PasswordProtection({
             this there is no recovery method so ensure you save this password.
           </p>
         </div>
-        {(error || success) && (
-          <div className="w-full flex px-6">
-            {error && (
-              <div className="w-full bg-red-300 text-red-800 font-semibold px-4 py-2 rounded-lg">
-                {error}
-              </div>
-            )}
-            {success && (
-              <div className="w-full bg-green-300 text-green-800 font-semibold px-4 py-2 rounded-lg">
-                Your page will refresh in a few seconds.
-              </div>
-            )}
-          </div>
-        )}
         <div className="p-6 space-y-6 flex h-full w-full">
           <div className="w-full flex flex-col gap-y-4">
             <form onSubmit={handleSubmit}>
diff --git a/frontend/src/pages/Admin/Appearance/index.jsx b/frontend/src/pages/Admin/Appearance/index.jsx
index e96319631..08e61f67c 100644
--- a/frontend/src/pages/Admin/Appearance/index.jsx
+++ b/frontend/src/pages/Admin/Appearance/index.jsx
@@ -8,13 +8,12 @@ import usePrefersDarkMode from "../../../hooks/usePrefersDarkMode";
 import useLogo from "../../../hooks/useLogo";
 import System from "../../../models/system";
 import EditingChatBubble from "../../../components/EditingChatBubble";
+import showToast from "../../../utils/toast";
 
 export default function Appearance() {
   const { logo: _initLogo } = useLogo();
   const [logo, setLogo] = useState("");
   const prefersDarkMode = usePrefersDarkMode();
-  const [errorMsg, setErrorMsg] = useState("");
-  const [successMsg, setSuccessMsg] = useState("");
   const [hasChanges, setHasChanges] = useState(false);
   const [messages, setMessages] = useState([]);
 
@@ -25,20 +24,6 @@ export default function Appearance() {
     setInitLogo();
   }, [_initLogo]);
 
-  useEffect(() => {
-    if (!!errorMsg) {
-      setTimeout(() => {
-        setErrorMsg("");
-      }, 3_500);
-    }
-
-    if (!!successMsg) {
-      setTimeout(() => {
-        setSuccessMsg("");
-      }, 3_500);
-    }
-  }, [errorMsg, successMsg]);
-
   useEffect(() => {
     async function fetchMessages() {
       const messages = await System.getWelcomeMessages();
@@ -55,29 +40,26 @@ export default function Appearance() {
     formData.append("logo", file);
     const { success, error } = await Admin.uploadLogo(formData);
     if (!success) {
-      setErrorMsg(error);
+      showToast(`Failed to upload logo: ${error}`, "error");
       return;
     }
 
     const logoURL = await System.fetchLogo();
     setLogo(logoURL);
-    setErrorMsg("");
-    window.location.reload();
+    showToast("Image uploaded successfully.", "success");
   };
 
   const handleRemoveLogo = async () => {
     const { success, error } = await Admin.removeCustomLogo();
     if (!success) {
       console.error("Failed to remove logo:", error);
-      setErrorMsg(error);
+      showToast(`Failed to remove logo: ${error}`, "error");
       return;
     }
 
     const logoURL = await System.fetchLogo();
     setLogo(logoURL);
-    setErrorMsg("");
-
-    window.location.reload();
+    showToast("Image successfully removed.", "success");
   };
 
   const addMessage = (type) => {
@@ -109,10 +91,10 @@ export default function Appearance() {
   const handleMessageSave = async () => {
     const { success, error } = await Admin.setWelcomeMessages(messages);
     if (!success) {
-      setErrorMsg(error);
+      showToast(`Failed to update welcome messages: ${error}`, "error");
       return;
     }
-    setSuccessMsg("Successfully updated welcome messages.");
+    showToast("Successfully updated welcome messages.", "success");
     setHasChanges(false);
   };
 
@@ -235,16 +217,6 @@ export default function Appearance() {
               </div>
             )}
           </div>
-          {errorMsg && (
-            <div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
-              {errorMsg}
-            </div>
-          )}
-          {successMsg && (
-            <div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
-              {successMsg}
-            </div>
-          )}
         </div>
       </div>
     </div>
diff --git a/frontend/src/pages/Admin/System/index.jsx b/frontend/src/pages/Admin/System/index.jsx
index 58874b90b..b16a17c26 100644
--- a/frontend/src/pages/Admin/System/index.jsx
+++ b/frontend/src/pages/Admin/System/index.jsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
 import Sidebar, { SidebarMobileHeader } from "../../../components/AdminSidebar";
 import { isMobile } from "react-device-detect";
 import Admin from "../../../models/admin";
+import showToast from "../../../utils/toast";
 
 export default function AdminSystem() {
   const [saving, setSaving] = useState(false);
@@ -21,6 +22,7 @@ export default function AdminSystem() {
     });
     setSaving(false);
     setHasChanges(false);
+    showToast("System preferences updated successfully.", "success");
   };
 
   useEffect(() => {