'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { Select, Toggle, toast, Spinner } from '@ciphera-net/ui' import { useSite, useSubscription, usePageSpeedConfig } from '@/lib/swr/dashboard' import { updateSite } from '@/lib/api/sites' import { updatePageSpeedConfig } from '@/lib/api/pagespeed' import { getRetentionOptionsForPlan, formatRetentionMonths } from '@/lib/plans' import { generatePrivacySnippet } from '@/lib/utils/privacySnippet' import { Copy, CheckCircle } from '@phosphor-icons/react' import Link from 'next/link' const GEO_OPTIONS = [ { value: 'full', label: 'Full (country, region, city)' }, { value: 'country', label: 'Country only' }, { value: 'none', label: 'Disabled' }, ] function PrivacyToggle({ label, desc, checked, onToggle }: { label: string; desc: string; checked: boolean; onToggle: () => void }) { return (

{label}

{desc}

) } export default function SitePrivacyTab({ siteId, onDirtyChange, onRegisterSave }: { siteId: string; onDirtyChange?: (dirty: boolean) => void; onRegisterSave?: (fn: () => Promise) => void }) { const { data: site, mutate } = useSite(siteId) const { data: subscription, error: subscriptionError, mutate: mutateSubscription } = useSubscription() const { data: psiConfig, mutate: mutatePSIConfig } = usePageSpeedConfig(siteId) const [collectPagePaths, setCollectPagePaths] = useState(true) const [collectReferrers, setCollectReferrers] = useState(true) const [collectDeviceInfo, setCollectDeviceInfo] = useState(true) const [collectScreenRes, setCollectScreenRes] = useState(true) const [collectGeoData, setCollectGeoData] = useState('full') const [hideUnknownLocations, setHideUnknownLocations] = useState(false) const [dataRetention, setDataRetention] = useState(6) const [excludedPaths, setExcludedPaths] = useState('') const [psiFrequency, setPsiFrequency] = useState('weekly') const [snippetCopied, setSnippetCopied] = useState(false) const initialRef = useRef('') // Sync form state — only on first load, skip dirty tracking until ready const hasInitialized = useRef(false) useEffect(() => { if (!site || hasInitialized.current) return setCollectPagePaths(site.collect_page_paths ?? true) setCollectReferrers(site.collect_referrers ?? true) setCollectDeviceInfo(site.collect_device_info ?? true) setCollectScreenRes(site.collect_screen_resolution ?? true) setCollectGeoData(site.collect_geo_data ?? 'full') setHideUnknownLocations(site.hide_unknown_locations ?? false) setDataRetention(site.data_retention_months ?? 6) setExcludedPaths((site.excluded_paths || []).join('\n')) initialRef.current = JSON.stringify({ collectPagePaths: site.collect_page_paths ?? true, collectReferrers: site.collect_referrers ?? true, collectDeviceInfo: site.collect_device_info ?? true, collectScreenRes: site.collect_screen_resolution ?? true, collectGeoData: site.collect_geo_data ?? 'full', hideUnknownLocations: site.hide_unknown_locations ?? false, dataRetention: site.data_retention_months ?? 6, excludedPaths: (site.excluded_paths || []).join('\n'), psiFrequency: 'weekly', }) hasInitialized.current = true }, [site]) // Sync PSI frequency separately — update both state AND snapshot when it first loads const psiInitialized = useRef(false) useEffect(() => { if (!psiConfig || psiInitialized.current) return const freq = psiConfig.frequency || 'weekly' setPsiFrequency(freq) // Update the snapshot to include the real PSI frequency so it doesn't show as dirty if (initialRef.current) { const snap = JSON.parse(initialRef.current) snap.psiFrequency = freq initialRef.current = JSON.stringify(snap) } psiInitialized.current = true }, [psiConfig]) // Track dirty state useEffect(() => { if (!initialRef.current) return const current = JSON.stringify({ collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths, psiFrequency }) onDirtyChange?.(current !== initialRef.current) }, [collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths, psiFrequency, onDirtyChange]) const handleSave = useCallback(async () => { try { await updateSite(siteId, { name: site?.name || '', collect_page_paths: collectPagePaths, collect_referrers: collectReferrers, collect_device_info: collectDeviceInfo, collect_screen_resolution: collectScreenRes, collect_geo_data: collectGeoData as 'full' | 'country' | 'none', hide_unknown_locations: hideUnknownLocations, data_retention_months: dataRetention, excluded_paths: excludedPaths.split('\n').map(p => p.trim()).filter(Boolean), }) // Save PSI frequency separately if it changed if (psiConfig?.enabled && psiFrequency !== (psiConfig.frequency || 'weekly')) { await updatePageSpeedConfig(siteId, { enabled: psiConfig.enabled, frequency: psiFrequency }) mutatePSIConfig() } await mutate() initialRef.current = JSON.stringify({ collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths, psiFrequency }) onDirtyChange?.(false) toast.success('Privacy settings updated') } catch { toast.error('Failed to save') } }, [siteId, site?.name, collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths, psiFrequency, psiConfig, mutatePSIConfig, mutate, onDirtyChange]) // Register save handler with modal useEffect(() => { onRegisterSave?.(handleSave) }, [handleSave, onRegisterSave]) if (!site) return
return (

Data & Privacy

Control what data is collected from your visitors.

setCollectPagePaths(v => !v)} /> setCollectReferrers(v => !v)} /> setCollectDeviceInfo(v => !v)} /> setCollectScreenRes(v => !v)} /> setHideUnknownLocations(v => !v)} />

Geographic data

Controls location granularity. "Disabled" collects no geographic data at all.

setDataRetention(Number(v))} options={getRetentionOptionsForPlan(subscription?.plan_id).map(o => ({ value: String(o.value), label: o.label }))} variant="input" className="min-w-[160px]" />
{subscription && (

Your {subscription.plan_id?.includes('pro') ? 'Pro' : 'Free'} plan supports up to {formatRetentionMonths(Math.max(...getRetentionOptionsForPlan(subscription.plan_id).map(o => o.value)))} of data retention.

)} {(!subscription || subscription.plan_id?.includes('free')) && (

Upgrade for longer retention.

)}
{/* Path Filtering */}

Path Filtering