'use client' import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { getSite, updateSite, resetSiteData, deleteSite, type Site } from '@/lib/api/sites' import { getRealtime } from '@/lib/api/stats' import { toast } from 'sonner' import LoadingOverlay from '@/components/LoadingOverlay' import { APP_URL, API_URL } from '@/lib/api/client' import { motion, AnimatePresence } from 'framer-motion' import { GearIcon, GlobeIcon, FileTextIcon, CheckIcon, CopyIcon, ExclamationTriangleIcon, LightningBoltIcon } from '@radix-ui/react-icons' const TIMEZONES = [ 'UTC', 'America/New_York', 'America/Los_Angeles', 'America/Chicago', 'America/Toronto', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Amsterdam', 'Asia/Tokyo', 'Asia/Singapore', 'Asia/Dubai', 'Australia/Sydney', 'Pacific/Auckland', ] export default function SiteSettingsPage() { const params = useParams() const router = useRouter() const siteId = params.id as string const [site, setSite] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [activeTab, setActiveTab] = useState<'general' | 'visibility' | 'data'>('general') const [formData, setFormData] = useState({ name: '', timezone: 'UTC', is_public: false, password: '', excluded_paths: '' }) const [scriptCopied, setScriptCopied] = useState(false) const [linkCopied, setLinkCopied] = useState(false) const [verificationStatus, setVerificationStatus] = useState<'idle' | 'checking' | 'success' | 'error'>('idle') useEffect(() => { loadSite() }, [siteId]) const loadSite = async () => { try { setLoading(true) const data = await getSite(siteId) setSite(data) setFormData({ name: data.name, timezone: data.timezone || 'UTC', is_public: data.is_public || false, password: '', // Don't show existing password excluded_paths: (data.excluded_paths || []).join('\n') }) } catch (error: any) { toast.error('Failed to load site: ' + (error.message || 'Unknown error')) } finally { setLoading(false) } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setSaving(true) try { const excludedPathsArray = formData.excluded_paths .split('\n') .map(p => p.trim()) .filter(p => p.length > 0) await updateSite(siteId, { name: formData.name, timezone: formData.timezone, is_public: formData.is_public, password: formData.password || undefined, excluded_paths: excludedPathsArray }) toast.success('Site updated successfully') loadSite() } catch (error: any) { toast.error('Failed to update site: ' + (error.message || 'Unknown error')) } finally { setSaving(false) } } const handleResetData = async () => { if (!confirm('Are you sure you want to delete ALL data for this site? This action cannot be undone.')) { return } try { await resetSiteData(siteId) toast.success('All site data has been reset') } catch (error: any) { toast.error('Failed to reset data: ' + (error.message || 'Unknown error')) } } const handleDeleteSite = async () => { const confirmation = prompt('To confirm deletion, please type the site domain:') if (confirmation !== site?.domain) { if (confirmation) toast.error('Domain does not match') return } try { await deleteSite(siteId) toast.success('Site deleted successfully') router.push('/') } catch (error: any) { toast.error('Failed to delete site: ' + (error.message || 'Unknown error')) } } const handleVerify = async () => { if (!site?.domain) return setVerificationStatus('checking') let attempts = 0 const maxAttempts = 10 // 1. Open site const protocol = site.domain.includes('http') ? '' : 'https://' const verificationUrl = `${protocol}${site.domain}/?utm_source=ciphera_verify&_t=${Date.now()}` window.open(verificationUrl, '_blank') // 2. Poll for success const checkInterval = setInterval(async () => { attempts++ try { const data = await getRealtime(siteId) if (data.visitors > 0) { clearInterval(checkInterval) setVerificationStatus('success') toast.success('Connection established!') } else if (attempts >= maxAttempts) { clearInterval(checkInterval) setVerificationStatus('error') } } catch (e) { // Ignore errors while polling } }, 2000) } const copyScript = () => { const script = `` navigator.clipboard.writeText(script) setScriptCopied(true) toast.success('Script copied to clipboard') setTimeout(() => setScriptCopied(false), 2000) } const copyLink = () => { const link = `${APP_URL}/share/${siteId}` navigator.clipboard.writeText(link) setLinkCopied(true) toast.success('Link copied to clipboard') setTimeout(() => setLinkCopied(false), 2000) } if (loading) { return } if (!site) { return (

Site not found

) } return (

Site Settings

Manage settings for {site.domain}

{/* Sidebar Navigation */} {/* Content Area */}
{activeTab === 'general' && (

General Configuration

Update your site details and tracking script.

setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-neutral-50/50 dark:bg-neutral-900/50 focus:bg-white dark:focus:bg-neutral-900 focus:border-brand-orange focus:ring-4 focus:ring-brand-orange/10 outline-none transition-all duration-200 dark:text-white" />

Domain cannot be changed after creation

Tracking Script

Add this script to your website to start tracking visitors.

{``}
{/* Status Text */} {verificationStatus === 'checking' && ( Waiting for signal from {site.domain}... )}
{/* Error State - Inline Troubleshooting */} {verificationStatus === 'error' && (

We couldn't detect the script.

Please ensure you opened the new tab and check the following:

  • Ad blockers are disabled on your site
  • The script is placed in the <head> tag
  • You are not testing on localhost (unless configured)
)}

Danger Zone

Irreversible actions for your site.

Reset Data

Delete all stats and events. This cannot be undone.

Delete Site

Permanently delete this site and all data.

)} {activeTab === 'visibility' && (

Visibility Settings

Manage who can view your dashboard.

Public Dashboard

Allow anyone with the link to view this dashboard

{formData.is_public && (

Share this link with others to view the dashboard.

setFormData({ ...formData, password: e.target.value })} placeholder="Leave empty to keep existing password (if any)" className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white focus:bg-white dark:focus:bg-neutral-900 focus:border-brand-orange focus:ring-4 focus:ring-brand-orange/10 outline-none transition-all duration-200" />

Set a password to restrict access to the public dashboard.

)}
)} {activeTab === 'data' && (

Data & Privacy

Manage data collection and filtering.