Handle Hotjar consent via local storage

This commit is contained in:
Remi
2025-10-18 14:48:13 +02:00
parent 5fcdef485f
commit 016e8ebe84
3 changed files with 114 additions and 57 deletions

View File

@@ -5,12 +5,27 @@
</v-app>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from '#imports';
import { computed, onMounted, watch } from 'vue';
import { useHotjar, useRoute, useState } from '#imports';
import { useAuthStore } from '~/stores/auth';
import { HOTJAR_LOCAL_STORAGE_KEY, useCookieConsent } from '~/composables/useCookieConsent';
const HOTJAR_ID = 6522897;
const HOTJAR_SCRIPT_VERSION = 6;
const HOTJAR_INIT_DELAY = 500;
declare global {
interface Window {
hj?: ((...args: unknown[]) => void) & { q?: unknown[][] };
_hjOptOut?: boolean;
}
}
const route = useRoute();
const authStore = useAuthStore();
const { hasConsent, analyticsEnabled } = useCookieConsent();
const { initialize } = useHotjar();
const hotjarInitialized = useState('hotjar-initialized', () => false);
const showCookieBanner = computed(() => {
if (!authStore.isAuthenticated) {
@@ -19,4 +34,82 @@ const showCookieBanner = computed(() => {
return route.path === '/';
});
const setHotjarOptOut = (optOut: boolean) => {
if (typeof window === 'undefined') {
return;
}
window._hjOptOut = optOut;
if (optOut && typeof window.hj === 'function') {
try {
window.hj('event', 'cookie_consent_opt_out');
} catch {
// ignore errors when notifying Hotjar of opt-out
}
}
};
const persistLocalPreference = (value: 'granted' | 'denied' | null) => {
if (typeof window === 'undefined') {
return;
}
if (value === null) {
window.localStorage.removeItem(HOTJAR_LOCAL_STORAGE_KEY);
return;
}
window.localStorage.setItem(HOTJAR_LOCAL_STORAGE_KEY, value);
};
const scheduleHotjarInitialization = () => {
if (hotjarInitialized.value || typeof window === 'undefined') {
return;
}
hotjarInitialized.value = true;
window.setTimeout(() => {
initialize(HOTJAR_ID, HOTJAR_SCRIPT_VERSION);
}, HOTJAR_INIT_DELAY);
};
onMounted(() => {
if (typeof window === 'undefined') {
return;
}
const storedPreference = window.localStorage.getItem(HOTJAR_LOCAL_STORAGE_KEY);
if (storedPreference === 'granted') {
setHotjarOptOut(false);
scheduleHotjarInitialization();
}
if (storedPreference === 'denied') {
setHotjarOptOut(true);
}
watch(
[() => hasConsent.value, () => analyticsEnabled.value],
([consent, enabled]) => {
if (!consent) {
persistLocalPreference(null);
setHotjarOptOut(true);
return;
}
const localValue = enabled ? 'granted' : 'denied';
persistLocalPreference(localValue);
setHotjarOptOut(!enabled);
if (enabled) {
scheduleHotjarInitialization();
}
},
{ immediate: true }
);
});
</script>

View File

@@ -14,6 +14,7 @@ type CookieConsentValue = {
const CONSENT_COOKIE_NAME = 'osq-cookie-consent';
const CONSENT_VERSION = 1;
const SIX_MONTHS_IN_SECONDS = 60 * 60 * 24 * 180;
export const HOTJAR_LOCAL_STORAGE_KEY = 'osq-hotjar-consent';
export const useCookieConsent = () => {
const consentCookie = useCookie<CookieConsentValue | null>(CONSENT_COOKIE_NAME, {
@@ -37,6 +38,24 @@ export const useCookieConsent = () => {
{ deep: true }
);
if (process.client) {
watch(
consentState,
(value) => {
if (!value) {
window.localStorage.removeItem(HOTJAR_LOCAL_STORAGE_KEY);
return;
}
window.localStorage.setItem(
HOTJAR_LOCAL_STORAGE_KEY,
value.preferences.analytics ? 'granted' : 'denied'
);
},
{ deep: true }
);
}
const hasConsent = computed(() => consentState.value !== null);
const analyticsEnabled = computed(() => {

View File

@@ -1,55 +0,0 @@
import { defineNuxtPlugin } from '#app';
import { watch } from 'vue';
import { useCookieConsent } from '~/composables/useCookieConsent';
import { useHotjar } from '#imports';
declare global {
interface Window {
hj?: ((...args: unknown[]) => void) & { q?: unknown[][] };
_hjOptOut?: boolean;
}
}
const setHotjarOptOut = (optOut: boolean) => {
if (typeof window === 'undefined') {
return;
}
window._hjOptOut = optOut;
if (optOut && typeof window.hj === 'function') {
try {
window.hj('event', 'cookie_consent_opt_out');
} catch {
// ignore errors from Hotjar when shutting down
}
}
};
export default defineNuxtPlugin(() => {
if (!import.meta.client) {
return;
}
const { initialize } = useHotjar();
const hasInitialized = useState('hotjar-initialized', () => false);
const { analyticsEnabled } = useCookieConsent();
watch(
() => analyticsEnabled.value,
(enabled) => {
if (enabled) {
setHotjarOptOut(false);
if (!hasInitialized.value) {
initialize();
hasInitialized.value = true;
}
} else {
setHotjarOptOut(true);
hasInitialized.value = false;
}
},
{ immediate: true }
);
});