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 [siteStats, setSiteStats] = useState<SiteStatsMap>({})
const [subscription, setSubscription] = useState<SubscriptionDetails | null>(null) const [subscription, setSubscription] = useState<SubscriptionDetails | null>(null)
const [showFinishSetupBanner, setShowFinishSetupBanner] = useState(true) const [showFinishSetupBanner, setShowFinishSetupBanner] = useState(true)
const [deleteModalSite, setDeleteModalSite] = useState<Site | null>(null)
const [deletedSites, setDeletedSites] = useState<Site[]>([]) const [deletedSites, setDeletedSites] = useState<Site[]>([])
const [permanentDeleteSiteModal, setPermanentDeleteSiteModal] = useState<Site | null>(null) 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) => { const handleRestore = async (id: string) => {
try { try {
await restoreSite(id) await restoreSite(id)
@@ -312,18 +306,9 @@ export default function HomePage() {
)} )}
{(sitesLoading || sites.length > 0) && ( {(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 <DeleteSiteModal
open={!!permanentDeleteSiteModal} open={!!permanentDeleteSiteModal}
onClose={() => setPermanentDeleteSiteModal(null)} onClose={() => setPermanentDeleteSiteModal(null)}

View File

@@ -303,7 +303,11 @@ export default function UnifiedSettingsModal() {
useEffect(() => { useEffect(() => {
if (!isOpen || !user?.org_id) return 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-]+)/) const match = window.location.pathname.match(/\/sites\/([a-f0-9-]+)/)
if (match) { if (match) {
setActiveSiteId(match[1]) setActiveSiteId(match[1])

View File

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

View File

@@ -2,7 +2,7 @@
import { createContext, useContext, useState, useCallback } from 'react' 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 { interface UnifiedSettingsContextType {
isOpen: boolean isOpen: boolean