diff --git a/src/App.tsx b/src/App.tsx index a46c1dc..826b1d5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,23 +9,41 @@ import CookieBanner from './components/CookieBanner'; import PageTransition from './components/PageTransition'; import { ScrollProvider } from './components/ScrollContext'; import DebugOverlay from './components/DebugOverlay'; -import { logPageView } from './utils/analytics'; +import { logPageView, isCookieCategoryEnabled } from './utils/analytics'; import './styles/scrollbar.css'; -// RouteTracker Component für präzises Seiten-Tracking +// RouteTracker Component für präzises Seiten-Tracking - aber nur mit Zustimmung const RouteTracker = () => { const location = useLocation(); useEffect(() => { - logPageView(); + // Nur tracken, wenn die Analytics-Cookies akzeptiert wurden + if (isCookieCategoryEnabled('analytics')) { + logPageView(); + } }, [location]); return null; }; +// Funktion zum Blockieren von externen Google Fonts +const blockExternalGoogleFonts = () => { + // Suche nach allen Google Fonts Link-Elementen und entferne sie + const linkElements = document.querySelectorAll('link[href*="fonts.googleapis.com"]'); + linkElements.forEach(link => { + link.parentNode?.removeChild(link); + }); +}; + function App() { useEffect(() => { + // Benutzerdefiniertes Scrollbar-Styling aktivieren document.documentElement.classList.add('custom-scrollbar'); + + // Blockiere Google Fonts beim ersten Laden, sofern nicht explizit zugestimmt wurde + if (!isCookieCategoryEnabled('functional')) { + blockExternalGoogleFonts(); + } }, []); return ( @@ -37,7 +55,7 @@ function App() { - {/* Tracking bei Routenwechsel */} + {/* Tracking nur mit Zustimmung */} diff --git a/src/components/CookieBanner.tsx b/src/components/CookieBanner.tsx index 569de4e..ed80907 100644 --- a/src/components/CookieBanner.tsx +++ b/src/components/CookieBanner.tsx @@ -1,12 +1,12 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { initGA, logPageView } from '../utils/analytics'; +import { initGA, logPageView, initFBPixel, initFunctionalCookies } from '../utils/analytics'; // Feste Übersetzungen direkt in der Komponente const translations = { de: { title: "Cookie-Einstellungen", - message: "Diese Website verwendet Cookies, um Ihr Erlebnis zu verbessern. Daten werden nicht an Dritte weitergegeben.", + message: "Diese Website verwendet Cookies, um Ihr Erlebnis zu verbessern. Daten werden ohne Ihre Zustimmung nicht an Dritte weitergegeben.", acceptAll: "Alle akzeptieren", save: "Einstellungen speichern", decline: "Ablehnen", @@ -23,7 +23,7 @@ const translations = { }, en: { title: "Cookie Settings", - message: "This website uses cookies to enhance your browsing experience. The data will not be shared with third parties.", + message: "This website uses cookies to enhance your browsing experience. Data will not be shared with third parties without your consent.", acceptAll: "Accept All", save: "Save Settings", decline: "Decline All", @@ -40,7 +40,7 @@ const translations = { }, sr: { title: "Podešavanja kolačića", - message: "Ovaj sajt koristi kolačiće za bolje iskustvo pretraživanja. Podaci neće biti deljeni sa trećim licima.", + message: "Ovaj sajt koristi kolačiće za bolje iskustvo pretraživanja. Podaci neće biti deljeni sa trećim licima bez vaše saglasnosti.", acceptAll: "Prihvati sve", save: "Sačuvaj podešavanja", decline: "Odbij sve", @@ -76,6 +76,11 @@ const cookieServices = { name: 'Spracheinstellungen', provider: 'Eigentümer der Website', purpose: 'Speichert Ihre bevorzugte Sprache' + }, + { + name: 'Google Fonts', + provider: 'Google LLC', + purpose: 'Anzeige von Webschriften' } ] }, @@ -113,6 +118,31 @@ interface CookieSettings { functional: boolean; } +// Entfernt externe Google Fonts, falls keine Zustimmung vorliegt +const removeExternalGoogleFonts = () => { + // Suche nach allen Google Fonts Link-Elementen und entferne sie + const linkElements = document.querySelectorAll('link[href*="fonts.googleapis.com"]'); + linkElements.forEach(link => { + link.parentNode?.removeChild(link); + }); + + // Suche nach allen Google Fonts Script-Elementen und entferne sie + const scriptElements = document.querySelectorAll('script[src*="fonts.googleapis.com"]'); + scriptElements.forEach(script => { + script.parentNode?.removeChild(script); + }); +}; + +// Funktion zum Blockieren von Google Analytics falls noch keine Zustimmung vorliegt +const blockGoogleAnalytics = () => { + // Entferne alle bestehenden GA Cookies + document.cookie.split(';').forEach(function(c) { + if (c.trim().indexOf('_ga') === 0) { + document.cookie = c.trim().split('=')[0] + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;'; + } + }); +}; + const CookieBanner = () => { const { i18n } = useTranslation(); const [currentLang, setCurrentLang] = useState<'de' | 'en' | 'sr'>('de'); @@ -124,6 +154,32 @@ const CookieBanner = () => { functional: false }); + // Initialisiere Dienste basierend auf den Cookie-Einstellungen + const initializeServices = useCallback((settings: CookieSettings) => { + // Blockieren von Diensten, wenn nicht zugestimmt wurde + if (!settings.functional) { + removeExternalGoogleFonts(); + } + + if (!settings.analytics) { + blockGoogleAnalytics(); + } else { + // Initialisiere Google Analytics nur wenn ausdrücklich zugestimmt wurde + initGA(); + logPageView(); + } + + // Marketing-Dienste nur mit Zustimmung + if (settings.marketing) { + initFBPixel(); + } + + // Funktionale Dienste nur mit Zustimmung + if (settings.functional) { + initFunctionalCookies(); + } + }, []); + // Aktuelle Sprache erkennen useEffect(() => { const lang = i18n.language.substring(0, 2); @@ -139,6 +195,13 @@ const CookieBanner = () => { return translations[currentLang][key]; }; + // DSGVO: Unmittelbar beim ersten Laden blockiere alle nicht-essentiellen Dienste + useEffect(() => { + // Blockiere externe Dienste bis Zustimmung vorliegt + removeExternalGoogleFonts(); + blockGoogleAnalytics(); + }, []); + // Prüfe beim Laden, ob bereits Zustimmung vorhanden ist useEffect(() => { try { @@ -153,50 +216,11 @@ const CookieBanner = () => { } catch (error) { console.error('Error loading cookie settings:', error); } - }, []); - - // Initialisiere Dienste basierend auf den Cookie-Einstellungen - const initializeServices = (settings: CookieSettings) => { - // Initialisiere Google Analytics, wenn Analytics-Cookies akzeptiert wurden - if (settings.analytics) { - initGA(); - logPageView(); - } - - // Hier könnten weitere Dienste initialisiert werden - if (settings.marketing) { - // Marketing-Dienste initialisieren (z.B. Facebook Pixel) - initializeMarketingServices(); - } - - if (settings.functional) { - // Funktionale Dienste initialisieren - initializeFunctionalServices(); - } - }; - - // Initialisiere Marketing-Dienste - const initializeMarketingServices = () => { - // Facebook Pixel (Beispiel) - try { - if (typeof window !== 'undefined' && !window.fbq) { - // Facebook Pixel Code hier einfügen - console.log('Facebook Pixel würde jetzt initialisiert'); - } - } catch (e) { - console.error('Error initializing marketing services:', e); - } - }; - - // Initialisiere funktionale Dienste - const initializeFunctionalServices = () => { - // Beispiel für funktionale Dienste - console.log('Funktionale Dienste würden jetzt initialisiert'); - }; + }, [initializeServices]); const handleSaveSettings = () => { localStorage.setItem('cookieSettings', JSON.stringify(cookieSettings)); - localStorage.setItem('cookieConsent', 'true'); + localStorage.setItem('cookieConsent', cookieSettings.analytics || cookieSettings.marketing || cookieSettings.functional ? 'true' : 'false'); // Initialisiere Dienste basierend auf den ausgewählten Einstellungen initializeServices(cookieSettings); @@ -234,6 +258,9 @@ const CookieBanner = () => { localStorage.setItem('cookieConsent', 'false'); setCookieSettings(minimalSettings); + // Block all non-essential services + initializeServices(minimalSettings); + setShowSettings(false); }; diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts index 7aaeae8..6fa8535 100644 --- a/src/utils/analytics.ts +++ b/src/utils/analytics.ts @@ -1,12 +1,10 @@ import ReactGA from 'react-ga4'; const TRACKING_ID = 'G-N0PZBL7X18'; - -// Facebook Pixel ID - ersetze dies durch deine echte Pixel-ID -const FB_PIXEL_ID = 'XXXXXXXXXXXXXXXXX'; +const FB_PIXEL_ID = 'XXXXXXXXXXXXXXXXX'; // Ersetze mit deiner Facebook Pixel ID // Prüft, ob eine bestimmte Cookie-Kategorie aktiviert ist -const isCookieCategoryEnabled = (category: string): boolean => { +export const isCookieCategoryEnabled = (category: string): boolean => { try { const cookieSettings = localStorage.getItem('cookieSettings'); if (!cookieSettings) return false; @@ -19,10 +17,28 @@ const isCookieCategoryEnabled = (category: string): boolean => { } }; -// Google Analytics Initialisierung -export const initGA = () => { +// Entfernt alle Google Analytics Cookies +const removeGACookies = (): void => { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + const cookieName = cookie.split('=')[0]; + + // Entferne alle GA-Cookies + if (cookieName.startsWith('_ga') || + cookieName.startsWith('_gid') || + cookieName.startsWith('_gat')) { + document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + } + } +}; + +// Google Analytics Initialisierung - mit strikter Zustimmungsprüfung +export const initGA = (): void => { if (!isCookieCategoryEnabled('analytics')) { - return; // Keine Analytics-Initialisierung ohne Zustimmung + // Entferne bestehende GA-Cookies, falls keine Zustimmung + removeGACookies(); + return; } ReactGA.initialize(TRACKING_ID, { @@ -33,9 +49,9 @@ export const initGA = () => { }); }; -export const logPageView = () => { +export const logPageView = (): void => { if (!isCookieCategoryEnabled('analytics')) { - return; // Kein Tracking ohne Zustimmung + return; } ReactGA.send({ @@ -45,9 +61,9 @@ export const logPageView = () => { }); }; -export const trackEvent = (category: string, action: string, label?: string) => { +export const trackEvent = (category: string, action: string, label?: string): void => { if (!isCookieCategoryEnabled('analytics')) { - return; // Kein Tracking ohne Zustimmung + return; } ReactGA.event({ @@ -57,9 +73,9 @@ export const trackEvent = (category: string, action: string, label?: string) => }); }; -export const trackConversion = (value?: number) => { +export const trackConversion = (value?: number): void => { if (!isCookieCategoryEnabled('analytics')) { - return; // Kein Tracking ohne Zustimmung + return; } ReactGA.event({ @@ -69,9 +85,9 @@ export const trackConversion = (value?: number) => { }); }; -export const trackTiming = (category: string, variable: string, value: number) => { +export const trackTiming = (category: string, variable: string, value: number): void => { if (!isCookieCategoryEnabled('analytics')) { - return; // Kein Tracking ohne Zustimmung + return; } ReactGA.event({ @@ -82,19 +98,17 @@ export const trackTiming = (category: string, variable: string, value: number) = }); }; -// Facebook Pixel Initialisierung -export const initFBPixel = () => { +// Facebook Pixel Initialisierung - nur mit Zustimmung +export const initFBPixel = (): void => { if (!isCookieCategoryEnabled('marketing')) { - return; // Keine Marketing-Cookie-Initialisierung ohne Zustimmung + return; } if (typeof window !== 'undefined' && !window.fbq) { - // @ts-ignore - fbq wird dynamisch hinzugefügt - window.fbq = function() { - // @ts-ignore + // fbq ist nicht Teil des Window-Interfaces und wird zur Laufzeit hinzugefügt + window.fbq = function(...args: unknown[]): void { window._fbq = window._fbq || []; - // @ts-ignore - window._fbq.push(arguments); + window._fbq.push(args); }; // Facebook Pixel Script laden @@ -109,7 +123,7 @@ export const initFBPixel = () => { }; // Facebook Event tracking -export const trackFBEvent = (event: string, params?: any) => { +export const trackFBEvent = (event: string, params?: Record): void => { if (!isCookieCategoryEnabled('marketing') || typeof window === 'undefined' || !window.fbq) { return; } @@ -117,40 +131,67 @@ export const trackFBEvent = (event: string, params?: any) => { window.fbq('track', event, params); }; -// Funktionale Cookies initialisieren (Beispiel) -export const initFunctionalCookies = () => { +// Google Fonts Handling - nur mit funktionaler Cookie-Zustimmung +export const initGoogleFonts = (): void => { + if (!isCookieCategoryEnabled('functional')) { + // Entferne bestehende Google Fonts + const linkElements = document.querySelectorAll('link[href*="fonts.googleapis.com"]'); + linkElements.forEach(link => { + link.parentNode?.removeChild(link); + }); + return; + } + + // Vermeide Duplikate beim Laden + if (document.querySelector('link[href*="fonts.googleapis.com"]')) { + return; + } + + // Lokales Laden von Google Fonts als Alternative + // Code hier zur lokalen Einbindung der Schriftarten +}; + +// Funktionale Cookies initialisieren +export const initFunctionalCookies = (): void => { if (!isCookieCategoryEnabled('functional')) { return; } - // Hier könnten Funktionen für funktionale Cookies implementiert werden - console.log('Funktionale Cookies initialisiert'); + // Initialisiere Google Fonts wenn zugestimmt wurde + initGoogleFonts(); + + // Weitere funktionale Dienste könnten hier initialisiert werden }; // Typdefinitionen für globale Objekte declare global { interface Window { initializeAnalytics: () => void; - fbq: (...args: any[]) => void; - _fbq: any[]; + fbq: (...args: unknown[]) => void; + _fbq: unknown[]; } } // Analytics-Funktionen global verfügbar machen (für den Cookie-Banner) -window.initializeAnalytics = () => { - initGA(); - logPageView(); +if (typeof window !== 'undefined') { + window.initializeAnalytics = (): void => { + // Prüfe die einzelnen Cookie-Kategorien und initialisiere entsprechend + if (isCookieCategoryEnabled('analytics')) { + initGA(); + logPageView(); + } else { + removeGACookies(); + } - // Wenn die entsprechenden Cookie-Kategorien aktiviert sind, - // initialisiere auch andere Dienste - if (isCookieCategoryEnabled('marketing')) { - initFBPixel(); - } + if (isCookieCategoryEnabled('marketing')) { + initFBPixel(); + } - if (isCookieCategoryEnabled('functional')) { - initFunctionalCookies(); - } -}; + if (isCookieCategoryEnabled('functional')) { + initFunctionalCookies(); + } + }; +} -// Export der Namespace-Erweiterung +// Export für TypeScript export {}; \ No newline at end of file