diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 042fde70..2b8a645b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,7 +1,10 @@ import React, { lazy, Suspense } from "react"; import { Routes, Route } from "react-router-dom"; import { ContextWrapper } from "./AuthContext"; -import PrivateRoute, { AdminRoute } from "./components/PrivateRoute"; +import PrivateRoute, { + AdminRoute, + ManagerRoute, +} from "./components/PrivateRoute"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import Login from "./pages/Login"; @@ -48,56 +51,55 @@ export default function App() { /> } /> - {/* General Routes */} + {/* Admin */} } + path="/settings/llm-preference" + element={} /> } + path="/settings/embedding-preference" + element={} /> } + path="/settings/vector-database" + element={} + /> + {/* Manager */} + } /> } + path="/settings/security" + element={} /> } + path="/settings/appearance" + element={} /> } + path="/settings/api-keys" + element={} /> } + path="/settings/workspace-chats" + element={} /> } - /> - - {/* Admin Routes */} - } + path="/settings/system-preferences" + element={} /> } + path="/settings/invites" + element={} /> } + path="/settings/users" + element={} /> } + path="/settings/workspaces" + element={} /> {/* Onboarding Flow */} } /> diff --git a/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx b/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx index 52094105..454ab5f9 100644 --- a/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx @@ -14,7 +14,7 @@ export default function AnthropicAiOptions({ settings, showAlert = false }) {

Manage embedding → diff --git a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx index 1f00c070..883b6e8e 100644 --- a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx +++ b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx @@ -14,7 +14,7 @@ export default function LMStudioOptions({ settings, showAlert = false }) {

Manage embedding → diff --git a/frontend/src/components/Modals/MangeWorkspace/index.jsx b/frontend/src/components/Modals/MangeWorkspace/index.jsx index 7f9d920b..d38cc353 100644 --- a/frontend/src/components/Modals/MangeWorkspace/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/index.jsx @@ -4,6 +4,7 @@ import { useParams } from "react-router-dom"; import Workspace from "../../../models/workspace"; import System from "../../../models/system"; import { isMobile } from "react-device-detect"; +import useUser from "../../../hooks/useUser"; const DocumentSettings = lazy(() => import("./Documents")); const WorkspaceSettings = lazy(() => import("./Settings")); @@ -117,9 +118,13 @@ const ManageWorkspace = ({ hideModal = noop, providedSlug = null }) => { export default memo(ManageWorkspace); export function useManageWorkspaceModal() { + const { user } = useUser(); const [showing, setShowing] = useState(false); + const showModal = () => { - setShowing(true); + if (user?.role !== "default") { + setShowing(true); + } }; const hideModal = () => { diff --git a/frontend/src/components/PrivateRoute/index.jsx b/frontend/src/components/PrivateRoute/index.jsx index e8db5400..7ca949b2 100644 --- a/frontend/src/components/PrivateRoute/index.jsx +++ b/frontend/src/components/PrivateRoute/index.jsx @@ -14,6 +14,7 @@ function useIsAuthenticated() { const [isAuthd, setIsAuthed] = useState(null); const [shouldRedirectToOnboarding, setShouldRedirectToOnboarding] = useState(false); + const [multiUserMode, setMultiUserMode] = useState(false); useEffect(() => { const validateSession = async () => { @@ -25,6 +26,8 @@ function useIsAuthenticated() { AzureOpenAiKey = false, } = await System.keys(); + setMultiUserMode(MultiUserMode); + // Check for the onboarding redirect condition if ( !MultiUserMode && @@ -77,11 +80,14 @@ function useIsAuthenticated() { validateSession(); }, []); - return { isAuthd, shouldRedirectToOnboarding }; + return { isAuthd, shouldRedirectToOnboarding, multiUserMode }; } +// Allows only admin to access the route and if in single user mode, +// allows all users to access the route export function AdminRoute({ Component }) { - const { isAuthd, shouldRedirectToOnboarding } = useIsAuthenticated(); + const { isAuthd, shouldRedirectToOnboarding, multiUserMode } = + useIsAuthenticated(); if (isAuthd === null) return ; if (shouldRedirectToOnboarding) { @@ -89,7 +95,28 @@ export function AdminRoute({ Component }) { } const user = userFromStorage(); - return isAuthd && user?.role === "admin" ? ( + return isAuthd && (user?.role === "admin" || !multiUserMode) ? ( + + + + ) : ( + + ); +} + +// Allows manager and admin to access the route and if in single user mode, +// allows all users to access the route +export function ManagerRoute({ Component }) { + const { isAuthd, shouldRedirectToOnboarding, multiUserMode } = + useIsAuthenticated(); + if (isAuthd === null) return ; + + if (shouldRedirectToOnboarding) { + return ; + } + + const user = userFromStorage(); + return isAuthd && (user?.role !== "default" || !multiUserMode) ? ( diff --git a/frontend/src/components/SettingsSidebar/index.jsx b/frontend/src/components/SettingsSidebar/index.jsx index 9b0c2553..887f8af3 100644 --- a/frontend/src/components/SettingsSidebar/index.jsx +++ b/frontend/src/components/SettingsSidebar/index.jsx @@ -65,96 +65,84 @@ export default function SettingsSidebar() {
- {/* Admin Settings */} - {user?.role === "admin" && ( + {/* Admin/manager Multi-user Settings */} + {!!user && user?.role !== "default" && ( <>
- {/*
-
- - -
-
*/} - {/* Footer */}
@@ -277,73 +265,70 @@ export function SidebarMobileHeader() { style={{ height: "calc(100vw - -3rem)" }} className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll" > - {user?.role === "admin" && ( - <> -
-
- {/* */} - -
+ {(!user || user?.role !== "default") && ( +
+ +
+ )}
{/* Primary Body */}
- + {(!user || user?.role !== "default") && ( + + )}
@@ -133,6 +138,7 @@ export function SidebarMobileHeader() { showModal: showNewWsModal, hideModal: hideNewWsModal, } = useNewWorkspaceModal(); + const { user } = useUser(); useEffect(() => { // Darkens the rest of the screen @@ -197,9 +203,11 @@ export function SidebarMobileHeader() { style={{ objectFit: "contain" }} />
-
- -
+ {(!user || user?.role !== "default") && ( +
+ +
+ )}
{/* Primary Body */} @@ -210,15 +218,17 @@ export function SidebarMobileHeader() { className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll" >
- + {(!user || user?.role !== "default") && ( + + )}
@@ -266,7 +276,7 @@ export function SidebarMobileHeader() { function SettingsButton() { return (
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx index 60134bd9..22dbdf32 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx @@ -10,6 +10,7 @@ import { isMobile } from "react-device-detect"; import ManageWorkspace, { useManageWorkspaceModal, } from "../../../Modals/MangeWorkspace"; +import useUser from "../../../../hooks/useUser"; export default function PromptInput({ workspace, @@ -22,6 +23,7 @@ export default function PromptInput({ const { showing, showModal, hideModal } = useManageWorkspaceModal(); const formRef = useRef(null); const [_, setFocused] = useState(false); + const { user } = useUser(); const handleSubmit = (e) => { setFocused(false); @@ -86,11 +88,14 @@ export default function PromptInput({
- + {user?.role !== "default" && ( + + )} + {/* { setError(null); e.preventDefault(); @@ -22,6 +25,8 @@ export default function NewUserModal() { setError(error); }; + const user = userFromStorage(); + return (
@@ -87,11 +92,16 @@ export default function NewUserModal() { 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" > - + + {user?.role === "admin" && ( + + )} +
{error && (

Error: {error}

diff --git a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx index c2e48fdb..c3b6a939 100644 --- a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx +++ b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx @@ -1,10 +1,12 @@ import React, { useState } from "react"; 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({ user }) { +export default function EditUserModal({ currentUser, user }) { + const [role, setRole] = useState(user.role); const [error, setError] = useState(null); const hideModal = () => { @@ -90,11 +92,16 @@ export default function EditUserModal({ user }) { 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" > - + + {currentUser?.role === "admin" && ( + + )} +
{error && (

Error: {error}

diff --git a/frontend/src/pages/Admin/Users/UserRow/index.jsx b/frontend/src/pages/Admin/Users/UserRow/index.jsx index c4dac62c..5964b6bd 100644 --- a/frontend/src/pages/Admin/Users/UserRow/index.jsx +++ b/frontend/src/pages/Admin/Users/UserRow/index.jsx @@ -40,15 +40,17 @@ export default function UserRow({ currUser, user }) { {titleCase(user.role)} {user.createdAt} - - {currUser.id !== user.id && ( + {currUser?.role !== "default" && ( + + )} + {currUser?.id !== user.id && currUser?.role !== "default" && ( <>