'use client' import { OfflineBanner } from '@/components/OfflineBanner' import { Footer } from '@/components/Footer' import { Header, type CipheraApp } from '@ciphera-net/ui' import { Header as MarketingHeader } from '@/components/marketing/Header' import NotificationCenter from '@/components/notifications/NotificationCenter' 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' import { LoadingOverlay } from '@ciphera-net/ui' import { useRouter } from 'next/navigation' import { UnifiedSettingsProvider, useUnifiedSettings } from '@/lib/unified-settings-context' import UnifiedSettingsModal from '@/components/settings/unified/UnifiedSettingsModal' import DashboardShell from '@/components/dashboard/DashboardShell' const ORG_SWITCH_KEY = 'pulse_switching_org' const CIPHERA_APPS: CipheraApp[] = [ { id: 'pulse', name: 'Pulse', description: 'Your current app — Privacy-first analytics', icon: 'https://ciphera.net/pulse_icon_no_margins.png', href: 'https://pulse.ciphera.net', isAvailable: false, }, { id: 'drop', name: 'Drop', description: 'Secure file sharing', icon: 'https://ciphera.net/drop_icon_no_margins.png', href: 'https://drop.ciphera.net', isAvailable: true, }, { id: 'auth', name: 'Auth', description: 'Your Ciphera account settings', icon: 'https://ciphera.net/auth_icon_no_margins.png', href: 'https://auth.ciphera.net', isAvailable: true, }, ] function LayoutInner({ children }: { children: React.ReactNode }) { const auth = useAuth() const router = useRouter() const pathname = usePathname() const isOnline = useOnlineStatus() const { openUnifiedSettings } = useUnifiedSettings() const [orgs, setOrgs] = useState([]) const [isSwitchingOrg, setIsSwitchingOrg] = useState(() => { if (typeof window === 'undefined') return false return sessionStorage.getItem(ORG_SWITCH_KEY) === 'true' }) useEffect(() => { if (isSwitchingOrg) { sessionStorage.removeItem(ORG_SWITCH_KEY) const timer = setTimeout(() => setIsSwitchingOrg(false), 600) return () => clearTimeout(timer) } }, [isSwitchingOrg]) useEffect(() => { if (auth.user) { getUserOrganizations() .then((organizations) => setOrgs(Array.isArray(organizations) ? organizations : [])) .catch(err => logger.error('Failed to fetch orgs for header', err)) } }, [auth.user]) const handleSwitchOrganization = async (orgId: string | null) => { if (!orgId) return try { setIsSwitchingOrg(true) const { access_token } = await switchContext(orgId) await setSessionAction(access_token) // Refresh auth context (re-fetches /auth/user/me with new JWT, updates org_id + SWR cache) await auth.refresh() router.push('/') setTimeout(() => setIsSwitchingOrg(false), 300) } catch (err) { setIsSwitchingOrg(false) logger.error('Failed to switch organization', err) } } 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' // Pages that use DashboardShell with home sidebar (no site context) const isDashboardPage = pathname === '/' || pathname.startsWith('/integrations') || pathname === '/pricing' // Checkout page has its own minimal layout — no app header/footer const isCheckoutPage = pathname.startsWith('/checkout') if (isSwitchingOrg) { return } // While auth is loading on a site or checkout page, render nothing to prevent flash of public header if (auth.loading && (isSitePage || isCheckoutPage || isDashboardPage)) { return null } // Authenticated site pages: DashboardShell provided by sites layout if (isAuthenticated && isSitePage) { return ( <> {showOfflineBar && } {children} ) } // Authenticated dashboard pages (home, integrations, pricing): wrap in DashboardShell if (isAuthenticated && isDashboardPage) { return ( <> {showOfflineBar && } {children} ) } // Checkout page: render children only (has its own layout) if (isAuthenticated && isCheckoutPage) { return <>{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={() => openUnifiedSettings({ context: 'account', tab: 'profile' })} />
{children}
) } // Public/marketing: sticky header + footer return (
{children}
) } export default function LayoutContent({ children }: { children: React.ReactNode }) { return ( {children} ) }