feat: DashboardShell for all auth pages, site settings modal from home

- layout-content wraps integrations/pricing in DashboardShell
- GlassTopBar derives title per page (Integrations, Pricing, etc.)
- Site card gear icon opens settings modal with siteId context
- Removed delete button from site cards (accessible via site settings)
- Extended InitialTab to accept optional siteId for cross-page use
This commit is contained in:
Usman Baig
2026-03-28 19:42:42 +01:00
parent c36c1b0696
commit 663abc9b9e
4 changed files with 17 additions and 43 deletions

View File

@@ -33,7 +33,6 @@ export default function HomePage() {
const [siteStats, setSiteStats] = useState<SiteStatsMap>({})
const [subscription, setSubscription] = useState<SubscriptionDetails | null>(null)
const [showFinishSetupBanner, setShowFinishSetupBanner] = useState(true)
const [deleteModalSite, setDeleteModalSite] = useState<Site | null>(null)
const [deletedSites, setDeletedSites] = useState<Site[]>([])
const [permanentDeleteSiteModal, setPermanentDeleteSiteModal] = useState<Site | null>(null)
@@ -119,11 +118,6 @@ export default function HomePage() {
}
}
const handleDelete = (id: string) => {
const site = sites.find((s) => s.id === id)
if (site) setDeleteModalSite(site)
}
const handleRestore = async (id: string) => {
try {
await restoreSite(id)
@@ -312,18 +306,9 @@ export default function HomePage() {
)}
{(sitesLoading || sites.length > 0) && (
<SiteList sites={sites} siteStats={siteStats} loading={sitesLoading} onDelete={handleDelete} />
<SiteList sites={sites} siteStats={siteStats} loading={sitesLoading} />
)}
<DeleteSiteModal
open={!!deleteModalSite}
onClose={() => setDeleteModalSite(null)}
onDeleted={loadSites}
siteName={deleteModalSite?.name || ''}
siteDomain={deleteModalSite?.domain || ''}
siteId={deleteModalSite?.id || ''}
/>
<DeleteSiteModal
open={!!permanentDeleteSiteModal}
onClose={() => setPermanentDeleteSiteModal(null)}

View File

@@ -303,7 +303,11 @@ export default function UnifiedSettingsModal() {
useEffect(() => {
if (!isOpen || !user?.org_id) return
if (typeof window !== 'undefined') {
if (initTab?.siteId) {
// Site ID passed explicitly (e.g. from home page site card)
setActiveSiteId(initTab.siteId)
if (!initTab?.context) setContext('site')
} else if (typeof window !== 'undefined') {
const match = window.location.pathname.match(/\/sites\/([a-f0-9-]+)/)
if (match) {
setActiveSiteId(match[1])

View File

@@ -5,8 +5,8 @@ import Image from 'next/image'
import { Site } from '@/lib/api/sites'
import type { Stats } from '@/lib/api/stats'
import { formatNumber } from '@ciphera-net/ui'
import { BarChartIcon, SettingsIcon, TrashIcon, BookOpenIcon, ExternalLinkIcon, Button } from '@ciphera-net/ui'
import { useAuth } from '@/lib/auth/context'
import { BarChartIcon, SettingsIcon, BookOpenIcon, ExternalLinkIcon, Button } from '@ciphera-net/ui'
import { useUnifiedSettings } from '@/lib/unified-settings-context'
import { FAVICON_SERVICE_URL } from '@/lib/utils/favicon'
export type SiteStatsMap = Record<string, { stats: Stats }>
@@ -15,18 +15,16 @@ interface SiteListProps {
sites: Site[]
siteStats: SiteStatsMap
loading: boolean
onDelete: (id: string) => void
}
interface SiteCardProps {
site: Site
stats: Stats | null
statsLoading: boolean
onDelete: (id: string) => void
canDelete: boolean
}
function SiteCard({ site, stats, statsLoading, onDelete, canDelete }: SiteCardProps) {
function SiteCard({ site, stats, statsLoading }: SiteCardProps) {
const { openUnifiedSettings } = useUnifiedSettings()
const visitors24h = stats?.visitors ?? 0
const pageviews = stats?.pageviews ?? 0
@@ -104,31 +102,20 @@ function SiteCard({ site, stats, statsLoading, onDelete, canDelete }: SiteCardPr
View Dashboard
</Button>
</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"
<button
type="button"
onClick={() => openUnifiedSettings({ context: 'site', tab: 'general', siteId: site.id })}
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 cursor-pointer"
title="Site Settings"
>
<SettingsIcon className="h-4 w-4" />
</Link>
{canDelete && (
<button
type="button"
onClick={() => onDelete(site.id)}
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"
>
<TrashIcon className="h-4 w-4" />
</button>
)}
</button>
</div>
</div>
)
}
export default function SiteList({ sites, siteStats, loading, onDelete }: SiteListProps) {
const { user } = useAuth()
const canDelete = user?.role === 'owner' || user?.role === 'admin'
export default function SiteList({ sites, siteStats, loading }: SiteListProps) {
if (loading) {
return (
@@ -172,8 +159,6 @@ export default function SiteList({ sites, siteStats, loading, onDelete }: SiteLi
site={site}
stats={data?.stats ?? null}
statsLoading={!data}
onDelete={onDelete}
canDelete={canDelete}
/>
)
})}

View File

@@ -2,7 +2,7 @@
import { createContext, useContext, useState, useCallback } from 'react'
type InitialTab = { context?: 'site' | 'workspace' | 'account'; tab?: string } | null
type InitialTab = { context?: 'site' | 'workspace' | 'account'; tab?: string; siteId?: string } | null
interface UnifiedSettingsContextType {
isOpen: boolean