feat: wrap all authenticated pages in DashboardShell, fix site card actions
- Move DashboardShell wrapping to layout-content.tsx for all dashboard pages (home, integrations, pricing) instead of per-page - GlassTopBar derives page title from pathname (Integrations, Pricing) - Site card: gear icon now opens site settings, separate trash icon for delete
This commit is contained in:
@@ -17,6 +17,7 @@ import { LoadingOverlay } from '@ciphera-net/ui'
|
|||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { UnifiedSettingsProvider, useUnifiedSettings } from '@/lib/unified-settings-context'
|
import { UnifiedSettingsProvider, useUnifiedSettings } from '@/lib/unified-settings-context'
|
||||||
import UnifiedSettingsModal from '@/components/settings/unified/UnifiedSettingsModal'
|
import UnifiedSettingsModal from '@/components/settings/unified/UnifiedSettingsModal'
|
||||||
|
import DashboardShell from '@/components/dashboard/DashboardShell'
|
||||||
|
|
||||||
const ORG_SWITCH_KEY = 'pulse_switching_org'
|
const ORG_SWITCH_KEY = 'pulse_switching_org'
|
||||||
|
|
||||||
@@ -95,7 +96,8 @@ function LayoutInner({ children }: { children: React.ReactNode }) {
|
|||||||
const showOfflineBar = Boolean(auth.user && !isOnline)
|
const showOfflineBar = Boolean(auth.user && !isOnline)
|
||||||
// Site pages use DashboardShell with full sidebar — no Header needed
|
// Site pages use DashboardShell with full sidebar — no Header needed
|
||||||
const isSitePage = pathname.startsWith('/sites/') && pathname !== '/sites/new'
|
const isSitePage = pathname.startsWith('/sites/') && pathname !== '/sites/new'
|
||||||
const isHomePage = pathname === '/'
|
// 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
|
// Checkout page has its own minimal layout — no app header/footer
|
||||||
const isCheckoutPage = pathname.startsWith('/checkout')
|
const isCheckoutPage = pathname.startsWith('/checkout')
|
||||||
|
|
||||||
@@ -104,13 +106,12 @@ function LayoutInner({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// While auth is loading on a site or checkout page, render nothing to prevent flash of public header
|
// While auth is loading on a site or checkout page, render nothing to prevent flash of public header
|
||||||
if (auth.loading && (isSitePage || isCheckoutPage || isHomePage)) {
|
if (auth.loading && (isSitePage || isCheckoutPage || isDashboardPage)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticated site pages: full sidebar layout
|
// Authenticated site pages: DashboardShell provided by sites layout
|
||||||
// DashboardShell inside children handles everything
|
if (isAuthenticated && isSitePage) {
|
||||||
if (isAuthenticated && (isSitePage || isHomePage)) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showOfflineBar && <OfflineBanner isOnline={isOnline} />}
|
{showOfflineBar && <OfflineBanner isOnline={isOnline} />}
|
||||||
@@ -120,6 +121,17 @@ function LayoutInner({ children }: { children: React.ReactNode }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authenticated dashboard pages (home, integrations, pricing): wrap in DashboardShell
|
||||||
|
if (isAuthenticated && isDashboardPage) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showOfflineBar && <OfflineBanner isOnline={isOnline} />}
|
||||||
|
<DashboardShell siteId={null}>{children}</DashboardShell>
|
||||||
|
<UnifiedSettingsModal />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Checkout page: render children only (has its own layout)
|
// Checkout page: render children only (has its own layout)
|
||||||
if (isAuthenticated && isCheckoutPage) {
|
if (isAuthenticated && isCheckoutPage) {
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import PulseFAQ from '@/components/marketing/PulseFAQ'
|
|||||||
import { toast } from '@ciphera-net/ui'
|
import { toast } from '@ciphera-net/ui'
|
||||||
import { getAuthErrorMessage } from '@ciphera-net/ui'
|
import { getAuthErrorMessage } from '@ciphera-net/ui'
|
||||||
import { getSitesLimitForPlan } from '@/lib/plans'
|
import { getSitesLimitForPlan } from '@/lib/plans'
|
||||||
import DashboardShell from '@/components/dashboard/DashboardShell'
|
|
||||||
|
|
||||||
type SiteStatsMap = Record<string, { stats: Stats }>
|
type SiteStatsMap = Record<string, { stats: Stats }>
|
||||||
|
|
||||||
@@ -230,7 +229,6 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardShell siteId={null}>
|
|
||||||
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 pb-8">
|
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 pb-8">
|
||||||
{showFinishSetupBanner && (
|
{showFinishSetupBanner && (
|
||||||
<div className="mb-6 flex items-center justify-between gap-4 rounded-2xl border border-brand-orange/30 bg-brand-orange/10 px-4 py-3">
|
<div className="mb-6 flex items-center justify-between gap-4 rounded-2xl border border-brand-orange/30 bg-brand-orange/10 px-4 py-3">
|
||||||
@@ -374,6 +372,5 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DashboardShell>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ function usePageTitle() {
|
|||||||
return PAGE_TITLES[segment] ?? (segment ? segment.charAt(0).toUpperCase() + segment.slice(1) : 'Dashboard')
|
return PAGE_TITLES[segment] ?? (segment ? segment.charAt(0).toUpperCase() + segment.slice(1) : 'Dashboard')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HOME_PAGE_TITLES: Record<string, string> = {
|
||||||
|
'': 'Your Sites',
|
||||||
|
integrations: 'Integrations',
|
||||||
|
pricing: 'Pricing',
|
||||||
|
}
|
||||||
|
|
||||||
|
function useHomePageTitle() {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const segment = pathname.split('/').filter(Boolean)[0] ?? ''
|
||||||
|
return HOME_PAGE_TITLES[segment] ?? (segment ? segment.charAt(0).toUpperCase() + segment.slice(1) : 'Your Sites')
|
||||||
|
}
|
||||||
|
|
||||||
// Load sidebar only on the client — prevents SSR flash
|
// Load sidebar only on the client — prevents SSR flash
|
||||||
const Sidebar = dynamic(() => import('./Sidebar'), {
|
const Sidebar = dynamic(() => import('./Sidebar'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -58,7 +70,8 @@ function GlassTopBar({ siteId }: { siteId: string | null }) {
|
|||||||
}, [realtime])
|
}, [realtime])
|
||||||
|
|
||||||
const dashboardTitle = usePageTitle()
|
const dashboardTitle = usePageTitle()
|
||||||
const pageTitle = siteId ? dashboardTitle : 'Your Sites'
|
const homeTitle = useHomePageTitle()
|
||||||
|
const pageTitle = siteId ? dashboardTitle : homeTitle
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden md:flex items-center justify-between shrink-0 px-3 pt-1.5 pb-1">
|
<div className="hidden md:flex items-center justify-between shrink-0 px-3 pt-1.5 pb-1">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Image from 'next/image'
|
|||||||
import { Site } from '@/lib/api/sites'
|
import { Site } from '@/lib/api/sites'
|
||||||
import type { Stats } from '@/lib/api/stats'
|
import type { Stats } from '@/lib/api/stats'
|
||||||
import { formatNumber } from '@ciphera-net/ui'
|
import { formatNumber } from '@ciphera-net/ui'
|
||||||
import { BarChartIcon, SettingsIcon, BookOpenIcon, ExternalLinkIcon, Button } from '@ciphera-net/ui'
|
import { BarChartIcon, SettingsIcon, TrashIcon, BookOpenIcon, ExternalLinkIcon, Button } from '@ciphera-net/ui'
|
||||||
import { useAuth } from '@/lib/auth/context'
|
import { useAuth } from '@/lib/auth/context'
|
||||||
import { FAVICON_SERVICE_URL } from '@/lib/utils/favicon'
|
import { FAVICON_SERVICE_URL } from '@/lib/utils/favicon'
|
||||||
|
|
||||||
@@ -104,6 +104,13 @@ function SiteCard({ site, stats, statsLoading, onDelete, canDelete }: SiteCardPr
|
|||||||
View Dashboard
|
View Dashboard
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={`/sites/${site.id}/settings`}
|
||||||
|
className="flex items-center justify-center rounded-lg border border-neutral-200 px-3 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 text-neutral-500 hover:text-neutral-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2"
|
||||||
|
title="Site Settings"
|
||||||
|
>
|
||||||
|
<SettingsIcon className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -111,7 +118,7 @@ function SiteCard({ site, stats, statsLoading, onDelete, canDelete }: SiteCardPr
|
|||||||
className="flex items-center justify-center rounded-lg border border-neutral-200 px-3 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 text-neutral-500 hover:text-red-600 dark:hover:text-red-400 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
|
className="flex items-center justify-center rounded-lg border border-neutral-200 px-3 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 text-neutral-500 hover:text-red-600 dark:hover:text-red-400 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
|
||||||
title="Delete Site"
|
title="Delete Site"
|
||||||
>
|
>
|
||||||
<SettingsIcon className="h-4 w-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user