feat: persist script feature toggles to backend
Features (scroll, 404, outbound, downloads, frustration, storage, ttl) are saved to site.script_features JSONB column on every toggle change. Values are read from the site object on load.
This commit is contained in:
@@ -809,9 +809,15 @@ export default function SiteSettingsPage() {
|
||||
Add this script to your website to start tracking visitors. Choose your framework for setup instructions.
|
||||
</p>
|
||||
<ScriptSetupBlock
|
||||
site={{ domain: site.domain, name: site.name }}
|
||||
site={{ domain: site.domain, name: site.name, script_features: site.script_features }}
|
||||
showFrameworkPicker
|
||||
className="mb-4"
|
||||
onFeaturesChange={async (features) => {
|
||||
try {
|
||||
await updateSite(siteId, { name: site.name, script_features: features })
|
||||
mutateSite()
|
||||
} catch { /* silent — not critical */ }
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
|
||||
@@ -37,6 +37,7 @@ type FeatureKey = (typeof FEATURES)[number]['key'] | 'frustration'
|
||||
export interface ScriptSetupBlockSite {
|
||||
domain: string
|
||||
name?: string
|
||||
script_features?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface ScriptSetupBlockProps {
|
||||
@@ -44,27 +45,39 @@ interface ScriptSetupBlockProps {
|
||||
site: ScriptSetupBlockSite
|
||||
/** Called when user copies the script (e.g. for analytics). */
|
||||
onScriptCopy?: () => void
|
||||
/** Called when features change so the parent can save to backend. */
|
||||
onFeaturesChange?: (features: Record<string, unknown>) => 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<Record<FeatureKey, boolean>>({
|
||||
const DEFAULT_FEATURES: Record<FeatureKey, boolean> = {
|
||||
scroll: true,
|
||||
'404': true,
|
||||
outbound: true,
|
||||
downloads: true,
|
||||
frustration: false,
|
||||
}
|
||||
|
||||
export default function ScriptSetupBlock({
|
||||
site,
|
||||
onScriptCopy,
|
||||
onFeaturesChange,
|
||||
showFrameworkPicker = true,
|
||||
className = '',
|
||||
}: ScriptSetupBlockProps) {
|
||||
const sf = site.script_features || {}
|
||||
const [features, setFeatures] = useState<Record<FeatureKey, boolean>>({
|
||||
scroll: sf.scroll != null ? Boolean(sf.scroll) : DEFAULT_FEATURES.scroll,
|
||||
'404': sf['404'] != null ? Boolean(sf['404']) : DEFAULT_FEATURES['404'],
|
||||
outbound: sf.outbound != null ? Boolean(sf.outbound) : DEFAULT_FEATURES.outbound,
|
||||
downloads: sf.downloads != null ? Boolean(sf.downloads) : DEFAULT_FEATURES.downloads,
|
||||
frustration: sf.frustration != null ? Boolean(sf.frustration) : DEFAULT_FEATURES.frustration,
|
||||
})
|
||||
const [storage, setStorage] = useState('local')
|
||||
const [ttl, setTtl] = useState('24')
|
||||
const [storage, setStorage] = useState(typeof sf.storage === 'string' ? sf.storage : 'local')
|
||||
const [ttl, setTtl] = useState(typeof sf.ttl === 'string' ? sf.ttl : '24')
|
||||
const [framework, setFramework] = useState('')
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
@@ -97,7 +110,11 @@ export default function ScriptSetupBlock({
|
||||
}, [scriptSnippet, onScriptCopy])
|
||||
|
||||
const toggleFeature = (key: FeatureKey) => {
|
||||
setFeatures((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setFeatures((prev) => {
|
||||
const next = { ...prev, [key]: !prev[key] }
|
||||
onFeaturesChange?.({ ...next, storage, ttl })
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const selectedIntegration = framework ? getIntegration(framework) : null
|
||||
@@ -201,7 +218,7 @@ export default function ScriptSetupBlock({
|
||||
<Select
|
||||
variant="input"
|
||||
value={storage}
|
||||
onChange={setStorage}
|
||||
onChange={(v: string) => { setStorage(v); onFeaturesChange?.({ ...features, storage: v, ttl }) }}
|
||||
options={STORAGE_OPTIONS}
|
||||
/>
|
||||
</div>
|
||||
@@ -213,7 +230,7 @@ export default function ScriptSetupBlock({
|
||||
<Select
|
||||
variant="input"
|
||||
value={ttl}
|
||||
onChange={setTtl}
|
||||
onChange={(v: string) => { setTtl(v); onFeaturesChange?.({ ...features, storage, ttl: v }) }}
|
||||
options={TTL_OPTIONS}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface Site {
|
||||
hide_unknown_locations?: boolean
|
||||
// Data retention (months); 0 = keep forever
|
||||
data_retention_months?: number
|
||||
// Script feature toggles
|
||||
script_features?: Record<string, unknown>
|
||||
is_verified?: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
@@ -49,6 +51,8 @@ export interface UpdateSiteRequest {
|
||||
collect_screen_resolution?: boolean
|
||||
// Bot and noise filtering
|
||||
filter_bots?: boolean
|
||||
// Script feature toggles
|
||||
script_features?: Record<string, unknown>
|
||||
// Hide unknown locations from stats
|
||||
hide_unknown_locations?: boolean
|
||||
// Data retention (months); 0 = keep forever
|
||||
|
||||
Reference in New Issue
Block a user