'use client' import { useEffect, useState } from 'react' import Link from 'next/link' import { motion } from 'framer-motion' import { useAuth } from '@/lib/auth/context' import { initiateOAuthFlow } from '@/lib/api/oauth' import { listSites, listDeletedSites, restoreSite, type Site } from '@/lib/api/sites' import { getStats } from '@/lib/api/stats' import type { Stats } from '@/lib/api/stats' import { getSubscription, type SubscriptionDetails } from '@/lib/api/billing' import { LoadingOverlay } from '@ciphera-net/ui' import SiteList from '@/components/sites/SiteList' import DeleteSiteModal from '@/components/sites/DeleteSiteModal' import { Button } from '@ciphera-net/ui' import { XIcon } from '@ciphera-net/ui' import { Cookie, ShieldCheck, Code, Lightning, ArrowRight, GithubLogo } from '@phosphor-icons/react' import DashboardDemo from '@/components/marketing/DashboardDemo' import FeatureSections from '@/components/marketing/FeatureSections' import ComparisonCards from '@/components/marketing/ComparisonCards' import CTASection from '@/components/marketing/CTASection' import PulseFAQ from '@/components/marketing/PulseFAQ' import { toast } from '@ciphera-net/ui' import { getAuthErrorMessage } from '@ciphera-net/ui' import { getSitesLimitForPlan } from '@/lib/plans' type SiteStatsMap = Record export default function HomePage() { const { user, loading: authLoading } = useAuth() const [sites, setSites] = useState([]) const [sitesLoading, setSitesLoading] = useState(true) const [siteStats, setSiteStats] = useState({}) const [subscription, setSubscription] = useState(null) const [showFinishSetupBanner, setShowFinishSetupBanner] = useState(true) const [deletedSites, setDeletedSites] = useState([]) const [permanentDeleteSiteModal, setPermanentDeleteSiteModal] = useState(null) useEffect(() => { if (user?.org_id) { loadSites() loadSubscription() } }, [user]) useEffect(() => { if (sites.length === 0) { setSiteStats({}) return } let cancelled = false const today = new Date().toISOString().split('T')[0] const emptyStats: Stats = { pageviews: 0, visitors: 0, bounce_rate: 0, avg_duration: 0 } const load = async () => { const results = await Promise.allSettled( sites.map(async (site) => { const statsRes = await getStats(site.id, today, today) return { siteId: site.id, stats: statsRes } }) ) if (cancelled) return const map: SiteStatsMap = {} results.forEach((r, i) => { const site = sites[i] if (r.status === 'fulfilled') { map[site.id] = { stats: r.value.stats } } else { map[site.id] = { stats: emptyStats } } }) setSiteStats(map) } load() return () => { cancelled = true } }, [sites]) useEffect(() => { if (typeof window === 'undefined') return if (localStorage.getItem('pulse_welcome_completed') === 'true') setShowFinishSetupBanner(false) }, [user?.org_id]) useEffect(() => { if (typeof window === 'undefined') return const params = new URLSearchParams(window.location.search) if (params.get('trial_started') === '1') { toast.success('Your trial is active. You can add sites and start tracking.') params.delete('trial_started') const newUrl = params.toString() ? `${window.location.pathname}?${params}` : window.location.pathname window.history.replaceState({}, '', newUrl) } }, []) const loadSites = async () => { try { setSitesLoading(true) const data = await listSites() setSites(Array.isArray(data) ? data : []) try { const deleted = await listDeletedSites() setDeletedSites(deleted) } catch { setDeletedSites([]) } } catch (error: unknown) { toast.error(getAuthErrorMessage(error) || 'Failed to load your sites') setSites([]) } finally { setSitesLoading(false) } } const loadSubscription = async () => { try { const sub = await getSubscription() setSubscription(sub) } catch { setSubscription(null) } } const handleRestore = async (id: string) => { try { await restoreSite(id) toast.success('Site restored successfully') loadSites() } catch (error: unknown) { toast.error(getAuthErrorMessage(error) || 'Failed to restore site') } } const handlePermanentDelete = (id: string) => { const site = deletedSites.find((s) => s.id === id) if (site) setPermanentDeleteSiteModal(site) } if (authLoading) { return } if (!user) { return ( <> {/* HERO — compact headline + live demo */}
Analytics without the{' '} surveillance. Respect your users' privacy while getting the insights you need. No cookies, no IP tracking, fully GDPR compliant. Cookie-free | Open source client | GDPR compliant | Under 2KB
{/* Live Dashboard Demo */}
) } // * Wait for organization context before rendering SiteList to avoid "Organization Required" flash if (user && !user.org_id) { return } return (
{showFinishSetupBanner && (

Finish setting up your workspace and add your first site.

)}

Your Sites

Manage your analytics sites and view insights.

{(() => { const siteLimit = getSitesLimitForPlan(subscription?.plan_id) const atLimit = siteLimit != null && sites.length >= siteLimit return atLimit ? (
Limit reached ({sites.length}/{siteLimit})
{deletedSites.length > 0 && (

You have a site pending deletion. Restore it or permanently delete it to free the slot.

)}
) : null })() ?? ( )}
{!sitesLoading && sites.length === 0 && (
Set up your first site

Add your first site

Connect a domain to start collecting privacy-friendly analytics. You can add more sites later from the dashboard.

)} {(sitesLoading || sites.length > 0) && ( )} setPermanentDeleteSiteModal(null)} onDeleted={loadSites} siteName={permanentDeleteSiteModal?.name || ''} siteDomain={permanentDeleteSiteModal?.domain || ''} siteId={permanentDeleteSiteModal?.id || ''} permanentOnly /> {deletedSites.length > 0 && (

Scheduled for Deletion

{deletedSites.map((site) => { const purgeAt = site.deleted_at ? new Date(new Date(site.deleted_at).getTime() + 7 * 24 * 60 * 60 * 1000) : null const daysLeft = purgeAt ? Math.max(0, Math.ceil((purgeAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24))) : 0 return (
{site.name} {site.domain} Deleting in {daysLeft} day{daysLeft !== 1 ? 's' : ''}
) })}
)}
) }