Landing page redesign, dashboard improvements & new settings sections #67

Merged
uz1mani merged 60 commits from staging into main 2026-03-22 16:17:38 +00:00
68 changed files with 8490 additions and 1658 deletions
Showing only changes of commit b7e92abb40 - Show all commits

View File

@@ -809,9 +809,15 @@ export default function SiteSettingsPage() {
Add this script to your website to start tracking visitors. Choose your framework for setup instructions. Add this script to your website to start tracking visitors. Choose your framework for setup instructions.
</p> </p>
<ScriptSetupBlock <ScriptSetupBlock
site={{ domain: site.domain, name: site.name }} site={{ domain: site.domain, name: site.name, script_features: site.script_features }}
showFrameworkPicker showFrameworkPicker
className="mb-4" 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"> <div className="flex items-center gap-4 mt-4">

View File

@@ -37,6 +37,7 @@ type FeatureKey = (typeof FEATURES)[number]['key'] | 'frustration'
export interface ScriptSetupBlockSite { export interface ScriptSetupBlockSite {
domain: string domain: string
name?: string name?: string
script_features?: Record<string, unknown>
} }
interface ScriptSetupBlockProps { interface ScriptSetupBlockProps {
@@ -44,27 +45,39 @@ interface ScriptSetupBlockProps {
site: ScriptSetupBlockSite site: ScriptSetupBlockSite
/** Called when user copies the script (e.g. for analytics). */ /** Called when user copies the script (e.g. for analytics). */
onScriptCopy?: () => void onScriptCopy?: () => void
/** Called when features change so the parent can save to backend. */
onFeaturesChange?: (features: Record<string, unknown>) => void
/** Show framework picker. Default true. */ /** Show framework picker. Default true. */
showFrameworkPicker?: boolean showFrameworkPicker?: boolean
/** Optional class for the root wrapper. */ /** Optional class for the root wrapper. */
className?: string className?: string
} }
export default function ScriptSetupBlock({ const DEFAULT_FEATURES: Record<FeatureKey, boolean> = {
site,
onScriptCopy,
showFrameworkPicker = true,
className = '',
}: ScriptSetupBlockProps) {
const [features, setFeatures] = useState<Record<FeatureKey, boolean>>({
scroll: true, scroll: true,
'404': true, '404': true,
outbound: true, outbound: true,
downloads: true, downloads: true,
frustration: false, 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 [storage, setStorage] = useState(typeof sf.storage === 'string' ? sf.storage : 'local')
const [ttl, setTtl] = useState('24') const [ttl, setTtl] = useState(typeof sf.ttl === 'string' ? sf.ttl : '24')
const [framework, setFramework] = useState('') const [framework, setFramework] = useState('')
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
@@ -97,7 +110,11 @@ export default function ScriptSetupBlock({
}, [scriptSnippet, onScriptCopy]) }, [scriptSnippet, onScriptCopy])
const toggleFeature = (key: FeatureKey) => { 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 const selectedIntegration = framework ? getIntegration(framework) : null
@@ -201,7 +218,7 @@ export default function ScriptSetupBlock({
<Select <Select
variant="input" variant="input"
value={storage} value={storage}
onChange={setStorage} onChange={(v: string) => { setStorage(v); onFeaturesChange?.({ ...features, storage: v, ttl }) }}
options={STORAGE_OPTIONS} options={STORAGE_OPTIONS}
/> />
</div> </div>
@@ -213,7 +230,7 @@ export default function ScriptSetupBlock({
<Select <Select
variant="input" variant="input"
value={ttl} value={ttl}
onChange={setTtl} onChange={(v: string) => { setTtl(v); onFeaturesChange?.({ ...features, storage, ttl: v }) }}
options={TTL_OPTIONS} options={TTL_OPTIONS}
/> />
</div> </div>

View File

@@ -23,6 +23,8 @@ export interface Site {
hide_unknown_locations?: boolean hide_unknown_locations?: boolean
// Data retention (months); 0 = keep forever // Data retention (months); 0 = keep forever
data_retention_months?: number data_retention_months?: number
// Script feature toggles
script_features?: Record<string, unknown>
is_verified?: boolean is_verified?: boolean
created_at: string created_at: string
updated_at: string updated_at: string
@@ -49,6 +51,8 @@ export interface UpdateSiteRequest {
collect_screen_resolution?: boolean collect_screen_resolution?: boolean
// Bot and noise filtering // Bot and noise filtering
filter_bots?: boolean filter_bots?: boolean
// Script feature toggles
script_features?: Record<string, unknown>
// Hide unknown locations from stats // Hide unknown locations from stats
hide_unknown_locations?: boolean hide_unknown_locations?: boolean
// Data retention (months); 0 = keep forever // Data retention (months); 0 = keep forever