'use client' /** * Shared block: script snippet with feature toggles, storage config, and framework guide link. * Used on welcome (step 5), /sites/new (step 2), and site settings. */ import { useState, useCallback, useMemo } from 'react' import Link from 'next/link' import { API_URL, APP_URL } from '@/lib/api/client' import { integrations, getIntegration } from '@/lib/integrations' import { toast, Toggle, Select, CheckIcon } from '@ciphera-net/ui' const FRAMEWORKS = integrations.filter((i) => i.category === 'framework').slice(0, 10) const STORAGE_OPTIONS = [ { value: 'local', label: 'Across all tabs' }, { value: 'session', label: 'Single tab only' }, ] const TTL_OPTIONS = [ { value: '24', label: '24 hours' }, { value: '48', label: '2 days' }, { value: '168', label: '7 days' }, { value: '720', label: '30 days' }, ] const FEATURES = [ { key: 'scroll', label: 'Scroll depth', description: 'Track 25 / 50 / 75 / 100%', attr: 'data-no-scroll' }, { key: '404', label: '404 detection', description: 'Auto-detect error pages', attr: 'data-no-404' }, { key: 'outbound', label: 'Outbound links', description: 'Track external link clicks', attr: 'data-no-outbound' }, { key: 'downloads', label: 'File downloads', description: 'Track PDF, ZIP, and more', attr: 'data-no-downloads' }, ] as const type FeatureKey = (typeof FEATURES)[number]['key'] | 'frustration' export interface ScriptSetupBlockSite { domain: string name?: string } interface ScriptSetupBlockProps { /** Site domain (and optional name for display). */ site: ScriptSetupBlockSite /** Called when user copies the script (e.g. for analytics). */ onScriptCopy?: () => void /** Show framework picker. Default true. */ showFrameworkPicker?: boolean /** Optional class for the root wrapper. */ className?: string } export default function ScriptSetupBlock({ site, onScriptCopy, showFrameworkPicker = true, className = '', }: ScriptSetupBlockProps) { const [features, setFeatures] = useState>({ scroll: true, '404': true, outbound: true, downloads: true, frustration: false, }) const [storage, setStorage] = useState('local') const [ttl, setTtl] = useState('24') const [framework, setFramework] = useState('') const [copied, setCopied] = useState(false) // * Build the script snippet dynamically based on toggles const scriptSnippet = useMemo(() => { const attrs: string[] = [ 'defer', `data-domain="${site.domain}"`, `data-api="${API_URL}"`, ] if (storage === 'session') attrs.push('data-storage="session"') if (storage === 'local' && ttl !== '24') attrs.push(`data-storage-ttl="${ttl}"`) for (const f of FEATURES) { if (!features[f.key]) attrs.push(f.attr) } attrs.push(`src="${APP_URL}/script.js"`) let script = `` if (features.frustration) { script += `\n` } return script }, [site.domain, features, storage, ttl]) const copyScript = useCallback(() => { navigator.clipboard.writeText(scriptSnippet) setCopied(true) toast.success('Script copied to clipboard') onScriptCopy?.() setTimeout(() => setCopied(false), 2000) }, [scriptSnippet, onScriptCopy]) const toggleFeature = (key: FeatureKey) => { setFeatures((prev) => ({ ...prev, [key]: !prev[key] })) } const selectedIntegration = framework ? getIntegration(framework) : null return (
{/* ── Script snippet ──────────────────────────────────────────────── */}
{/* * Orange accent bar */}
{/* * Terminal dots */}
tracking script
            {scriptSnippet}
          
{/* ── Feature toggles ─────────────────────────────────────────────── */}

Features

{FEATURES.map((f) => (
{f.label} {f.description}
toggleFeature(f.key)} />
))}
{/* * Frustration — full-width, visually distinct as add-on */}
Frustration tracking Rage clicks & dead clicks · Loads separate add-on script
toggleFeature('frustration')} />
{/* ── Storage + TTL ───────────────────────────────────────────────── */}

Visitor identity

How returning visitors are recognized. Stricter settings increase privacy but may raise unique visitor counts.

)}
{/* ── Framework guide ─────────────────────────────────────────────── */} {showFrameworkPicker && (

Setup guide

All integrations →
{FRAMEWORKS.map((fw) => ( ))}
{selectedIntegration && ( See full {selectedIntegration.name} guide → )}
)}
) }