diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx new file mode 100644 index 000000000..8dd7eb38e --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx @@ -0,0 +1,40 @@ +import { + SearchSpaceSettingsPanel, + type SearchSpaceSettingsTab, +} from "@/components/settings/search-space-settings-panel"; + +const SEARCH_SPACE_SETTINGS_TABS = new Set([ + "general", + "roles", + "models", + "image-models", + "vision-models", + "team-roles", + "prompts", + "team-memory", + "public-links", +]); + +function getInitialTab(tab: string | string[] | undefined): SearchSpaceSettingsTab { + const value = Array.isArray(tab) ? tab[0] : tab; + return value && SEARCH_SPACE_SETTINGS_TABS.has(value) + ? (value as SearchSpaceSettingsTab) + : "general"; +} + +export default async function SearchSpaceSettingsPage({ + params, + searchParams, +}: { + params: Promise<{ search_space_id: string }>; + searchParams: Promise<{ tab?: string | string[] }>; +}) { + const [{ search_space_id }, resolvedSearchParams] = await Promise.all([params, searchParams]); + + return ( + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index e93ad1799..cc83148a3 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -1,7 +1,7 @@ "use client"; import { useQuery } from "@tanstack/react-query"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue } from "jotai"; import { Calendar, Check, @@ -20,6 +20,7 @@ import { UserPlus, Users, } from "lucide-react"; +import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { @@ -31,7 +32,6 @@ import { updateMemberMutationAtom, } from "@/atoms/members/members-mutation.atoms"; import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; -import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { AlertDialog, AlertDialogAction, @@ -346,6 +346,7 @@ export function TeamContent({ searchSpaceId }: TeamContentProps) { {owners.map((member) => ( ( Promise; onRemoveMember: (membershipId: number) => Promise; }) { - const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom); + const router = useRouter(); const initials = getAvatarInitials(member); const displayName = member.user_display_name || member.user_email || "Unknown"; const roleName = member.is_owner ? "Owner" : member.role?.name || "No role"; @@ -541,10 +545,7 @@ function MemberRow({ - setSearchSpaceSettingsDialog({ - open: true, - initialTab: "team-roles", - }) + router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=team-roles`) } > Manage Roles diff --git a/surfsense_web/atoms/layout/dialogs.atom.ts b/surfsense_web/atoms/layout/dialogs.atom.ts new file mode 100644 index 000000000..e9ee0c6ea --- /dev/null +++ b/surfsense_web/atoms/layout/dialogs.atom.ts @@ -0,0 +1,5 @@ +import { atom } from "jotai"; + +export const teamDialogAtom = atom(false); + +export const announcementsDialogAtom = atom(false); diff --git a/surfsense_web/atoms/settings/settings-dialog.atoms.ts b/surfsense_web/atoms/settings/settings-dialog.atoms.ts deleted file mode 100644 index e066fc8ca..000000000 --- a/surfsense_web/atoms/settings/settings-dialog.atoms.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { atom } from "jotai"; - -export interface SearchSpaceSettingsDialogState { - open: boolean; - initialTab: string; -} - -export const searchSpaceSettingsDialogAtom = atom({ - open: false, - initialTab: "general", -}); - -export const teamDialogAtom = atom(false); - -export const announcementsDialogAtom = atom(false); diff --git a/surfsense_web/components/announcements/AnnouncementsDialog.tsx b/surfsense_web/components/announcements/AnnouncementsDialog.tsx index d624444c4..54653b535 100644 --- a/surfsense_web/components/announcements/AnnouncementsDialog.tsx +++ b/surfsense_web/components/announcements/AnnouncementsDialog.tsx @@ -2,7 +2,7 @@ import { useAtom } from "jotai"; import { useEffect } from "react"; -import { announcementsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; +import { announcementsDialogAtom } from "@/atoms/layout/dialogs.atom"; import { AnnouncementCard } from "@/components/announcements/AnnouncementCard"; import { AnnouncementsEmptyState } from "@/components/announcements/AnnouncementsEmptyState"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 001259236..ed671d374 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -1,7 +1,8 @@ "use client"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue } from "jotai"; import { AlertTriangle, Settings } from "lucide-react"; +import { useRouter } from "next/navigation"; import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom"; @@ -10,7 +11,6 @@ import { llmPreferencesAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; -import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; @@ -44,8 +44,8 @@ interface ConnectorIndicatorProps { export const ConnectorIndicator = forwardRef( (_props, ref) => { + const router = useRouter(); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); - const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom); const { data: preferences = {}, isFetching: preferencesLoading } = useAtomValue(llmPreferencesAtom); const { data: globalConfigs = [], isFetching: globalConfigsLoading } = @@ -397,10 +397,9 @@ export const ConnectorIndicator = forwardRef { handleOpenChange(false); - setSearchSpaceSettingsDialog({ - open: true, - initialTab: "models", - }); + router.push( + `/dashboard/${searchSpaceId}/search-space-settings?tab=models` + ); }} > diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 13683162f..d4482a01b 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -1,7 +1,8 @@ "use client"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue } from "jotai"; import { AlertTriangle, Settings } from "lucide-react"; +import { useRouter } from "next/navigation"; import { createContext, type FC, @@ -16,7 +17,6 @@ import { llmPreferencesAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; -import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { DocumentUploadTab } from "@/components/sources/DocumentUploadTab"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -98,8 +98,8 @@ const DocumentUploadPopupContent: FC<{ isOpen: boolean; onOpenChange: (open: boolean) => void; }> = ({ isOpen, onOpenChange }) => { + const router = useRouter(); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); - const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom); const { data: preferences = {}, isFetching: preferencesLoading } = useAtomValue(llmPreferencesAtom); const { data: globalConfigs = [], isFetching: globalConfigsLoading } = @@ -164,10 +164,7 @@ const DocumentUploadPopupContent: FC<{ variant="outline" onClick={() => { onOpenChange(false); - setSearchSpaceSettingsDialog({ - open: true, - initialTab: "models", - }); + router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=models`); }} > diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index c1fe3e5c1..d46895a0a 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -11,19 +11,14 @@ import { toast } from "sonner"; import { currentThreadAtom, resetCurrentThreadAtom } from "@/atoms/chat/current-thread.atom"; import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms"; import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom"; +import { announcementsDialogAtom, teamDialogAtom } from "@/atoms/layout/dialogs.atom"; import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom"; import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms"; -import { - announcementsDialogAtom, - searchSpaceSettingsDialogAtom, - teamDialogAtom, -} from "@/atoms/settings/settings-dialog.atoms"; import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog"; import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog"; -import { SearchSpaceSettingsDialog } from "@/components/settings/search-space-settings-dialog"; import { TeamDialog } from "@/components/settings/team-dialog"; import { AlertDialog, @@ -380,7 +375,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid setIsCreateSearchSpaceDialogOpen(true); }, []); - const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom); const setTeamDialogOpen = useSetAtom(teamDialogAtom); const setAnnouncementsDialog = useSetAtom(announcementsDialogAtom); @@ -393,10 +387,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid }, [setAnnouncementsDialog]); const handleSearchSpaceSettings = useCallback( - (_space: SearchSpace) => { - setSearchSpaceSettingsDialog({ open: true, initialTab: "general" }); + (space: SearchSpace) => { + router.push(`/dashboard/${space.id}/search-space-settings`); }, - [setSearchSpaceSettingsDialog] + [router] ); const handleSearchSpaceDeleteClick = useCallback((space: SearchSpace) => { @@ -568,8 +562,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid ); const handleSettings = useCallback(() => { - setSearchSpaceSettingsDialog({ open: true, initialTab: "general" }); - }, [setSearchSpaceSettingsDialog]); + router.push(`/dashboard/${searchSpaceId}/search-space-settings`); + }, [router, searchSpaceId]); const handleManageMembers = useCallback(() => { setTeamDialogOpen(true); @@ -666,10 +660,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid // Detect if we're on the chat page (needs overflow-hidden for chat's own scroll) const isChatPage = pathname?.includes("/new-chat") ?? false; const isUserSettingsPage = pathname?.endsWith("/user-settings") === true; + const isSearchSpaceSettingsPage = pathname?.endsWith("/search-space-settings") === true; const useWorkspacePanel = pathname?.endsWith("/buy-more") === true || pathname?.endsWith("/more-pages") === true || - isUserSettingsPage; + isUserSettingsPage || + isSearchSpaceSettingsPage; return ( <> @@ -709,9 +705,13 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid isChatPage={isChatPage} useWorkspacePanel={useWorkspacePanel} workspacePanelViewportClassName={ - isUserSettingsPage ? "items-start justify-center px-6 py-8 md:px-10 md:py-10" : undefined + isUserSettingsPage || isSearchSpaceSettingsPage + ? "items-start justify-center px-6 py-8 md:px-10 md:py-10" + : undefined + } + workspacePanelContentClassName={ + isUserSettingsPage || isSearchSpaceSettingsPage ? "max-w-5xl" : undefined } - workspacePanelContentClassName={isUserSettingsPage ? "max-w-5xl" : undefined} isLoadingChats={isLoadingThreads} activeSlideoutPanel={activeSlideoutPanel} onSlideoutPanelChange={setActiveSlideoutPanel} @@ -891,7 +891,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid onOpenChange={setIsCreateSearchSpaceDialogOpen} /> - diff --git a/surfsense_web/components/new-chat/chat-share-button.tsx b/surfsense_web/components/new-chat/chat-share-button.tsx index f8a9f55ff..3b4ff0654 100644 --- a/surfsense_web/components/new-chat/chat-share-button.tsx +++ b/surfsense_web/components/new-chat/chat-share-button.tsx @@ -3,13 +3,13 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtomValue, useSetAtom } from "jotai"; import { Earth, User, Users } from "lucide-react"; +import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { currentThreadAtom, setThreadVisibilityAtom } from "@/atoms/chat/current-thread.atom"; import { myAccessAtom } from "@/atoms/members/members-query.atoms"; import { createPublicChatSnapshotMutationAtom } from "@/atoms/public-chat-snapshots/public-chat-snapshots-mutation.atoms"; -import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; @@ -49,8 +49,8 @@ const visibilityOptions: { export function ChatShareButton({ thread, onVisibilityChange, className }: ChatShareButtonProps) { const queryClient = useQueryClient(); + const router = useRouter(); const [open, setOpen] = useState(false); - const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom); // Use Jotai atom for visibility (single source of truth) const currentThreadState = useAtomValue(currentThreadAtom); @@ -148,10 +148,9 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS variant="ghost" size="icon" onClick={() => - setSearchSpaceSettingsDialog({ - open: true, - initialTab: "public-links", - }) + router.push( + `/dashboard/${thread.search_space_id}/search-space-settings?tab=public-links` + ) } className="size-8 bg-muted/50 hover:bg-accent hover:text-accent-foreground" > diff --git a/surfsense_web/components/settings/search-space-settings-dialog.tsx b/surfsense_web/components/settings/search-space-settings-dialog.tsx deleted file mode 100644 index 2a7ba82b6..000000000 --- a/surfsense_web/components/settings/search-space-settings-dialog.tsx +++ /dev/null @@ -1,140 +0,0 @@ -"use client"; - -import { useAtom } from "jotai"; -import { - BookText, - Bot, - Brain, - CircleUser, - Earth, - ImageIcon, - ListChecks, - ScanEye, - UserKey, -} from "lucide-react"; -import dynamic from "next/dynamic"; -import { useTranslations } from "next-intl"; -import type React from "react"; -import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; -import { SettingsDialog } from "@/components/settings/settings-dialog"; - -const GeneralSettingsManager = dynamic( - () => - import("@/components/settings/general-settings-manager").then((m) => ({ - default: m.GeneralSettingsManager, - })), - { ssr: false } -); -const AgentModelManager = dynamic( - () => - import("@/components/settings/agent-model-manager").then((m) => ({ - default: m.AgentModelManager, - })), - { ssr: false } -); -const LLMRoleManager = dynamic( - () => - import("@/components/settings/llm-role-manager").then((m) => ({ default: m.LLMRoleManager })), - { ssr: false } -); -const ImageModelManager = dynamic( - () => - import("@/components/settings/image-model-manager").then((m) => ({ - default: m.ImageModelManager, - })), - { ssr: false } -); -const VisionModelManager = dynamic( - () => - import("@/components/settings/vision-model-manager").then((m) => ({ - default: m.VisionModelManager, - })), - { ssr: false } -); -const RolesManager = dynamic( - () => import("@/components/settings/roles-manager").then((m) => ({ default: m.RolesManager })), - { ssr: false } -); -const PromptConfigManager = dynamic( - () => - import("@/components/settings/prompt-config-manager").then((m) => ({ - default: m.PromptConfigManager, - })), - { ssr: false } -); -const PublicChatSnapshotsManager = dynamic( - () => - import("@/components/public-chat-snapshots/public-chat-snapshots-manager").then((m) => ({ - default: m.PublicChatSnapshotsManager, - })), - { ssr: false } -); -const TeamMemoryManager = dynamic( - () => - import("@/components/settings/team-memory-manager").then((m) => ({ - default: m.TeamMemoryManager, - })), - { ssr: false } -); - -interface SearchSpaceSettingsDialogProps { - searchSpaceId: number; -} - -export function SearchSpaceSettingsDialog({ searchSpaceId }: SearchSpaceSettingsDialogProps) { - const t = useTranslations("searchSpaceSettings"); - const [state, setState] = useAtom(searchSpaceSettingsDialogAtom); - - const navItems = [ - { value: "general", label: t("nav_general"), icon: }, - { value: "roles", label: t("nav_role_assignments"), icon: }, - { value: "models", label: t("nav_agent_models"), icon: }, - { - value: "image-models", - label: t("nav_image_models"), - icon: , - }, - { - value: "vision-models", - label: t("nav_vision_models"), - icon: , - }, - { value: "team-roles", label: t("nav_team_roles"), icon: }, - { - value: "prompts", - label: t("nav_system_instructions"), - icon: , - }, - { - value: "team-memory", - label: "Team Memory", - icon: , - }, - { value: "public-links", label: t("nav_public_links"), icon: }, - ]; - - const content: Record = { - general: , - models: , - roles: , - "image-models": , - "vision-models": , - "team-roles": , - prompts: , - "team-memory": , - "public-links": , - }; - - return ( - setState((prev) => ({ ...prev, open }))} - title={t("title")} - navItems={navItems} - activeItem={state.initialTab} - onItemChange={(tab) => setState((prev) => ({ ...prev, initialTab: tab }))} - > -
{content[state.initialTab]}
-
- ); -} diff --git a/surfsense_web/components/settings/search-space-settings-panel.tsx b/surfsense_web/components/settings/search-space-settings-panel.tsx new file mode 100644 index 000000000..248b2c585 --- /dev/null +++ b/surfsense_web/components/settings/search-space-settings-panel.tsx @@ -0,0 +1,245 @@ +"use client"; + +import { + BookText, + Bot, + Brain, + CircleUser, + Earth, + ImageIcon, + ListChecks, + ScanEye, + UserKey, +} from "lucide-react"; +import dynamic from "next/dynamic"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import type React from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; + +const GeneralSettingsManager = dynamic( + () => + import("@/components/settings/general-settings-manager").then((m) => ({ + default: m.GeneralSettingsManager, + })), + { ssr: false } +); +const AgentModelManager = dynamic( + () => + import("@/components/settings/agent-model-manager").then((m) => ({ + default: m.AgentModelManager, + })), + { ssr: false } +); +const LLMRoleManager = dynamic( + () => + import("@/components/settings/llm-role-manager").then((m) => ({ default: m.LLMRoleManager })), + { ssr: false } +); +const ImageModelManager = dynamic( + () => + import("@/components/settings/image-model-manager").then((m) => ({ + default: m.ImageModelManager, + })), + { ssr: false } +); +const VisionModelManager = dynamic( + () => + import("@/components/settings/vision-model-manager").then((m) => ({ + default: m.VisionModelManager, + })), + { ssr: false } +); +const RolesManager = dynamic( + () => import("@/components/settings/roles-manager").then((m) => ({ default: m.RolesManager })), + { ssr: false } +); +const PromptConfigManager = dynamic( + () => + import("@/components/settings/prompt-config-manager").then((m) => ({ + default: m.PromptConfigManager, + })), + { ssr: false } +); +const PublicChatSnapshotsManager = dynamic( + () => + import("@/components/public-chat-snapshots/public-chat-snapshots-manager").then((m) => ({ + default: m.PublicChatSnapshotsManager, + })), + { ssr: false } +); +const TeamMemoryManager = dynamic( + () => + import("@/components/settings/team-memory-manager").then((m) => ({ + default: m.TeamMemoryManager, + })), + { ssr: false } +); + +export type SearchSpaceSettingsTab = + | "general" + | "roles" + | "models" + | "image-models" + | "vision-models" + | "team-roles" + | "prompts" + | "team-memory" + | "public-links"; + +interface SearchSpaceSettingsPanelProps { + searchSpaceId: string; + initialTab?: SearchSpaceSettingsTab; +} + +export function SearchSpaceSettingsPanel({ + searchSpaceId, + initialTab = "general", +}: SearchSpaceSettingsPanelProps) { + const t = useTranslations("searchSpaceSettings"); + const router = useRouter(); + const numericSearchSpaceId = Number(searchSpaceId); + const [activeTab, setActiveTab] = useState(initialTab); + const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); + + useEffect(() => { + setActiveTab(initialTab); + }, [initialTab]); + + const handleTabScroll = useCallback((e: React.UIEvent) => { + const el = e.currentTarget; + const atStart = el.scrollLeft <= 2; + const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; + setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); + }, []); + + const navItems = useMemo( + () => [ + { value: "general", label: t("nav_general"), icon: }, + { value: "roles", label: t("nav_role_assignments"), icon: }, + { value: "models", label: t("nav_agent_models"), icon: }, + { + value: "image-models", + label: t("nav_image_models"), + icon: , + }, + { + value: "vision-models", + label: t("nav_vision_models"), + icon: , + }, + { value: "team-roles", label: t("nav_team_roles"), icon: }, + { + value: "prompts", + label: t("nav_system_instructions"), + icon: , + }, + { + value: "team-memory", + label: "Team Memory", + icon: , + }, + { value: "public-links", label: t("nav_public_links"), icon: }, + ], + [t] + ); + + const selectedTab = navItems.some((item) => item.value === activeTab) ? activeTab : "general"; + const selectedLabel = navItems.find((item) => item.value === selectedTab)?.label ?? t("title"); + + const handleItemChange = (tab: SearchSpaceSettingsTab) => { + setActiveTab(tab); + const suffix = tab === "general" ? "" : `?tab=${tab}`; + router.replace(`/dashboard/${searchSpaceId}/search-space-settings${suffix}`, { scroll: false }); + }; + + return ( +
+
+

{t("title")}

+ +
+
+ {navItems.map((item) => ( + + ))} +
+
+
+ +
+
+

{selectedLabel}

+ +
+
+ {selectedTab === "general" && ( + + )} + {selectedTab === "models" && } + {selectedTab === "roles" && ( + + )} + {selectedTab === "image-models" && ( + + )} + {selectedTab === "vision-models" && ( + + )} + {selectedTab === "team-roles" && } + {selectedTab === "prompts" && ( + + )} + {selectedTab === "team-memory" && ( + + )} + {selectedTab === "public-links" && ( + + )} +
+
+
+ ); +} diff --git a/surfsense_web/components/settings/settings-dialog.tsx b/surfsense_web/components/settings/settings-dialog.tsx deleted file mode 100644 index d3f75c2a8..000000000 --- a/surfsense_web/components/settings/settings-dialog.tsx +++ /dev/null @@ -1,129 +0,0 @@ -"use client"; - -import type * as React from "react"; -import { useCallback, useRef, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; - -interface NavItem { - value: string; - label: string; - icon: React.ReactNode; -} - -interface SettingsDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - title: string; - navItems: NavItem[]; - activeItem: string; - onItemChange: (value: string) => void; - children: React.ReactNode; -} - -export function SettingsDialog({ - open, - onOpenChange, - title, - navItems, - activeItem, - onItemChange, - children, -}: SettingsDialogProps) { - const activeRef = useRef(null); - const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); - - const handleTabScroll = useCallback((e: React.UIEvent) => { - const el = e.currentTarget; - const atStart = el.scrollLeft <= 2; - const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; - setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); - }, []); - - const handleItemChange = (value: string) => { - onItemChange(value); - activeRef.current?.scrollIntoView({ inline: "center", block: "nearest", behavior: "smooth" }); - }; - - return ( - - - {title} - - {/* Desktop: Left sidebar */} - - - {/* Mobile: Top header + horizontal tabs */} -
-
-

{title}

-
-
-
- {navItems.map((item) => ( - - ))} -
-
-
- - {/* Content area */} -
-
-

- {navItems.find((i) => i.value === activeItem)?.label ?? title} -

- -
-
-
{children}
-
-
-
-
- ); -} diff --git a/surfsense_web/components/settings/team-dialog.tsx b/surfsense_web/components/settings/team-dialog.tsx index 22c2a0eda..eb601fb64 100644 --- a/surfsense_web/components/settings/team-dialog.tsx +++ b/surfsense_web/components/settings/team-dialog.tsx @@ -3,7 +3,7 @@ import { useAtom } from "jotai"; import { useTranslations } from "next-intl"; import { TeamContent } from "@/app/dashboard/[search_space_id]/team/team-content"; -import { teamDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; +import { teamDialogAtom } from "@/atoms/layout/dialogs.atom"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; interface TeamDialogProps {