diff --git a/components/sites/ScriptSetupBlock.tsx b/components/sites/ScriptSetupBlock.tsx index fe3c44d..08874e8 100644 --- a/components/sites/ScriptSetupBlock.tsx +++ b/components/sites/ScriptSetupBlock.tsx @@ -1,18 +1,44 @@ 'use client' /** - * Shared block: framework picker, tracking script snippet with copy, and integration guide links. + * 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 } from 'react' +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 } from '@ciphera-net/ui' -import { CheckIcon } from '@ciphera-net/ui' +import { toast, Toggle, Select, CheckIcon } from '@ciphera-net/ui' -const POPULAR_INTEGRATIONS = integrations.filter((i) => i.category === 'framework').slice(0, 10) +const FRAMEWORK_OPTIONS = [ + { value: '', label: 'Choose framework' }, + ...integrations + .filter((i) => i.category === 'framework') + .slice(0, 10) + .map((i) => ({ value: i.id, label: i.name })), +] + +const STORAGE_OPTIONS = [ + { value: 'local', label: 'Cross-tab (localStorage)' }, + { value: 'session', label: 'Per-tab (sessionStorage)' }, +] + +const TTL_OPTIONS = [ + { value: '24', label: '24 hours' }, + { value: '48', label: '48 hours' }, + { 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 @@ -24,7 +50,7 @@ interface ScriptSetupBlockProps { site: ScriptSetupBlockSite /** Called when user copies the script (e.g. for analytics). */ onScriptCopy?: () => void - /** Show framework picker and "View all integrations" / "See full guide" links. Default true. */ + /** Show framework picker. Default true. */ showFrameworkPicker?: boolean /** Optional class for the root wrapper. */ className?: string @@ -36,94 +62,194 @@ export default function ScriptSetupBlock({ showFrameworkPicker = true, className = '', }: ScriptSetupBlockProps) { - const [selectedIntegrationSlug, setSelectedIntegrationSlug] = useState(null) - const [scriptCopied, setScriptCopied] = useState(false) + 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(() => { - const script = `` - navigator.clipboard.writeText(script) - setScriptCopied(true) + navigator.clipboard.writeText(scriptSnippet) + setCopied(true) toast.success('Script copied to clipboard') onScriptCopy?.() - setTimeout(() => setScriptCopied(false), 2000) - }, [site.domain, onScriptCopy]) + setTimeout(() => setCopied(false), 2000) + }, [scriptSnippet, onScriptCopy]) + + const toggleFeature = (key: FeatureKey) => { + setFeatures((prev) => ({ ...prev, [key]: !prev[key] })) + } + + const selectedIntegration = framework ? getIntegration(framework) : null return (
- {showFrameworkPicker && ( - <> -

- Add the script to your site -

-

- Choose your framework for setup instructions. -

-
- {POPULAR_INTEGRATIONS.map((int) => ( - - ))} -
-

- - View all integrations → - -

- - )} - -
- - {``} - -

- Default: cross-tab (localStorage). Optional: data-storage="session" to opt out (per-tab, ephemeral). Optional: data-storage-ttl="48" to set expiry in hours (default: 24). -

-

- Optional: add {``} for rage click and dead click detection. -

- + {/* ── Script snippet ──────────────────────────────────────────────── */} +
+
+ + Your tracking script + + +
+
+          {scriptSnippet}
+        
- {showFrameworkPicker && selectedIntegrationSlug && getIntegration(selectedIntegrationSlug) && ( -

- - See full {getIntegration(selectedIntegrationSlug)!.name} guide → - -

+ {/* ── 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 +

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

+ Setup guide +

+
+
+