From 62da5c9933bb8834ffc201aee6851fe34dae2c8d Mon Sep 17 00:00:00 2001
From: Sean Hatfield <seanhatfield5@gmail.com>
Date: Tue, 23 Jan 2024 14:19:08 -0800
Subject: [PATCH] [REFACTOR] remove all <dialog> modals and replace with custom
 ModalWrapper component (#641)

* add useModal hook and ModalWrapper component that will be used to replace all <dialog> modals for better browser support

* implement useModal hook and ModalWrapper component to replace all exisiting <dialog>
---
 .../src/components/ChangeWarning/index.jsx    |  68 +++---
 .../src/components/ModalWrapper/index.jsx     |   9 +
 frontend/src/components/UserMenu/index.jsx    |   2 +-
 .../ChatHistory/Citation/index.jsx            |  28 +--
 .../src/components/WorkspaceChat/index.jsx    |   9 +-
 frontend/src/hooks/useModal.js                |  10 +
 .../Invitations/NewInviteModal/index.jsx      | 131 +++++-----
 .../src/pages/Admin/Invitations/index.jsx     |  13 +-
 .../pages/Admin/Users/NewUserModal/index.jsx  | 197 ++++++++-------
 .../Users/UserRow/EditUserModal/index.jsx     | 188 +++++++--------
 .../src/pages/Admin/Users/UserRow/index.jsx   |  17 +-
 frontend/src/pages/Admin/Users/index.jsx      |  13 +-
 .../Workspaces/NewWorkspaceModal/index.jsx    | 120 +++++-----
 .../EditWorkspaceUsersModal/index.jsx         | 225 +++++++++---------
 .../Admin/Workspaces/WorkspaceRow/index.jsx   |  22 +-
 frontend/src/pages/Admin/Workspaces/index.jsx |  13 +-
 .../ApiKeys/NewApiKeyModal/index.jsx          | 144 ++++++-----
 .../pages/GeneralSettings/ApiKeys/index.jsx   |  14 +-
 .../GeneralSettings/Chats/ChatRow/index.jsx   |  79 +++---
 .../EmbeddingPreference/index.jsx             |  19 +-
 .../GeneralSettings/VectorDatabase/index.jsx  |  19 +-
 .../src/pages/Invite/NewUserModal/index.jsx   | 122 +++++-----
 frontend/src/pages/Invite/index.jsx           |   5 +-
 23 files changed, 722 insertions(+), 745 deletions(-)
 create mode 100644 frontend/src/components/ModalWrapper/index.jsx
 create mode 100644 frontend/src/hooks/useModal.js

diff --git a/frontend/src/components/ChangeWarning/index.jsx b/frontend/src/components/ChangeWarning/index.jsx
index a0fe14c1f..42b211baf 100644
--- a/frontend/src/components/ChangeWarning/index.jsx
+++ b/frontend/src/components/ChangeWarning/index.jsx
@@ -6,44 +6,42 @@ export default function ChangeWarningModal({
   onConfirm,
 }) {
   return (
-    <dialog id="confirmation-modal" className="bg-transparent outline-none">
-      <div className="relative w-full max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <div className="flex items-center gap-2">
-              <Warning
-                className="text-yellow-300 text-lg w-6 h-6"
-                weight="fill"
-              />
-              <h3 className="text-xl font-semibold text-yellow-300">Warning</h3>
-            </div>
-          </div>
-          <div className="w-[550px] p-6 text-white">
-            <p>
-              {warningText}
-              <br />
-              <br />
-              Are you sure you want to proceed?
-            </p>
+    <div className="relative w-full max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <div className="flex items-center gap-2">
+            <Warning
+              className="text-yellow-300 text-lg w-6 h-6"
+              weight="fill"
+            />
+            <h3 className="text-xl font-semibold text-yellow-300">Warning</h3>
           </div>
+        </div>
+        <div className="w-[550px] p-6 text-white">
+          <p>
+            {warningText}
+            <br />
+            <br />
+            Are you sure you want to proceed?
+          </p>
+        </div>
 
-          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-            <button
-              onClick={onClose}
-              type="button"
-              className="px-4 py-2 rounded-lg text-white hover:bg-red-500 transition-all duration-300"
-            >
-              Cancel
-            </button>
-            <button
-              onClick={onConfirm}
-              className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-            >
-              Confirm
-            </button>
-          </div>
+        <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
+          <button
+            onClick={onClose}
+            type="button"
+            className="px-4 py-2 rounded-lg text-white hover:bg-red-500 transition-all duration-300"
+          >
+            Cancel
+          </button>
+          <button
+            onClick={onConfirm}
+            className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
+          >
+            Confirm
+          </button>
         </div>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/components/ModalWrapper/index.jsx b/frontend/src/components/ModalWrapper/index.jsx
new file mode 100644
index 000000000..37041f634
--- /dev/null
+++ b/frontend/src/components/ModalWrapper/index.jsx
@@ -0,0 +1,9 @@
+export default function ModalWrapper({ children, isOpen }) {
+  if (!isOpen) return null;
+
+  return (
+    <div className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center z-30">
+      {children}
+    </div>
+  );
+}
diff --git a/frontend/src/components/UserMenu/index.jsx b/frontend/src/components/UserMenu/index.jsx
index 61e111da4..e6f3c7cfb 100644
--- a/frontend/src/components/UserMenu/index.jsx
+++ b/frontend/src/components/UserMenu/index.jsx
@@ -193,7 +193,7 @@ function AccountModal({ user, hideModal }) {
   return (
     <div
       id="account-modal"
-      className="bg-black/20 fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center"
+      className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center"
     >
       <div className="relative w-[500px] max-w-2xl max-h-full bg-main-gradient rounded-lg shadow">
         <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
index 9af36fc5a..697c55214 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
@@ -1,9 +1,10 @@
-import { memo, useState, useEffect, useRef } from "react";
+import { memo, useState } from "react";
 import { X } from "@phosphor-icons/react";
 import { v4 } from "uuid";
 import { decode as HTMLDecode } from "he";
 import { CaretRight, FileText } from "@phosphor-icons/react";
 import truncate from "truncate";
+import ModalWrapper from "@/components/ModalWrapper";
 
 function combineLikeSources(sources) {
   const combined = {};
@@ -98,27 +99,10 @@ function SkeletonLine() {
 
 function CitationDetailModal({ source, onClose }) {
   const { references, title, text } = source;
-  const dialogRef = useRef(null);
-
-  useEffect(() => {
-    if (source && dialogRef.current) {
-      dialogRef.current.showModal();
-    }
-  }, [source]);
-
-  const handleModalClose = () => {
-    if (dialogRef.current) {
-      dialogRef.current.close();
-    }
-    onClose();
-  };
 
   return (
-    <dialog
-      ref={dialogRef}
-      className="bg-transparent outline-none fixed top-0 left-0 w-full h-full flex items-center justify-center z-10"
-    >
-      <div className="relative w-full max-w-2xl bg-main-gradient rounded-lg shadow border border-white/10 overflow-hidden">
+    <ModalWrapper isOpen={source}>
+      <div className="w-full max-w-2xl bg-main-gradient rounded-lg shadow border border-white/10 overflow-hidden">
         <div className="relative p-6 border-b rounded-t border-gray-500/50">
           <h3 className="text-xl font-semibold text-white overflow-hidden overflow-ellipsis whitespace-nowrap">
             {truncate(title, 45)}
@@ -129,7 +113,7 @@ function CitationDetailModal({ source, onClose }) {
             </p>
           )}
           <button
-            onClick={handleModalClose}
+            onClick={onClose}
             type="button"
             className="absolute top-6 right-6 transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
           >
@@ -153,7 +137,7 @@ function CitationDetailModal({ source, onClose }) {
           </div>
         </div>
       </div>
-    </dialog>
+    </ModalWrapper>
   );
 }
 
diff --git a/frontend/src/components/WorkspaceChat/index.jsx b/frontend/src/components/WorkspaceChat/index.jsx
index 30bd494f3..cbe7dbdd0 100644
--- a/frontend/src/components/WorkspaceChat/index.jsx
+++ b/frontend/src/components/WorkspaceChat/index.jsx
@@ -3,6 +3,7 @@ import Workspace from "@/models/workspace";
 import LoadingChat from "./LoadingChat";
 import ChatContainer from "./ChatContainer";
 import paths from "@/utils/paths";
+import ModalWrapper from "../ModalWrapper";
 
 export default function WorkspaceChat({ loading, workspace }) {
   const [history, setHistory] = useState([]);
@@ -28,11 +29,7 @@ export default function WorkspaceChat({ loading, workspace }) {
     return (
       <>
         {loading === false && !workspace && (
-          <dialog
-            open={true}
-            style={{ zIndex: 100 }}
-            className="fixed top-0 flex bg-black bg-opacity-50 w-full md:w-[100vw] h-full items-center justify-center"
-          >
+          <ModalWrapper isOpen={true}>
             <div className="relative w-full md:max-w-2xl max-h-full bg-main-gradient rounded-lg shadow p-4">
               <div className="flex flex-col gap-y-4 w-full p-6 text-center">
                 <p className="font-semibold text-red-500 text-xl">
@@ -52,7 +49,7 @@ export default function WorkspaceChat({ loading, workspace }) {
                 </div>
               </div>
             </div>
-          </dialog>
+          </ModalWrapper>
         )}
         <LoadingChat />
       </>
diff --git a/frontend/src/hooks/useModal.js b/frontend/src/hooks/useModal.js
new file mode 100644
index 000000000..854b5d4a8
--- /dev/null
+++ b/frontend/src/hooks/useModal.js
@@ -0,0 +1,10 @@
+import { useState } from "react";
+
+export function useModal() {
+  const [isOpen, setIsOpen] = useState(false);
+
+  const openModal = () => setIsOpen(true);
+  const closeModal = () => setIsOpen(false);
+
+  return { isOpen, openModal, closeModal };
+}
diff --git a/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx b/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx
index 54ae7e076..3aef87a65 100644
--- a/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx
+++ b/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx
@@ -2,14 +2,7 @@ import React, { useEffect, useState } from "react";
 import { X } from "@phosphor-icons/react";
 import Admin from "@/models/admin";
 
-const DIALOG_ID = `new-invite-modal`;
-
-function hideModal() {
-  document.getElementById(DIALOG_ID)?.close();
-}
-
-export const NewInviteModalId = DIALOG_ID;
-export default function NewInviteModal() {
+export default function NewInviteModal({ closeModal }) {
   const [invite, setInvite] = useState(null);
   const [error, setError] = useState(null);
   const [copied, setCopied] = useState(false);
@@ -39,74 +32,70 @@ export default function NewInviteModal() {
   }, [copied]);
 
   return (
-    <dialog id={DIALOG_ID} className="bg-transparent outline-none">
-      <div className="relative w-[500px] max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Create new invite
-            </h3>
-            <button
-              onClick={hideModal}
-              type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
-            >
-              <X className="text-gray-300 text-lg" />
-            </button>
-          </div>
-          <form onSubmit={handleCreate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-                {invite && (
-                  <input
-                    type="url"
-                    defaultValue={`${window.location.origin}/accept-invite/${invite.code}`}
-                    disabled={true}
-                    className="rounded-lg px-4 py-2 text-white bg-zinc-900 border border-gray-500/50"
-                  />
-                )}
-                <p className="text-white text-xs md:text-sm">
-                  After creation you will be able to copy the invite and send it
-                  to a new user where they can create an account as a default
-                  user.
-                </p>
-              </div>
+    <div className="relative w-[500px] max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Create new invite
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleCreate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+              {invite && (
+                <input
+                  type="url"
+                  defaultValue={`${window.location.origin}/accept-invite/${invite.code}`}
+                  disabled={true}
+                  className="rounded-lg px-4 py-2 text-white bg-zinc-900 border border-gray-500/50"
+                />
+              )}
+              <p className="text-white text-xs md:text-sm">
+                After creation you will be able to copy the invite and send it
+                to a new user where they can create an account as a default
+                user.
+              </p>
             </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              {!invite ? (
-                <>
-                  <button
-                    onClick={hideModal}
-                    type="button"
-                    className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-                  >
-                    Cancel
-                  </button>
-                  <button
-                    type="submit"
-                    className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-                  >
-                    Create Invite
-                  </button>
-                </>
-              ) : (
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
+            {!invite ? (
+              <>
                 <button
-                  onClick={copyInviteLink}
+                  onClick={closeModal}
                   type="button"
-                  disabled={copied}
-                  className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
+                  className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
                 >
-                  {copied ? "Copied Link" : "Copy Invite Link"}
+                  Cancel
                 </button>
-              )}
-            </div>
-          </form>
-        </div>
+                <button
+                  type="submit"
+                  className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
+                >
+                  Create Invite
+                </button>
+              </>
+            ) : (
+              <button
+                onClick={copyInviteLink}
+                type="button"
+                disabled={copied}
+                className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
+              >
+                {copied ? "Copied Link" : "Copy Invite Link"}
+              </button>
+            )}
+          </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Admin/Invitations/index.jsx b/frontend/src/pages/Admin/Invitations/index.jsx
index cf5b38d37..94b07f330 100644
--- a/frontend/src/pages/Admin/Invitations/index.jsx
+++ b/frontend/src/pages/Admin/Invitations/index.jsx
@@ -7,9 +7,12 @@ import { EnvelopeSimple } from "@phosphor-icons/react";
 import usePrefersDarkMode from "@/hooks/usePrefersDarkMode";
 import Admin from "@/models/admin";
 import InviteRow from "./InviteRow";
-import NewInviteModal, { NewInviteModalId } from "./NewInviteModal";
+import NewInviteModal from "./NewInviteModal";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function AdminInvites() {
+  const { isOpen, openModal, closeModal } = useModal();
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
       {!isMobile && <Sidebar />}
@@ -23,9 +26,7 @@ export default function AdminInvites() {
             <div className="items-center flex gap-x-4">
               <p className="text-2xl font-semibold text-white">Invitations</p>
               <button
-                onClick={() =>
-                  document?.getElementById(NewInviteModalId)?.showModal()
-                }
+                onClick={openModal}
                 className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
               >
                 <EnvelopeSimple className="h-4 w-4" /> Create Invite Link
@@ -38,7 +39,9 @@ export default function AdminInvites() {
           </div>
           <InvitationsContainer />
         </div>
-        <NewInviteModal />
+        <ModalWrapper isOpen={isOpen}>
+          <NewInviteModal closeModal={closeModal} />
+        </ModalWrapper>
       </div>
     </div>
   );
diff --git a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
index d8c26d1e3..9a7e2a6d0 100644
--- a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
+++ b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
@@ -4,14 +4,7 @@ import Admin from "@/models/admin";
 import { userFromStorage } from "@/utils/request";
 import { RoleHintDisplay } from "..";
 
-const DIALOG_ID = `new-user-modal`;
-
-function hideModal() {
-  document.getElementById(DIALOG_ID)?.close();
-}
-
-export const NewUserModalId = DIALOG_ID;
-export default function NewUserModal() {
+export default function NewUserModal({ closeModal }) {
   const [error, setError] = useState(null);
   const [role, setRole] = useState("default");
   const handleCreate = async (e) => {
@@ -28,107 +21,103 @@ export default function NewUserModal() {
   const user = userFromStorage();
 
   return (
-    <dialog id={DIALOG_ID} className="bg-transparent outline-none">
-      <div className="relative w-full max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Add user to instance
-            </h3>
+    <div className="relative w-full max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Add user to instance
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleCreate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              <div>
+                <label
+                  htmlFor="username"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Username
+                </label>
+                <input
+                  name="username"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="User's username"
+                  minLength={2}
+                  required={true}
+                  autoComplete="off"
+                />
+              </div>
+              <div>
+                <label
+                  htmlFor="password"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Password
+                </label>
+                <input
+                  name="password"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="User's initial password"
+                  required={true}
+                  autoComplete="off"
+                />
+              </div>
+              <div>
+                <label
+                  htmlFor="role"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Role
+                </label>
+                <select
+                  name="role"
+                  required={true}
+                  defaultValue={"default"}
+                  onChange={(e) => setRole(e.target.value)}
+                  className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
+                >
+                  <option value="default">Default</option>
+                  <option value="manager">Manager </option>
+                  {user?.role === "admin" && (
+                    <option value="admin">Administrator</option>
+                  )}
+                </select>
+                <RoleHintDisplay role={role} />
+              </div>
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+              <p className="text-white text-xs md:text-sm">
+                After creating a user they will need to login with their initial
+                login to get access.
+              </p>
+            </div>
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
             <button
-              onClick={hideModal}
+              onClick={closeModal}
               type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
+              className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
+            >
+              Cancel
+            </button>
+            <button
+              type="submit"
+              className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
             >
-              <X className="text-gray-300 text-lg" />
+              Add user
             </button>
           </div>
-          <form onSubmit={handleCreate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                <div>
-                  <label
-                    htmlFor="username"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Username
-                  </label>
-                  <input
-                    name="username"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="User's username"
-                    minLength={2}
-                    required={true}
-                    autoComplete="off"
-                  />
-                </div>
-                <div>
-                  <label
-                    htmlFor="password"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Password
-                  </label>
-                  <input
-                    name="password"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="User's initial password"
-                    required={true}
-                    autoComplete="off"
-                  />
-                </div>
-                <div>
-                  <label
-                    htmlFor="role"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Role
-                  </label>
-                  <select
-                    name="role"
-                    required={true}
-                    defaultValue={"default"}
-                    onChange={(e) => setRole(e.target.value)}
-                    className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
-                  >
-                    <option value="default">Default</option>
-                    <option value="manager">Manager </option>
-                    {user?.role === "admin" && (
-                      <option value="admin">Administrator</option>
-                    )}
-                  </select>
-                  <RoleHintDisplay role={role} />
-                </div>
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-                <p className="text-white text-xs md:text-sm">
-                  After creating a user they will need to login with their
-                  initial login to get access.
-                </p>
-              </div>
-            </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              <button
-                onClick={hideModal}
-                type="button"
-                className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-              >
-                Cancel
-              </button>
-              <button
-                type="submit"
-                className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-              >
-                Add user
-              </button>
-            </div>
-          </form>
-        </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
index 4c3c2686a..959b04b4c 100644
--- a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
+++ b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
@@ -3,16 +3,10 @@ import { X } from "@phosphor-icons/react";
 import Admin from "@/models/admin";
 import { RoleHintDisplay } from "../..";
 
-export const EditUserModalId = (user) => `edit-user-${user.id}-modal`;
-
-export default function EditUserModal({ currentUser, user }) {
+export default function EditUserModal({ currentUser, user, closeModal }) {
   const [role, setRole] = useState(user.role);
   const [error, setError] = useState(null);
 
-  const hideModal = () => {
-    document.getElementById(EditUserModalId(user)).close();
-  };
-
   const handleUpdate = async (e) => {
     setError(null);
     e.preventDefault();
@@ -28,103 +22,99 @@ export default function EditUserModal({ currentUser, user }) {
   };
 
   return (
-    <dialog id={EditUserModalId(user)} className="bg-transparent outline-none">
-      <div className="relative w-[500px] max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Edit {user.username}
-            </h3>
+    <div className="relative w-[500px] max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Edit {user.username}
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleUpdate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              <div>
+                <label
+                  htmlFor="username"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Username
+                </label>
+                <input
+                  name="username"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="User's username"
+                  minLength={2}
+                  defaultValue={user.username}
+                  required={true}
+                  autoComplete="off"
+                />
+              </div>
+              <div>
+                <label
+                  htmlFor="password"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  New Password
+                </label>
+                <input
+                  name="password"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder={`${user.username}'s new password`}
+                  autoComplete="off"
+                />
+              </div>
+              <div>
+                <label
+                  htmlFor="role"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Role
+                </label>
+                <select
+                  name="role"
+                  required={true}
+                  defaultValue={user.role}
+                  onChange={(e) => setRole(e.target.value)}
+                  className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
+                >
+                  <option value="default">Default</option>
+                  <option value="manager">Manager</option>
+                  {currentUser?.role === "admin" && (
+                    <option value="admin">Administrator</option>
+                  )}
+                </select>
+                <RoleHintDisplay role={role} />
+              </div>
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+            </div>
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
             <button
-              onClick={hideModal}
+              onClick={closeModal}
               type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
+              className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
+            >
+              Cancel
+            </button>
+            <button
+              type="submit"
+              className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
             >
-              <X className="text-gray-300 text-lg" />
+              Update user
             </button>
           </div>
-          <form onSubmit={handleUpdate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                <div>
-                  <label
-                    htmlFor="username"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Username
-                  </label>
-                  <input
-                    name="username"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="User's username"
-                    minLength={2}
-                    defaultValue={user.username}
-                    required={true}
-                    autoComplete="off"
-                  />
-                </div>
-                <div>
-                  <label
-                    htmlFor="password"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    New Password
-                  </label>
-                  <input
-                    name="password"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder={`${user.username}'s new password`}
-                    autoComplete="off"
-                  />
-                </div>
-                <div>
-                  <label
-                    htmlFor="role"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Role
-                  </label>
-                  <select
-                    name="role"
-                    required={true}
-                    defaultValue={user.role}
-                    onChange={(e) => setRole(e.target.value)}
-                    className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
-                  >
-                    <option value="default">Default</option>
-                    <option value="manager">Manager</option>
-                    {currentUser?.role === "admin" && (
-                      <option value="admin">Administrator</option>
-                    )}
-                  </select>
-                  <RoleHintDisplay role={role} />
-                </div>
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-              </div>
-            </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              <button
-                onClick={hideModal}
-                type="button"
-                className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-              >
-                Cancel
-              </button>
-              <button
-                type="submit"
-                className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-              >
-                Update user
-              </button>
-            </div>
-          </form>
-        </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Admin/Users/UserRow/index.jsx b/frontend/src/pages/Admin/Users/UserRow/index.jsx
index cd1c47732..c58b8124b 100644
--- a/frontend/src/pages/Admin/Users/UserRow/index.jsx
+++ b/frontend/src/pages/Admin/Users/UserRow/index.jsx
@@ -1,9 +1,11 @@
 import { useRef, useState } from "react";
 import { titleCase } from "text-case";
 import Admin from "@/models/admin";
-import EditUserModal, { EditUserModalId } from "./EditUserModal";
+import EditUserModal from "./EditUserModal";
 import { DotsThreeOutline } from "@phosphor-icons/react";
 import showToast from "@/utils/toast";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 const ModMap = {
   admin: ["admin", "manager", "default"],
@@ -15,6 +17,7 @@ export default function UserRow({ currUser, user }) {
   const rowRef = useRef(null);
   const canModify = ModMap[currUser?.role || "default"].includes(user.role);
   const [suspended, setSuspended] = useState(user.suspended === 1);
+  const { isOpen, openModal, closeModal } = useModal();
   const handleSuspend = async () => {
     if (
       !window.confirm(
@@ -65,9 +68,7 @@ export default function UserRow({ currUser, user }) {
         <td className="px-6 py-4 flex items-center gap-x-6">
           {canModify && (
             <button
-              onClick={() =>
-                document?.getElementById(EditUserModalId(user))?.showModal()
-              }
+              onClick={openModal}
               className="font-medium text-white text-opacity-80 rounded-lg hover:text-white px-2 py-1 hover:text-opacity-60 hover:bg-white hover:bg-opacity-10"
             >
               <DotsThreeOutline weight="fill" className="h-5 w-5" />
@@ -91,7 +92,13 @@ export default function UserRow({ currUser, user }) {
           )}
         </td>
       </tr>
-      <EditUserModal currentUser={currUser} user={user} />
+      <ModalWrapper isOpen={isOpen}>
+        <EditUserModal
+          currentUser={currUser}
+          user={user}
+          closeModal={closeModal}
+        />
+      </ModalWrapper>
     </>
   );
 }
diff --git a/frontend/src/pages/Admin/Users/index.jsx b/frontend/src/pages/Admin/Users/index.jsx
index fb8f50080..7c9699483 100644
--- a/frontend/src/pages/Admin/Users/index.jsx
+++ b/frontend/src/pages/Admin/Users/index.jsx
@@ -7,9 +7,12 @@ import { UserPlus } from "@phosphor-icons/react";
 import Admin from "@/models/admin";
 import UserRow from "./UserRow";
 import useUser from "@/hooks/useUser";
-import NewUserModal, { NewUserModalId } from "./NewUserModal";
+import NewUserModal from "./NewUserModal";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function AdminUsers() {
+  const { isOpen, openModal, closeModal } = useModal();
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
       {!isMobile && <Sidebar />}
@@ -23,9 +26,7 @@ export default function AdminUsers() {
             <div className="items-center flex gap-x-4">
               <p className="text-2xl font-semibold text-white">Users</p>
               <button
-                onClick={() =>
-                  document?.getElementById(NewUserModalId)?.showModal()
-                }
+                onClick={openModal}
                 className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
               >
                 <UserPlus className="h-4 w-4" /> Add user
@@ -39,7 +40,9 @@ export default function AdminUsers() {
           </div>
           <UsersContainer />
         </div>
-        <NewUserModal />
+        <ModalWrapper isOpen={isOpen}>
+          <NewUserModal closeModal={closeModal} />
+        </ModalWrapper>
       </div>
     </div>
   );
diff --git a/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx b/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx
index 21d9faed4..5179b3fb7 100644
--- a/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx
+++ b/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx
@@ -1,14 +1,8 @@
 import React, { useState } from "react";
 import { X } from "@phosphor-icons/react";
 import Admin from "@/models/admin";
-const DIALOG_ID = `new-workspace-modal`;
 
-function hideModal() {
-  document.getElementById(DIALOG_ID)?.close();
-}
-
-export const NewWorkspaceModalId = DIALOG_ID;
-export default function NewWorkspaceModal() {
+export default function NewWorkspaceModal({ closeModal }) {
   const [error, setError] = useState(null);
   const handleCreate = async (e) => {
     setError(null);
@@ -20,69 +14,65 @@ export default function NewWorkspaceModal() {
   };
 
   return (
-    <dialog id={DIALOG_ID} className="bg-transparent outline-none">
-      <div className="relative w-[500px] max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
-            <h3 className="text-xl font-semibold text-white">
-              Create new workspace
-            </h3>
+    <div className="relative w-[500px] max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
+          <h3 className="text-xl font-semibold text-white">
+            Create new workspace
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleCreate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              <div>
+                <label
+                  htmlFor="name"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Workspace name
+                </label>
+                <input
+                  name="name"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="My workspace"
+                  minLength={4}
+                  required={true}
+                  autoComplete="off"
+                />
+              </div>
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+              <p className="text-white text-opacity-60 text-xs md:text-sm">
+                After creating this workspace only admins will be able to see
+                it. You can add users after it has been created.
+              </p>
+            </div>
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-600">
             <button
-              onClick={hideModal}
+              onClick={closeModal}
               type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
+              className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
             >
-              <X className="text-gray-300 text-lg" />
+              Cancel
+            </button>
+            <button
+              type="submit"
+              className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
+            >
+              Create workspace
             </button>
           </div>
-          <form onSubmit={handleCreate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                <div>
-                  <label
-                    htmlFor="name"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Workspace name
-                  </label>
-                  <input
-                    name="name"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="My workspace"
-                    minLength={4}
-                    required={true}
-                    autoComplete="off"
-                  />
-                </div>
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-                <p className="text-white text-opacity-60 text-xs md:text-sm">
-                  After creating this workspace only admins will be able to see
-                  it. You can add users after it has been created.
-                </p>
-              </div>
-            </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-600">
-              <button
-                onClick={hideModal}
-                type="button"
-                className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-              >
-                Cancel
-              </button>
-              <button
-                type="submit"
-                className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-              >
-                Create workspace
-              </button>
-            </div>
-          </form>
-        </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Admin/Workspaces/WorkspaceRow/EditWorkspaceUsersModal/index.jsx b/frontend/src/pages/Admin/Workspaces/WorkspaceRow/EditWorkspaceUsersModal/index.jsx
index 052f845b4..cd8d5f011 100644
--- a/frontend/src/pages/Admin/Workspaces/WorkspaceRow/EditWorkspaceUsersModal/index.jsx
+++ b/frontend/src/pages/Admin/Workspaces/WorkspaceRow/EditWorkspaceUsersModal/index.jsx
@@ -6,13 +6,13 @@ import { titleCase } from "text-case";
 export const EditWorkspaceUsersModalId = (workspace) =>
   `edit-workspace-${workspace.id}-modal`;
 
-export default function EditWorkspaceUsersModal({ workspace, users }) {
+export default function EditWorkspaceUsersModal({
+  workspace,
+  users,
+  closeModal,
+}) {
   const [error, setError] = useState(null);
 
-  const hideModal = () => {
-    document.getElementById(EditWorkspaceUsersModalId(workspace)).close();
-  };
-
   const handleUpdate = async (e) => {
     setError(null);
     e.preventDefault();
@@ -35,122 +35,115 @@ export default function EditWorkspaceUsersModal({ workspace, users }) {
   };
 
   return (
-    <dialog
-      id={EditWorkspaceUsersModalId(workspace)}
-      className="bg-transparent outline-none"
-    >
-      <div className="relative w-[500px] max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Edit {workspace.name}
-            </h3>
+    <div className="relative w-[500px] max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Edit {workspace.name}
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleUpdate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              {users
+                .filter((user) => user.role !== "admin")
+                .map((user) => {
+                  return (
+                    <div
+                      key={`workspace-${workspace.id}-user-${user.id}`}
+                      data-workspace={workspace.id}
+                      className="flex items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
+                      onClick={() => {
+                        document
+                          .getElementById(
+                            `workspace-${workspace.id}-user-${user.id}`
+                          )
+                          ?.click();
+                      }}
+                    >
+                      <input
+                        id={`workspace-${workspace.id}-user-${user.id}`}
+                        defaultChecked={workspace.userIds.includes(user.id)}
+                        type="checkbox"
+                        value="yes"
+                        name={`user-${user.id}`}
+                        className="w-4 h-4 text-blue-600 bg-zinc-900 border border-gray-500/50 rounded focus:ring-blue-500 focus:border-blue-500 pointer-events-none"
+                      />
+                      <label
+                        htmlFor={`user-${user.id}`}
+                        className="pointer-events-none w-full py-4 ml-2 text-sm font-medium text-white"
+                      >
+                        {titleCase(user.username)}
+                      </label>
+                    </div>
+                  );
+                })}
+              <div className="flex items-center gap-x-4">
+                <button
+                  type="button"
+                  className="w-full p-4 flex text-white items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
+                  onClick={() => {
+                    document
+                      .getElementById(`workspace-${workspace.id}-select-all`)
+                      ?.click();
+                    Array.from(
+                      document.querySelectorAll(
+                        `[data-workspace='${workspace.id}']`
+                      )
+                    ).forEach((el) => {
+                      if (!el.firstChild.checked) el.firstChild.click();
+                    });
+                  }}
+                >
+                  Select All
+                </button>
+                <button
+                  type="button"
+                  className="w-full p-4 flex text-white items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
+                  onClick={() => {
+                    document
+                      .getElementById(`workspace-${workspace.id}-select-all`)
+                      ?.click();
+                    Array.from(
+                      document.querySelectorAll(
+                        `[data-workspace='${workspace.id}']`
+                      )
+                    ).forEach((el) => {
+                      if (el.firstChild.checked) el.firstChild.click();
+                    });
+                  }}
+                >
+                  Deselect All
+                </button>
+              </div>
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+            </div>
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
             <button
-              onClick={hideModal}
+              onClick={closeModal}
               type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
+              className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
             >
-              <X className="text-gray-300 text-lg" />
+              Cancel
+            </button>
+            <button
+              type="submit"
+              className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
+            >
+              Update workspace
             </button>
           </div>
-          <form onSubmit={handleUpdate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                {users
-                  .filter((user) => user.role !== "admin")
-                  .map((user) => {
-                    return (
-                      <div
-                        key={`workspace-${workspace.id}-user-${user.id}`}
-                        data-workspace={workspace.id}
-                        className="flex items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
-                        onClick={() => {
-                          document
-                            .getElementById(
-                              `workspace-${workspace.id}-user-${user.id}`
-                            )
-                            ?.click();
-                        }}
-                      >
-                        <input
-                          id={`workspace-${workspace.id}-user-${user.id}`}
-                          defaultChecked={workspace.userIds.includes(user.id)}
-                          type="checkbox"
-                          value="yes"
-                          name={`user-${user.id}`}
-                          className="w-4 h-4 text-blue-600 bg-zinc-900 border border-gray-500/50 rounded focus:ring-blue-500 focus:border-blue-500 pointer-events-none"
-                        />
-                        <label
-                          htmlFor={`user-${user.id}`}
-                          className="pointer-events-none w-full py-4 ml-2 text-sm font-medium text-white"
-                        >
-                          {titleCase(user.username)}
-                        </label>
-                      </div>
-                    );
-                  })}
-                <div className="flex items-center gap-x-4">
-                  <button
-                    type="button"
-                    className="w-full p-4 flex text-white items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
-                    onClick={() => {
-                      document
-                        .getElementById(`workspace-${workspace.id}-select-all`)
-                        ?.click();
-                      Array.from(
-                        document.querySelectorAll(
-                          `[data-workspace='${workspace.id}']`
-                        )
-                      ).forEach((el) => {
-                        if (!el.firstChild.checked) el.firstChild.click();
-                      });
-                    }}
-                  >
-                    Select All
-                  </button>
-                  <button
-                    type="button"
-                    className="w-full p-4 flex text-white items-center pl-4 border border-gray-500/50 rounded group hover:bg-stone-900 transition-all duration-300 cursor-pointer"
-                    onClick={() => {
-                      document
-                        .getElementById(`workspace-${workspace.id}-select-all`)
-                        ?.click();
-                      Array.from(
-                        document.querySelectorAll(
-                          `[data-workspace='${workspace.id}']`
-                        )
-                      ).forEach((el) => {
-                        if (el.firstChild.checked) el.firstChild.click();
-                      });
-                    }}
-                  >
-                    Deselect All
-                  </button>
-                </div>
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-              </div>
-            </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              <button
-                onClick={hideModal}
-                type="button"
-                className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-              >
-                Cancel
-              </button>
-              <button
-                type="submit"
-                className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-              >
-                Update workspace
-              </button>
-            </div>
-          </form>
-        </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Admin/Workspaces/WorkspaceRow/index.jsx b/frontend/src/pages/Admin/Workspaces/WorkspaceRow/index.jsx
index 8c8969622..e755e1857 100644
--- a/frontend/src/pages/Admin/Workspaces/WorkspaceRow/index.jsx
+++ b/frontend/src/pages/Admin/Workspaces/WorkspaceRow/index.jsx
@@ -1,13 +1,14 @@
 import { useRef } from "react";
 import Admin from "@/models/admin";
 import paths from "@/utils/paths";
-import EditWorkspaceUsersModal, {
-  EditWorkspaceUsersModalId,
-} from "./EditWorkspaceUsersModal";
+import EditWorkspaceUsersModal from "./EditWorkspaceUsersModal";
 import { DotsThreeOutline, LinkSimple, Trash } from "@phosphor-icons/react";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function WorkspaceRow({ workspace, users }) {
   const rowRef = useRef(null);
+  const { isOpen, openModal, closeModal } = useModal();
   const handleDelete = async () => {
     if (
       !window.confirm(
@@ -32,6 +33,7 @@ export default function WorkspaceRow({ workspace, users }) {
           <a
             href={paths.workspace.chat(workspace.slug)}
             target="_blank"
+            rel="noreferrer"
             className="text-white flex items-center hover:underline"
           >
             <LinkSimple className="mr-2 w-5 h-5" /> {workspace.slug}
@@ -41,11 +43,7 @@ export default function WorkspaceRow({ workspace, users }) {
         <td className="px-6 py-4">{workspace.createdAt}</td>
         <td className="px-6 py-4 flex items-center gap-x-6">
           <button
-            onClick={() =>
-              document
-                ?.getElementById(EditWorkspaceUsersModalId(workspace))
-                ?.showModal()
-            }
+            onClick={openModal}
             className="font-medium rounded-lg hover:text-white hover:text-opacity-60 px-2 py-1 hover:bg-white hover:bg-opacity-10"
           >
             <DotsThreeOutline weight="fill" className="h-5 w-5" />
@@ -58,7 +56,13 @@ export default function WorkspaceRow({ workspace, users }) {
           </button>
         </td>
       </tr>
-      <EditWorkspaceUsersModal workspace={workspace} users={users} />
+      <ModalWrapper isOpen={isOpen}>
+        <EditWorkspaceUsersModal
+          workspace={workspace}
+          users={users}
+          closeModal={closeModal}
+        />
+      </ModalWrapper>
     </>
   );
 }
diff --git a/frontend/src/pages/Admin/Workspaces/index.jsx b/frontend/src/pages/Admin/Workspaces/index.jsx
index c29c92ac2..8ca7410b0 100644
--- a/frontend/src/pages/Admin/Workspaces/index.jsx
+++ b/frontend/src/pages/Admin/Workspaces/index.jsx
@@ -7,9 +7,12 @@ import { BookOpen } from "@phosphor-icons/react";
 import usePrefersDarkMode from "@/hooks/usePrefersDarkMode";
 import Admin from "@/models/admin";
 import WorkspaceRow from "./WorkspaceRow";
-import NewWorkspaceModal, { NewWorkspaceModalId } from "./NewWorkspaceModal";
+import NewWorkspaceModal from "./NewWorkspaceModal";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function AdminWorkspaces() {
+  const { isOpen, openModal, closeModal } = useModal();
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
       {!isMobile && <Sidebar />}
@@ -25,9 +28,7 @@ export default function AdminWorkspaces() {
                 Instance workspaces
               </p>
               <button
-                onClick={() =>
-                  document?.getElementById(NewWorkspaceModalId)?.showModal()
-                }
+                onClick={openModal}
                 className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
               >
                 <BookOpen className="h-4 w-4" /> New Workspace
@@ -40,7 +41,9 @@ export default function AdminWorkspaces() {
           </div>
           <WorkspacesContainer />
         </div>
-        <NewWorkspaceModal />
+        <ModalWrapper isOpen={isOpen}>
+          <NewWorkspaceModal closeModal={closeModal} />
+        </ModalWrapper>
       </div>
     </div>
   );
diff --git a/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx b/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx
index a4095ae9c..cde8bdbe6 100644
--- a/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx
+++ b/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx
@@ -5,14 +5,7 @@ import paths from "@/utils/paths";
 import { userFromStorage } from "@/utils/request";
 import System from "@/models/system";
 
-const DIALOG_ID = `new-api-key-modal`;
-
-function hideModal() {
-  document.getElementById(DIALOG_ID)?.close();
-}
-
-export const NewApiKeyModalId = DIALOG_ID;
-export default function NewApiKeyModal() {
+export default function NewApiKeyModal({ closeModal }) {
   const [apiKey, setApiKey] = useState(null);
   const [error, setError] = useState(null);
   const [copied, setCopied] = useState(false);
@@ -43,80 +36,77 @@ export default function NewApiKeyModal() {
   }, [copied]);
 
   return (
-    <dialog id={DIALOG_ID} className="bg-transparent outline-none">
-      <div className="relative w-[500px] max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Create new API key
-            </h3>
-            <button
-              onClick={hideModal}
-              type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
-            >
-              <X className="text-gray-300 text-lg" />
-            </button>
-          </div>
-          <form onSubmit={handleCreate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-                {apiKey && (
-                  <input
-                    type="text"
-                    defaultValue={`${apiKey.secret}`}
-                    disabled={true}
-                    className="rounded-lg px-4 py-2 text-white bg-zinc-900 border border-gray-500/50"
-                  />
-                )}
-                <p className="text-white text-xs md:text-sm">
-                  Once created the API key can be used to programmatically
-                  access and configure this AnythingLLM instance.
-                </p>
-                <a
-                  href={paths.apiDocs()}
-                  target="_blank"
-                  className="text-blue-400 hover:underline"
-                >
-                  Read the API documentation &rarr;
-                </a>
-              </div>
+    <div className="relative w-[500px] max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Create new API key
+          </h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+            data-modal-hide="staticModal"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <form onSubmit={handleCreate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+              {apiKey && (
+                <input
+                  type="text"
+                  defaultValue={`${apiKey.secret}`}
+                  disabled={true}
+                  className="rounded-lg px-4 py-2 text-white bg-zinc-900 border border-gray-500/50"
+                />
+              )}
+              <p className="text-white text-xs md:text-sm">
+                Once created the API key can be used to programmatically access
+                and configure this AnythingLLM instance.
+              </p>
+              <a
+                href={paths.apiDocs()}
+                target="_blank"
+                rel="noreferrer"
+                className="text-blue-400 hover:underline"
+              >
+                Read the API documentation &rarr;
+              </a>
             </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              {!apiKey ? (
-                <>
-                  <button
-                    onClick={hideModal}
-                    type="button"
-                    className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
-                  >
-                    Cancel
-                  </button>
-                  <button
-                    type="submit"
-                    className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
-                  >
-                    Create API key
-                  </button>
-                </>
-              ) : (
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
+            {!apiKey ? (
+              <>
                 <button
-                  onClick={copyApiKey}
+                  onClick={closeModal}
                   type="button"
-                  disabled={copied}
-                  className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
+                  className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
                 >
-                  {copied ? "Copied API key" : "Copy API key"}
+                  Cancel
                 </button>
-              )}
-            </div>
-          </form>
-        </div>
+                <button
+                  type="submit"
+                  className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
+                >
+                  Create API key
+                </button>
+              </>
+            ) : (
+              <button
+                onClick={copyApiKey}
+                type="button"
+                disabled={copied}
+                className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
+              >
+                {copied ? "Copied API key" : "Copy API key"}
+              </button>
+            )}
+          </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx b/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx
index 5ee5fd0a3..bf4ac51ef 100644
--- a/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx
+++ b/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx
@@ -6,12 +6,15 @@ import "react-loading-skeleton/dist/skeleton.css";
 import { PlusCircle } from "@phosphor-icons/react";
 import Admin from "@/models/admin";
 import ApiKeyRow from "./ApiKeyRow";
-import NewApiKeyModal, { NewApiKeyModalId } from "./NewApiKeyModal";
+import NewApiKeyModal from "./NewApiKeyModal";
 import paths from "@/utils/paths";
 import { userFromStorage } from "@/utils/request";
 import System from "@/models/system";
+import ModalWrapper from "@/components/ModalWrapper";
+import { useModal } from "@/hooks/useModal";
 
 export default function AdminApiKeys() {
+  const { isOpen, openModal, closeModal } = useModal();
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
       {!isMobile && <Sidebar />}
@@ -25,9 +28,7 @@ export default function AdminApiKeys() {
             <div className="items-center flex gap-x-4">
               <p className="text-2xl font-semibold text-white">API Keys</p>
               <button
-                onClick={() =>
-                  document?.getElementById(NewApiKeyModalId)?.showModal()
-                }
+                onClick={openModal}
                 className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
               >
                 <PlusCircle className="h-4 w-4" /> Generate New API Key
@@ -40,6 +41,7 @@ export default function AdminApiKeys() {
             <a
               href={paths.apiDocs()}
               target="_blank"
+              rel="noreferrer"
               className="text-sm font-base text-blue-300 hover:underline"
             >
               Read the API documentation &rarr;
@@ -47,7 +49,9 @@ export default function AdminApiKeys() {
           </div>
           <ApiKeysContainer />
         </div>
-        <NewApiKeyModal />
+        <ModalWrapper isOpen={isOpen}>
+          <NewApiKeyModal closeModal={closeModal} />
+        </ModalWrapper>
       </div>
     </div>
   );
diff --git a/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx b/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
index 23fc39d9a..3056e7659 100644
--- a/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
+++ b/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
@@ -2,9 +2,22 @@ import { useRef } from "react";
 import truncate from "truncate";
 import { X, Trash } from "@phosphor-icons/react";
 import System from "@/models/system";
+import ModalWrapper from "@/components/ModalWrapper";
+import { useModal } from "@/hooks/useModal";
 
 export default function ChatRow({ chat }) {
   const rowRef = useRef(null);
+  const {
+    isOpen: isPromptOpen,
+    openModal: openPromptModal,
+    closeModal: closePromptModal,
+  } = useModal();
+  const {
+    isOpen: isResponseOpen,
+    openModal: openResponseModal,
+    closeModal: closeResponseModal,
+  } = useModal();
+
   const handleDelete = async () => {
     if (
       !window.confirm(
@@ -30,17 +43,13 @@ export default function ChatRow({ chat }) {
         </td>
         <td className="px-6 py-4">{chat.workspace?.name}</td>
         <td
-          onClick={() => {
-            document.getElementById(`chat-${chat.id}-prompt`)?.showModal();
-          }}
+          onClick={openPromptModal}
           className="px-6 py-4 border-transparent cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
         >
           {truncate(chat.prompt, 40)}
         </td>
         <td
-          onClick={() => {
-            document.getElementById(`chat-${chat.id}-response`)?.showModal();
-          }}
+          onClick={openResponseModal}
           className="px-6 py-4 cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
         >
           {truncate(JSON.parse(chat.response)?.text, 40)}
@@ -55,42 +64,38 @@ export default function ChatRow({ chat }) {
           </button>
         </td>
       </tr>
-      <TextPreview text={chat.prompt} modalName={`chat-${chat.id}-prompt`} />
-      <TextPreview
-        text={JSON.parse(chat.response)?.text}
-        modalName={`chat-${chat.id}-response`}
-      />
+      <ModalWrapper isOpen={isPromptOpen}>
+        <TextPreview text={chat.prompt} closeModal={closePromptModal} />
+      </ModalWrapper>
+      <ModalWrapper isOpen={isResponseOpen}>
+        <TextPreview
+          text={JSON.parse(chat.response)?.text}
+          closeModal={closeResponseModal}
+        />
+      </ModalWrapper>
     </>
   );
 }
-
-function hideModal(modalName) {
-  document.getElementById(modalName)?.close();
-}
-
-const TextPreview = ({ text, modalName }) => {
+const TextPreview = ({ text, closeModal }) => {
   return (
-    <dialog id={modalName} className="bg-transparent outline-none w-full">
-      <div className="relative w-full md:max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
-            <h3 className="text-xl font-semibold text-white">Viewing Text</h3>
-            <button
-              onClick={() => hideModal(modalName)}
-              type="button"
-              className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
-              data-modal-hide="staticModal"
-            >
-              <X className="text-gray-300 text-lg" />
-            </button>
-          </div>
-          <div className="w-full p-6">
-            <pre className="w-full h-[200px] py-2 px-4 whitespace-pre-line overflow-auto rounded-lg bg-zinc-900 border border-gray-500 text-white text-sm">
-              {text}
-            </pre>
-          </div>
+    <div className="relative w-full md:max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
+          <h3 className="text-xl font-semibold text-white">Viewing Text</h3>
+          <button
+            onClick={closeModal}
+            type="button"
+            className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
+          >
+            <X className="text-gray-300 text-lg" />
+          </button>
+        </div>
+        <div className="w-full p-6">
+          <pre className="w-full h-[200px] py-2 px-4 whitespace-pre-line overflow-auto rounded-lg bg-zinc-900 border border-gray-500 text-white text-sm">
+            {text}
+          </pre>
         </div>
       </div>
-    </dialog>
+    </div>
   );
 };
diff --git a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx
index ddcb81319..c6c62fef7 100644
--- a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx
+++ b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx
@@ -15,6 +15,8 @@ import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions";
 import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions";
 import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem";
 import { MagnifyingGlass } from "@phosphor-icons/react";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function GeneralEmbeddingPreference() {
   const [saving, setSaving] = useState(false);
@@ -25,6 +27,7 @@ export default function GeneralEmbeddingPreference() {
   const [searchQuery, setSearchQuery] = useState("");
   const [filteredEmbedders, setFilteredEmbedders] = useState([]);
   const [selectedEmbedder, setSelectedEmbedder] = useState(null);
+  const { isOpen, openModal, closeModal } = useModal();
 
   const handleSubmit = async (e) => {
     e.preventDefault();
@@ -33,7 +36,7 @@ export default function GeneralEmbeddingPreference() {
       hasChanges &&
       hasEmbeddings
     ) {
-      document.getElementById("confirmation-modal")?.showModal();
+      openModal();
     } else {
       await handleSaveSettings();
     }
@@ -56,7 +59,7 @@ export default function GeneralEmbeddingPreference() {
       setHasChanges(false);
     }
     setSaving(false);
-    document.getElementById("confirmation-modal")?.close();
+    closeModal();
   };
 
   const updateChoice = (selection) => {
@@ -116,11 +119,13 @@ export default function GeneralEmbeddingPreference() {
 
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
-      <ChangeWarningModal
-        warningText=" Switching the embedder may affect previously embedded documents and future similarity search results."
-        onClose={() => document.getElementById("confirmation-modal")?.close()}
-        onConfirm={handleSaveSettings}
-      />
+      <ModalWrapper isOpen={isOpen}>
+        <ChangeWarningModal
+          warningText="Switching the vector database will ignore previously embedded documents and future similarity search results. They will need to be re-added to each workspace."
+          onClose={closeModal}
+          onConfirm={handleSaveSettings}
+        />
+      </ModalWrapper>
       {!isMobile && <Sidebar />}
       {loading ? (
         <div
diff --git a/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx b/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx
index 02887b86a..f5f697a72 100644
--- a/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx
+++ b/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx
@@ -21,6 +21,8 @@ import WeaviateDBOptions from "@/components/VectorDBSelection/WeaviateDBOptions"
 import VectorDBItem from "@/components/VectorDBSelection/VectorDBItem";
 import MilvusDBOptions from "@/components/VectorDBSelection/MilvusDBOptions";
 import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
+import { useModal } from "@/hooks/useModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function GeneralVectorDatabase() {
   const [saving, setSaving] = useState(false);
@@ -31,6 +33,7 @@ export default function GeneralVectorDatabase() {
   const [searchQuery, setSearchQuery] = useState("");
   const [filteredVDBs, setFilteredVDBs] = useState([]);
   const [selectedVDB, setSelectedVDB] = useState(null);
+  const { isOpen, openModal, closeModal } = useModal();
 
   useEffect(() => {
     async function fetchKeys() {
@@ -107,7 +110,7 @@ export default function GeneralVectorDatabase() {
   const handleSubmit = async (e) => {
     e.preventDefault();
     if (selectedVDB !== settings?.VectorDB && hasChanges && hasEmbeddings) {
-      document.getElementById("confirmation-modal")?.showModal();
+      openModal();
     } else {
       await handleSaveSettings();
     }
@@ -130,7 +133,7 @@ export default function GeneralVectorDatabase() {
       setHasChanges(false);
     }
     setSaving(false);
-    document.getElementById("confirmation-modal")?.close();
+    closeModal();
   };
 
   useEffect(() => {
@@ -142,11 +145,13 @@ export default function GeneralVectorDatabase() {
 
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex">
-      <ChangeWarningModal
-        warningText="Switching the vector database will ignore previously embedded documents and future similarity search results. They will need to be re-added to each workspace."
-        onClose={() => document.getElementById("confirmation-modal")?.close()}
-        onConfirm={handleSaveSettings}
-      />
+      <ModalWrapper isOpen={isOpen}>
+        <ChangeWarningModal
+          warningText="Switching the vector database will ignore previously embedded documents and future similarity search results. They will need to be re-added to each workspace."
+          onClose={closeModal}
+          onConfirm={handleSaveSettings}
+        />
+      </ModalWrapper>
       {!isMobile && <Sidebar />}
       {loading ? (
         <div
diff --git a/frontend/src/pages/Invite/NewUserModal/index.jsx b/frontend/src/pages/Invite/NewUserModal/index.jsx
index 0733a3602..6a84792f3 100644
--- a/frontend/src/pages/Invite/NewUserModal/index.jsx
+++ b/frontend/src/pages/Invite/NewUserModal/index.jsx
@@ -31,71 +31,67 @@ export default function NewUserModal() {
   };
 
   return (
-    <dialog open={true} className="bg-transparent outline-none">
-      <div className="relative w-full max-w-2xl max-h-full">
-        <div className="relative bg-main-gradient rounded-lg shadow">
-          <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
-            <h3 className="text-xl font-semibold text-white">
-              Create a new account
-            </h3>
-          </div>
-          <form onSubmit={handleCreate}>
-            <div className="p-6 space-y-6 flex h-full w-full">
-              <div className="w-full flex flex-col gap-y-4">
-                <div>
-                  <label
-                    htmlFor="username"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Username
-                  </label>
-                  <input
-                    name="username"
-                    type="text"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="My username"
-                    minLength={2}
-                    required={true}
-                    autoComplete="off"
-                  />
-                </div>
-                <div>
-                  <label
-                    htmlFor="password"
-                    className="block mb-2 text-sm font-medium text-white"
-                  >
-                    Password
-                  </label>
-                  <input
-                    name="password"
-                    type="password"
-                    className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
-                    placeholder="Your password"
-                    required={true}
-                    minLength={8}
-                    autoComplete="off"
-                  />
-                </div>
-                {error && (
-                  <p className="text-red-400 text-sm">Error: {error}</p>
-                )}
-                <p className="text-slate-200 text-xs md:text-sm">
-                  After creating your account you will be able to login with
-                  these credentials and start using workspaces.
-                </p>
+    <div className="relative w-full max-w-2xl max-h-full">
+      <div className="relative bg-main-gradient rounded-lg shadow">
+        <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
+          <h3 className="text-xl font-semibold text-white">
+            Create a new account
+          </h3>
+        </div>
+        <form onSubmit={handleCreate}>
+          <div className="p-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              <div>
+                <label
+                  htmlFor="username"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Username
+                </label>
+                <input
+                  name="username"
+                  type="text"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="My username"
+                  minLength={2}
+                  required={true}
+                  autoComplete="off"
+                />
               </div>
+              <div>
+                <label
+                  htmlFor="password"
+                  className="block mb-2 text-sm font-medium text-white"
+                >
+                  Password
+                </label>
+                <input
+                  name="password"
+                  type="password"
+                  className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+                  placeholder="Your password"
+                  required={true}
+                  minLength={8}
+                  autoComplete="off"
+                />
+              </div>
+              {error && <p className="text-red-400 text-sm">Error: {error}</p>}
+              <p className="text-slate-200 text-xs md:text-sm">
+                After creating your account you will be able to login with these
+                credentials and start using workspaces.
+              </p>
             </div>
-            <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
-              <button
-                type="submit"
-                className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
-              >
-                Accept Invitation
-              </button>
-            </div>
-          </form>
-        </div>
+          </div>
+          <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
+            <button
+              type="submit"
+              className="w-full transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800 text-center justify-center"
+            >
+              Accept Invitation
+            </button>
+          </div>
+        </form>
       </div>
-    </dialog>
+    </div>
   );
 }
diff --git a/frontend/src/pages/Invite/index.jsx b/frontend/src/pages/Invite/index.jsx
index e44ff2359..a7522a54e 100644
--- a/frontend/src/pages/Invite/index.jsx
+++ b/frontend/src/pages/Invite/index.jsx
@@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
 import { FullScreenLoader } from "@/components/Preloader";
 import Invite from "@/models/invite";
 import NewUserModal from "./NewUserModal";
+import ModalWrapper from "@/components/ModalWrapper";
 
 export default function InvitePage() {
   const { code } = useParams();
@@ -47,7 +48,9 @@ export default function InvitePage() {
 
   return (
     <div className="w-screen h-screen overflow-hidden bg-sidebar flex items-center justify-center">
-      <NewUserModal />
+      <ModalWrapper isOpen={true}>
+        <NewUserModal />
+      </ModalWrapper>
     </div>
   );
 }
-- 
GitLab