11/**
2- * Composable for managing PostHog analytics consent (GDPR compliance) .
2+ * Composable for managing PostHog analytics consent.
33 *
4- * By default, PostHog captures events. Users can opt out via this composable.
5- * The consent state is persisted in a cookie shared across subdomains
6- * (e.g. reqcore.com and app.reqcore.com) via the NUXT_PUBLIC_COOKIE_DOMAIN
7- * runtime config.
8- *
9- * Usage:
10- * const { hasConsented, acceptAnalytics, declineAnalytics } = useAnalyticsConsent()
4+ * Cookieless tracking is ALWAYS active — no consent needed.
5+ * Accepting analytics upgrades to full cookie-based tracking
6+ * (UTM, sessions, identity). Declining keeps cookieless mode.
117 */
128
13- import { flushPendingEvents , discardPendingEvents } from '~/composables/useTrack'
14-
159/** Cookie name — shared across reqcore-web and applirank */
1610export const CONSENT_COOKIE_NAME = 'reqcore-consent'
1711
@@ -24,7 +18,7 @@ export function useAnalyticsConsent() {
2418 // usePostHog() is auto-imported by @posthog/nuxt, but the module is
2519 // conditionally loaded. Replicate the safe accessor so this composable
2620 // works even when PostHog is not configured (CI, self-hosted without key).
27- const posthog = ( useNuxtApp ( ) as Record < string , unknown > ) . $posthog as ( ( ( ) => { opt_in_capturing : ( ) => void , opt_out_capturing : ( ) => void , capture : ( event : string , properties ?: Record < string , unknown > ) => void } ) | undefined )
21+ const posthog = ( useNuxtApp ( ) as Record < string , unknown > ) . $posthog as ( ( ) => import ( 'posthog-js' ) . PostHog ) | undefined
2822 const ph = posthog ?.( )
2923
3024 const cookieDomain = ( useRuntimeConfig ( ) . public as Record < string , string > ) . cookieDomain
@@ -54,50 +48,43 @@ export function useAnalyticsConsent() {
5448
5549 function acceptAnalytics ( ) {
5650 consentCookie . value = 'granted'
57- // Also clean up legacy key if it still exists
5851 if ( import . meta. client ) localStorage . removeItem ( LEGACY_STORAGE_KEY )
59- ph ?. opt_in_capturing ( )
60- // Capture the entry-page pageview now that the user has opted in.
61- // PostHog's init-time $pageview was suppressed by opt_out_capturing_by_default,
62- // and subsequent pushState events only fire after this call, so we must
63- // manually send one for the page the user is currently on.
52+ if ( ! ph ) return
53+ // Upgrade from cookieless to full cookie-based persistence
54+ ph . set_config ( {
55+ persistence : 'localStorage+cookie' ,
56+ person_profiles : 'identified_only' ,
57+ cross_subdomain_cookie : true ,
58+ } )
59+ // Register UTM + attribution now that we have persistence
6460 if ( import . meta. client ) {
65- const url = new URL ( window . location . href )
66- url . search = ''
67- url . hash = ''
68- ph ?. capture ( '$pageview' , { $current_url : url . toString ( ) } )
69- }
70- // Replay any events that were buffered before consent was granted
71- // (e.g. signup_submitted, org_created fired during the auth flow).
72- if ( import . meta. client ) {
73- flushPendingEvents ( )
61+ const params = new URLSearchParams ( window . location . search )
62+ const utmKeys = [ 'utm_source' , 'utm_medium' , 'utm_campaign' , 'utm_content' , 'utm_term' ] as const
63+ const utmProps : Record < string , string > = { }
64+ for ( const key of utmKeys ) {
65+ const val = params . get ( key )
66+ if ( val ) utmProps [ key ] = val
67+ }
68+ if ( Object . keys ( utmProps ) . length > 0 ) {
69+ ph . register ( utmProps )
70+ const firstTouch : Record < string , string > = { }
71+ for ( const [ k , v ] of Object . entries ( utmProps ) ) {
72+ firstTouch [ `initial_${ k } ` ] = v
73+ }
74+ ph . register_once ( firstTouch )
75+ }
76+ ph . register_once ( {
77+ initial_referrer : document . referrer || '$direct' ,
78+ initial_landing_page : window . location . pathname ,
79+ } )
80+ ph . capture ( 'consent_granted' )
7481 }
7582 }
7683
7784 function declineAnalytics ( ) {
7885 consentCookie . value = 'denied'
7986 if ( import . meta. client ) localStorage . removeItem ( LEGACY_STORAGE_KEY )
80- ph ?. opt_out_capturing ( )
81- // Discard any events buffered before the user declined.
82- if ( import . meta. client ) {
83- discardPendingEvents ( )
84- }
85- }
86-
87- // Apply stored consent on mount.
88- // PostHog defaults to opt_out_capturing_by_default: true (GDPR), so we only
89- // need to explicitly opt-in for users who previously granted consent.
90- if ( import . meta. client ) {
91- if ( consentCookie . value === 'granted' ) {
92- ph ?. opt_in_capturing ( )
93- // Replay any events buffered (in sessionStorage) before this page load —
94- // e.g. signup_completed or org_created tracked during a flow that ended
95- // with a hard window.location.href navigation.
96- flushPendingEvents ( )
97- }
98- else {
99- ph ?. opt_out_capturing ( )
100- }
87+ // No action needed — cookieless tracking continues without cookies or person profiles
10188 }
10289
10390 return {
0 commit comments