diff --git a/components/dashboard/DashboardShell.tsx b/components/dashboard/DashboardShell.tsx index 4369ef8..c328b8a 100644 --- a/components/dashboard/DashboardShell.tsx +++ b/components/dashboard/DashboardShell.tsx @@ -6,7 +6,7 @@ import { motion, AnimatePresence } from 'framer-motion' import dynamic from 'next/dynamic' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' -import { formatUpdatedAgo, PlusIcon } from '@ciphera-net/ui' +import { formatUpdatedAgo, PlusIcon, ExternalLinkIcon, type CipheraApp } from '@ciphera-net/ui' import { CaretDown, CaretRight, SidebarSimple } from '@phosphor-icons/react' import { SidebarProvider, useSidebar } from '@/lib/sidebar-context' import { useRealtime } from '@/lib/swr/dashboard' @@ -14,6 +14,12 @@ import { getSite, listSites, type Site } from '@/lib/api/sites' import { FAVICON_SERVICE_URL } from '@/lib/utils/favicon' import ContentHeader from './ContentHeader' +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 }, +] + const PAGE_TITLES: Record = { '': 'Dashboard', journeys: 'Journeys', @@ -58,6 +64,105 @@ const Sidebar = dynamic(() => import('./Sidebar'), { ), }) +// ─── Breadcrumb App Switcher ─────────────────────────────── + +function BreadcrumbAppSwitcher() { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const panelRef = useRef(null) + const buttonRef = useRef(null) + const [fixedPos, setFixedPos] = useState<{ left: number; top: number } | null>(null) + + useEffect(() => { + const handler = (e: MouseEvent) => { + const target = e.target as Node + if ( + ref.current && !ref.current.contains(target) && + (!panelRef.current || !panelRef.current.contains(target)) + ) setOpen(false) + } + document.addEventListener('mousedown', handler) + return () => document.removeEventListener('mousedown', handler) + }, []) + + useEffect(() => { + if (open && buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect() + let top = rect.bottom + 4 + if (panelRef.current) { + const maxTop = window.innerHeight - panelRef.current.offsetHeight - 8 + top = Math.min(top, Math.max(8, maxTop)) + } + setFixedPos({ left: rect.left, top }) + requestAnimationFrame(() => { + if (buttonRef.current) { + const r = buttonRef.current.getBoundingClientRect() + setFixedPos({ left: r.left, top: r.bottom + 4 }) + } + }) + } + }, [open]) + + const dropdown = ( + + {open && ( + +
+
Ciphera Apps
+ + + + )} + + ) + + return ( +
+ + {typeof document !== 'undefined' ? createPortal(dropdown, document.body) : dropdown} +
+ ) +} + // ─── Breadcrumb Site Picker ──────────────────────────────── function BreadcrumbSitePicker({ currentSiteId, currentSiteName }: { currentSiteId: string; currentSiteName: string }) { @@ -233,17 +338,21 @@ function GlassTopBar({ siteId }: { siteId: string | null }) { > - {siteId && siteName ? ( -
{/* Realtime indicator */} diff --git a/components/dashboard/Sidebar.tsx b/components/dashboard/Sidebar.tsx index 5214f48..dca4d4b 100644 --- a/components/dashboard/Sidebar.tsx +++ b/components/dashboard/Sidebar.tsx @@ -25,39 +25,10 @@ import { PlusIcon, XIcon, BookOpenIcon, - AppLauncher, UserMenu, - type CipheraApp, } from '@ciphera-net/ui' import NotificationCenter from '@/components/notifications/NotificationCenter' -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, - }, -] - const EXPANDED = 256 const COLLAPSED = 64 @@ -287,16 +258,6 @@ function SidebarContent({ return (
- {/* App Switcher — top of sidebar (scope-level switch) */} -
- - - - -
- {/* Logo — fixed layout, text fades */}