'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 [verifying, setVerifying] = useState(false) 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 setVerifying(true) let attempts = 0 const maxAttempts = 10 // Try for 20 seconds (10 * 2s) // 1. Open the site in a new tab with a cache-busting/tracking param // We use a timestamp to ensure it's a fresh navigation const protocol = site.domain.includes('http') ? '' : 'https://' const verificationUrl = `${protocol}${site.domain}/?utm_source=ciphera_verify&_t=${Date.now()}` window.open(verificationUrl, '_blank') toast.info('Checking for connection... (Please ensure the new tab loads)') // 2. Poll for success const checkInterval = setInterval(async () => { attempts++ try { const data = await getRealtime(siteId) // Success condition: We see visitors if (data.visitors > 0) { clearInterval(checkInterval) setVerifying(false) toast.success('Success! Installation verified.') } // Failure condition: Time out else if (attempts >= maxAttempts) { clearInterval(checkInterval) setVerifying(false) toast.warning( 'We couldn\'t detect the connection yet. Please checking the following:', { description: '1. Ad blockers are disabled\n2. The script is in the \n3. You are not on localhost (unless configured)', duration: 6000 } ) } } catch (e) { // Ignore network errors during polling } }, 2000) // Poll every 2 seconds } 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.

{``}

This will open your site in a new tab to check connection.

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.