From 1d25368292920a730ba8ebc2b1eb4312b1230101 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Wed, 18 Mar 2026 15:58:06 +0100 Subject: [PATCH] feat: Dokploy-style sidebar layout for site pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar takes full viewport height with Pulse logo at top. No Header on site pages — UtilityBar in content area provides theme toggle, app launcher, notifications, and user menu. Non-site authenticated pages keep static Header. No footer on dashboard pages. --- app/layout-content.tsx | 132 ++++++++++++------------ components/dashboard/DashboardShell.tsx | 12 ++- components/dashboard/Sidebar.tsx | 19 +++- components/dashboard/UtilityBar.tsx | 102 ++++++++++++++++++ 4 files changed, 193 insertions(+), 72 deletions(-) create mode 100644 components/dashboard/UtilityBar.tsx diff --git a/app/layout-content.tsx b/app/layout-content.tsx index 55111d5..47d8698 100644 --- a/app/layout-content.tsx +++ b/app/layout-content.tsx @@ -8,6 +8,7 @@ import { useAuth } from '@/lib/auth/context' import { useOnlineStatus } from '@/lib/hooks/useOnlineStatus' import Link from 'next/link' import { useEffect, useState } from 'react' +import { usePathname } from 'next/navigation' import { logger } from '@/lib/utils/logger' import { getUserOrganizations, switchContext, type OrganizationMember } from '@/lib/api/organization' import { setSessionAction } from '@/app/actions/auth' @@ -15,11 +16,10 @@ import { LoadingOverlay } from '@ciphera-net/ui' import { useRouter } from 'next/navigation' import { SettingsModalProvider, useSettingsModal } from '@/lib/settings-modal-context' import SettingsModalWrapper from '@/components/settings/SettingsModalWrapper' -import { SidebarProvider, useSidebar } from '@/lib/sidebar-context' +import { SidebarProvider } from '@/lib/sidebar-context' const ORG_SWITCH_KEY = 'pulse_switching_org' -// * Available Ciphera apps for the app switcher const CIPHERA_APPS: CipheraApp[] = [ { id: 'pulse', @@ -27,7 +27,7 @@ const CIPHERA_APPS: CipheraApp[] = [ description: 'Your current app — Privacy-first analytics', icon: 'https://ciphera.net/pulse_icon_no_margins.png', href: 'https://pulse.ciphera.net', - isAvailable: false, // * Current app + isAvailable: false, }, { id: 'drop', @@ -47,22 +47,10 @@ const CIPHERA_APPS: CipheraApp[] = [ }, ] -function MobileSidebarToggle() { - const { openMobile } = useSidebar() - return ( - - ) -} - function LayoutInner({ children }: { children: React.ReactNode }) { const auth = useAuth() const router = useRouter() + const pathname = usePathname() const isOnline = useOnlineStatus() const { openSettings } = useSettingsModal() const [orgs, setOrgs] = useState([]) @@ -71,7 +59,6 @@ function LayoutInner({ children }: { children: React.ReactNode }) { return sessionStorage.getItem(ORG_SWITCH_KEY) === 'true' }) - // * Clear the switching flag once the page has settled after reload useEffect(() => { if (isSwitchingOrg) { sessionStorage.removeItem(ORG_SWITCH_KEY) @@ -80,7 +67,6 @@ function LayoutInner({ children }: { children: React.ReactNode }) { } }, [isSwitchingOrg]) - // * Fetch organizations for the header organization switcher useEffect(() => { if (auth.user) { getUserOrganizations() @@ -90,7 +76,7 @@ function LayoutInner({ children }: { children: React.ReactNode }) { }, [auth.user]) const handleSwitchOrganization = async (orgId: string | null) => { - if (!orgId) return // Pulse doesn't support personal organization context + if (!orgId) return try { const { access_token } = await switchContext(orgId) await setSessionAction(access_token) @@ -101,69 +87,83 @@ function LayoutInner({ children }: { children: React.ReactNode }) { } } - const handleCreateOrganization = () => { - router.push('/onboarding') - } - const isAuthenticated = !!auth.user const showOfflineBar = Boolean(auth.user && !isOnline) + // Site pages use DashboardShell with full sidebar — no Header needed + const isSitePage = pathname.startsWith('/sites/') && pathname !== '/sites/new' if (isSwitchingOrg) { return } - const headerElement = ( -
: null} - apps={CIPHERA_APPS} - currentAppId="pulse" - onOpenSettings={openSettings} - leftActions={isAuthenticated ? : undefined} - customNavItems={ - <> - {!auth.user && ( - - Features - - )} - - } - /> - ) - - if (isAuthenticated) { - // Dashboard layout: header pinned, sidebar + content fill remaining viewport + // Authenticated site pages: full Dokploy-style layout (sidebar + utility bar) + // DashboardShell inside children handles everything + if (isAuthenticated && isSitePage) { return ( -
- {auth.user && } -
{headerElement}
+ <> + {showOfflineBar && } {children} + + ) + } + + // Authenticated non-site pages (sites list, onboarding, etc.): static header + if (isAuthenticated) { + return ( +
+ {showOfflineBar && } +
router.push('/onboarding')} + allowPersonalOrganization={false} + showFaq={false} + showSecurity={false} + showPricing={false} + rightSideActions={} + apps={CIPHERA_APPS} + currentAppId="pulse" + onOpenSettings={openSettings} + /> +
+ {children} +
+
) } - // Public/marketing layout: floating header, scrollable page, footer + // Public/marketing: floating header + footer return (
- {headerElement} +
+ Features + + } + />
{children}
diff --git a/components/dashboard/DashboardShell.tsx b/components/dashboard/DashboardShell.tsx index bd83581..8b3d6cb 100644 --- a/components/dashboard/DashboardShell.tsx +++ b/components/dashboard/DashboardShell.tsx @@ -1,6 +1,7 @@ 'use client' import Sidebar from './Sidebar' +import UtilityBar from './UtilityBar' import { useSidebar } from '@/lib/sidebar-context' export default function DashboardShell({ @@ -13,11 +14,14 @@ export default function DashboardShell({ const { mobileOpen, closeMobile } = useSidebar() return ( -
+
-
- {children} -
+
+ +
+ {children} +
+
) } diff --git a/components/dashboard/Sidebar.tsx b/components/dashboard/Sidebar.tsx index b47884d..b478151 100644 --- a/components/dashboard/Sidebar.tsx +++ b/components/dashboard/Sidebar.tsx @@ -253,8 +253,23 @@ export default function Sidebar({ return (
+ {/* Logo */} + + Pulse + {!isCollapsed && ( + Pulse + )} + + {/* Site Picker */} -
+
@@ -319,7 +334,7 @@ export default function Sidebar({ {/* Desktop sidebar */}