'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 (
)
}
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.
{/* Data Retention */}
Data Retention
{subscriptionError && (
Plan limits could not be loaded.
mutateSubscription()} className="text-xs font-medium text-amber-400 hover:text-amber-300">Retry
)}
Keep raw event data for
Events older than this are automatically deleted. Aggregated daily stats are kept permanently.
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 */}
{/* PageSpeed Monitoring */}
PageSpeed Monitoring
Check frequency
How often PageSpeed Insights runs automated checks.
{psiConfig?.enabled ? (
setPsiFrequency(v)}
options={[
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
{ value: 'monthly', label: 'Monthly' },
]}
variant="input"
className="min-w-[140px]"
/>
) : (
Not enabled
)}
{/* Privacy Policy */}
For your privacy policy
Copy the text below into your Privacy Policy. It updates automatically based on your saved settings.
This is provided for convenience and is not legal advice. Consult a lawyer for compliance requirements.
{
navigator.clipboard.writeText(generatePrivacySnippet(site))
setSnippetCopied(true)
toast.success('Privacy snippet copied')
setTimeout(() => setSnippetCopied(false), 2000)
}}
className="absolute top-3 right-3 p-2 rounded-lg bg-neutral-700 hover:bg-neutral-600 text-neutral-300 transition-colors"
>
{snippetCopied ? : }
)
}