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:
17
app/page.tsx
17
app/page.tsx
@@ -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)}
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user