'use client'
import { useState, useEffect, useRef } from 'react'
import { Button, 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 }: { siteId: string; onDirtyChange?: (dirty: boolean) => 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 [snippetCopied, setSnippetCopied] = useState(false)
const [saving, setSaving] = useState(false)
const [isDirty, setIsDirty] = useState(false)
const initialRef = useRef('')
// Sync form state from site data — only on first load, not on SWR revalidation
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'),
})
hasInitialized.current = true
setIsDirty(false)
}, [site])
// Track dirty state
useEffect(() => {
if (!initialRef.current) return
const current = JSON.stringify({ collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths })
const dirty = current !== initialRef.current
setIsDirty(dirty)
onDirtyChange?.(dirty)
}, [collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths, onDirtyChange])
const handleSave = async () => {
setSaving(true)
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),
})
await mutate()
initialRef.current = JSON.stringify({ collectPagePaths, collectReferrers, collectDeviceInfo, collectScreenRes, collectGeoData, hideUnknownLocations, dataRetention, excludedPaths })
onDirtyChange?.(false)
toast.success('Privacy settings updated')
} catch {
toast.error('Failed to save')
} finally {
setSaving(false)
}
}
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 ? (
{
try {
await updatePageSpeedConfig(siteId, { enabled: psiConfig.enabled, frequency: v })
mutatePSIConfig()
toast.success('Check frequency updated')
} catch {
toast.error('Failed to update')
}
}}
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 ? : }
{/* Sticky save bar — only visible when dirty */}
{isDirty && (
Unsaved changes
{saving ? 'Saving...' : 'Save Changes'}
)}
)
}