From 1c5184988bf0eb8634e4267058365fdde5346b7d Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 18 Jan 2026 22:15:34 +0100 Subject: [PATCH] feat(analytics): add site verification modal --- app/sites/[id]/settings/page.tsx | 124 +++---------- components/sites/VerificationModal.tsx | 230 +++++++++++++++++++++++++ 2 files changed, 252 insertions(+), 102 deletions(-) create mode 100644 components/sites/VerificationModal.tsx diff --git a/app/sites/[id]/settings/page.tsx b/app/sites/[id]/settings/page.tsx index 73b475d..2f104b2 100644 --- a/app/sites/[id]/settings/page.tsx +++ b/app/sites/[id]/settings/page.tsx @@ -6,6 +6,7 @@ import { getSite, updateSite, resetSiteData, deleteSite, type Site } from '@/lib import { getRealtime } from '@/lib/api/stats' import { toast } from 'sonner' import LoadingOverlay from '@/components/LoadingOverlay' +import VerificationModal from '@/components/sites/VerificationModal' import { APP_URL, API_URL } from '@/lib/api/client' import { motion, AnimatePresence } from 'framer-motion' import { @@ -15,7 +16,7 @@ import { CheckIcon, CopyIcon, ExclamationTriangleIcon, - LightningBoltIcon + LightningBoltIcon, } from '@radix-ui/react-icons' const TIMEZONES = [ @@ -54,7 +55,7 @@ export default function SiteSettingsPage() { }) const [scriptCopied, setScriptCopied] = useState(false) const [linkCopied, setLinkCopied] = useState(false) - const [verificationStatus, setVerificationStatus] = useState<'idle' | 'checking' | 'success' | 'error'>('idle') + const [showVerificationModal, setShowVerificationModal] = useState(false) useEffect(() => { loadSite() @@ -134,38 +135,6 @@ export default function SiteSettingsPage() { } } - 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) @@ -336,75 +305,21 @@ export default function SiteSettingsPage() { -
-
- - - {/* 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)
  • -
- -
-
-
-
- )} -
+
+ +

+ Check if your site is sending data correctly. +

+
+ + setShowVerificationModal(false)} + site={site} + /> - ) } diff --git a/components/sites/VerificationModal.tsx b/components/sites/VerificationModal.tsx new file mode 100644 index 0000000..806fc93 --- /dev/null +++ b/components/sites/VerificationModal.tsx @@ -0,0 +1,230 @@ +'use client' + +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { motion, AnimatePresence } from 'framer-motion' +import { + Cross2Icon, + ExternalLinkIcon, + CheckCircledIcon, + ExclamationTriangleIcon, + LightningBoltIcon +} from '@radix-ui/react-icons' +import { Site } from '@/lib/api/sites' +import { getRealtime } from '@/lib/api/stats' +import { toast } from 'sonner' + +interface VerificationModalProps { + isOpen: boolean + onClose: () => void + site: Site +} + +export default function VerificationModal({ isOpen, onClose, site }: VerificationModalProps) { + const [mounted, setMounted] = useState(false) + const [status, setStatus] = useState<'idle' | 'checking' | 'success' | 'error'>('idle') + const [attempts, setAttempts] = useState(0) + + useEffect(() => { + setMounted(true) + return () => setMounted(false) + }, []) + + useEffect(() => { + if (isOpen) { + setStatus('idle') + setAttempts(0) + } + }, [isOpen]) + + // * Polling Logic + useEffect(() => { + let interval: NodeJS.Timeout + const maxAttempts = 30 // 60 seconds (2s interval) + + if (status === 'checking') { + interval = setInterval(async () => { + setAttempts(prev => { + if (prev >= maxAttempts) { + setStatus('error') + return prev + } + return prev + 1 + }) + + try { + const data = await getRealtime(site.id) + if (data.visitors > 0) { + setStatus('success') + toast.success('Connection established!') + } + } catch (e) { + // Ignore errors + } + }, 2000) + } + + return () => clearInterval(interval) + }, [status, site.id]) + + const handleStartVerification = () => { + const protocol = site.domain.includes('http') ? '' : 'https://' + const verificationUrl = `${protocol}${site.domain}/?utm_source=ciphera_verify&_t=${Date.now()}` + + // * Open site + window.open(verificationUrl, '_blank') + + // * Start polling + setStatus('checking') + setAttempts(0) + } + + if (!mounted) return null + + return createPortal( + + {isOpen && ( + <> + {/* Backdrop */} + + + {/* Modal */} +
+ + {/* Header */} +
+

+ Verify Installation +

+ +
+ + {/* Content */} +
+ {status === 'idle' && ( +
+
+
+ +
+
+

How this works

+

+ We will open your website in a new tab. Keep it open while we check if the script sends back a signal. +

+
+
+ + +
+ )} + + {status === 'checking' && ( +
+
+
+
+
+
+

+ Checking connection... +

+

+ Waiting for signal from {site.domain} +

+
+
+ )} + + {status === 'success' && ( +
+
+ +
+
+

+ You're all set! +

+

+ We are successfully receiving data from your website. +

+
+ +
+ )} + + {status === 'error' && ( +
+
+
+ +
+

+ Connection Timed Out +

+
+ +
+

+ Troubleshooting Checklist: +

+
    +
  • Did the new tab open successfully?
  • +
  • Is your ad blocker disabled?
  • +
  • Is the script inside the <head> tag?
  • +
  • Are you running on a valid domain (not localhost)?
  • +
+
+ +
+ + +
+
+ )} +
+ +
+ + )} + , + document.body + ) +}