diff --git a/surfsense_web/app/dashboard/[workspace_id]/client-layout.tsx b/surfsense_web/app/dashboard/[workspace_id]/client-layout.tsx index 221586674..54fc9d000 100644 --- a/surfsense_web/app/dashboard/[workspace_id]/client-layout.tsx +++ b/surfsense_web/app/dashboard/[workspace_id]/client-layout.tsx @@ -20,9 +20,11 @@ import { useElectronAPI } from "@/hooks/use-platform"; export function DashboardClientLayout({ children, workspaceId, + initialPlaygroundSidebarCollapsed, }: { children: React.ReactNode; workspaceId: string; + initialPlaygroundSidebarCollapsed: boolean; }) { const t = useTranslations("dashboard"); const router = useRouter(); @@ -162,7 +164,10 @@ export function DashboardClientLayout({ return ( - + {children} diff --git a/surfsense_web/app/dashboard/[workspace_id]/layout.tsx b/surfsense_web/app/dashboard/[workspace_id]/layout.tsx index 74c12fd98..4ef79076e 100644 --- a/surfsense_web/app/dashboard/[workspace_id]/layout.tsx +++ b/surfsense_web/app/dashboard/[workspace_id]/layout.tsx @@ -1,16 +1,27 @@ // Server component import type React from "react"; -import { use } from "react"; +import { cookies } from "next/headers"; import { DashboardClientLayout } from "./client-layout"; -export default function DashboardLayout({ +const PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE = "surfsense_playground_sidebar_collapsed"; + +export default async function DashboardLayout({ params, children, }: { params: Promise<{ workspace_id: string }>; children: React.ReactNode; }) { - const { workspace_id } = use(params); + const [{ workspace_id }, cookieStore] = await Promise.all([params, cookies()]); + const initialPlaygroundSidebarCollapsed = + cookieStore.get(PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE)?.value === "true"; - return {children}; + return ( + + {children} + + ); } diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index e60d04198..eec58648f 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -56,10 +56,15 @@ import { LayoutShell } from "../ui/shell"; interface LayoutDataProviderProps { workspaceId: string; + initialPlaygroundSidebarCollapsed: boolean; children: React.ReactNode; } -export function LayoutDataProvider({ workspaceId, children }: LayoutDataProviderProps) { +export function LayoutDataProvider({ + workspaceId, + initialPlaygroundSidebarCollapsed, + children, +}: LayoutDataProviderProps) { const t = useTranslations("dashboard"); const tCommon = useTranslations("common"); const tSidebar = useTranslations("sidebar"); @@ -720,6 +725,7 @@ export function LayoutDataProvider({ workspaceId, children }: LayoutDataProvider onTabSwitch={handleTabSwitch} onTabPrefetch={handleTabPrefetch} playgroundSidebar={} + initialPlaygroundSidebarCollapsed={initialPlaygroundSidebarCollapsed} > {children} diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index aec472f84..7a42d5a2e 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -2,7 +2,7 @@ import { useAtomValue } from "jotai"; import dynamic from "next/dynamic"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { activeTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; import { Logo } from "@/components/Logo"; import { Spinner } from "@/components/ui/spinner"; @@ -41,7 +41,8 @@ const DocumentTabContent = dynamic( } ); -const PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY = "surfsense:layout:v1:playground-sidebar-collapsed"; +const PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE = "surfsense_playground_sidebar_collapsed"; +const PLAYGROUND_SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365; function MacDesktopTitleBar({ isSidebarCollapsed, @@ -115,6 +116,7 @@ interface LayoutShellProps { onTabSwitch?: (tab: Tab) => void; onTabPrefetch?: (tab: Tab) => void; playgroundSidebar?: React.ReactNode; + initialPlaygroundSidebarCollapsed?: boolean; } function MainContentPanel({ @@ -216,12 +218,15 @@ export function LayoutShell({ onTabSwitch, onTabPrefetch, playgroundSidebar, + initialPlaygroundSidebarCollapsed = false, }: LayoutShellProps) { const isMobile = useIsMobile(); const electronAPI = useElectronAPI(); const isMacDesktop = electronAPI?.versions.platform === "darwin"; const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [isPlaygroundSidebarCollapsed, setIsPlaygroundSidebarCollapsed] = useState(false); + const [isPlaygroundSidebarCollapsed, setIsPlaygroundSidebarCollapsed] = useState( + initialPlaygroundSidebarCollapsed + ); const { isCollapsed, setIsCollapsed, toggleCollapsed } = useSidebarState(defaultCollapsed); const { sidebarWidth, @@ -234,23 +239,11 @@ export function LayoutShell({ () => ({ isCollapsed, setIsCollapsed, toggleCollapsed }), [isCollapsed, setIsCollapsed, toggleCollapsed] ); - useEffect(() => { - try { - setIsPlaygroundSidebarCollapsed( - window.localStorage.getItem(PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY) === "true" - ); - } catch {} - }, []); - const handlePlaygroundSidebarToggle = () => { setIsPlaygroundSidebarCollapsed((collapsed) => { const nextCollapsed = !collapsed; - try { - window.localStorage.setItem( - PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY, - String(nextCollapsed) - ); - } catch {} + const secureAttribute = window.location.protocol === "https:" ? "; Secure" : ""; + document.cookie = `${PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE}=${nextCollapsed}; Path=/; Max-Age=${PLAYGROUND_SIDEBAR_COOKIE_MAX_AGE}; SameSite=Lax${secureAttribute}`; return nextCollapsed; }); };