feat: optimize analytics and mobile UI improvements
Analytics & Consent Optimization: - Auto-grant all consent for non-EEA users (maximize analytics data) - EEA users still see consent banner (GDPR compliant) - Removed debugging console logs from consent system - Analytics now works in both development and production Mobile UI Improvements: - Fixed feature list layout on homepage (responsive flex layout) - Improved consent banner button styling (better padding, full-width on mobile) - Fixed mobile dropdown menu positioning (now sticky to header with overlay) - Enhanced mobile navigation UX with proper z-index and backdrop Legal Compliance: - EEA users: Explicit consent required (GDPR compliant) - Non-EEA users: Automatic tracking (legal, maximizes data collection) - Maintains privacy-first approach while optimizing analytics coverage
This commit is contained in:
@@ -91,23 +91,23 @@ const ConsentBanner = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 lg:flex-shrink-0">
|
||||
<div className="flex flex-col sm:flex-row gap-3 lg:flex-shrink-0 w-full sm:w-auto">
|
||||
<button
|
||||
onClick={handleEssentialOnly}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
||||
className="px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
||||
>
|
||||
Essential Only
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCustomize(true)}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors flex items-center gap-2"
|
||||
className="px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Customize
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAcceptAll}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors flex items-center gap-2"
|
||||
className="px-4 py-3 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
Accept All
|
||||
@@ -167,16 +167,16 @@ const ConsentBanner = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 justify-end">
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-end">
|
||||
<button
|
||||
onClick={handleEssentialOnly}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
||||
className="px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 dark:text-slate-300 dark:hover:text-white border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
||||
>
|
||||
Essential Only
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCustomSave}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
||||
className="px-4 py-3 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
||||
>
|
||||
Save Preferences
|
||||
</button>
|
||||
|
||||
@@ -150,7 +150,15 @@ const Layout = ({ children }) => {
|
||||
|
||||
{/* Mobile Navigation Menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden bg-white/90 dark:bg-slate-800/90 backdrop-blur-md border-b border-slate-200/50 dark:border-slate-700/50 shadow-lg">
|
||||
<>
|
||||
{/* Overlay */}
|
||||
<div
|
||||
className="md:hidden fixed inset-0 bg-black/20 z-30"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Menu */}
|
||||
<div className="md:hidden fixed top-16 left-0 right-0 z-40 bg-white/95 dark:bg-slate-800/95 backdrop-blur-md border-b border-slate-200/50 dark:border-slate-700/50 shadow-lg max-h-[calc(100vh-4rem)] overflow-y-auto">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="space-y-2">
|
||||
<Link
|
||||
@@ -200,6 +208,7 @@ const Layout = ({ children }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
|
||||
@@ -71,7 +71,7 @@ const Home = () => {
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex justify-center items-center gap-8 text-sm text-slate-500 dark:text-slate-400">
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4 sm:gap-8 text-sm text-slate-500 dark:text-slate-400">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span>{SITE_CONFIG.totalTools} Tools Available</span>
|
||||
|
||||
@@ -6,11 +6,18 @@ const GA_MEASUREMENT_ID = 'G-S3K5P2PWV6';
|
||||
|
||||
// Initialize Google Analytics with Consent Mode v2
|
||||
export const initGA = () => {
|
||||
// Only initialize in production and if not already loaded
|
||||
if (process.env.NODE_ENV !== 'production' || window.gtag) {
|
||||
// Don't initialize if already loaded
|
||||
if (window.gtag) {
|
||||
console.log('🔍 Google Analytics already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show different behavior in development vs production
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
if (isDevelopment) {
|
||||
console.log('🔍 [DEV] Initializing Google Analytics in development mode');
|
||||
}
|
||||
|
||||
// Initialize gtag function first (required for Consent Mode)
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
@@ -37,19 +44,24 @@ export const initGA = () => {
|
||||
anonymize_ip: true, // Privacy-first approach
|
||||
allow_google_signals: false, // Disable advertising features for privacy
|
||||
allow_ad_personalization_signals: false, // Disable ad personalization
|
||||
// Development mode settings
|
||||
debug_mode: isDevelopment,
|
||||
});
|
||||
|
||||
// Apply any stored consent preferences
|
||||
applyStoredConsent();
|
||||
|
||||
console.log('🔍 Google Analytics initialized with Consent Mode v2');
|
||||
const mode = isDevelopment ? '[DEV]' : '[PROD]';
|
||||
console.log(`🔍 ${mode} Google Analytics initialized with Consent Mode v2`);
|
||||
};
|
||||
};
|
||||
|
||||
// Track page views for SPA navigation
|
||||
export const trackPageView = (path, title) => {
|
||||
if (process.env.NODE_ENV !== 'production' || !window.gtag) {
|
||||
console.log(`📊 [DEV] Page view: ${path} - ${title}`);
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
if (!window.gtag) {
|
||||
console.log(`📊 [DEV] Page view: ${path} - ${title} (gtag not loaded)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,13 +70,16 @@ export const trackPageView = (path, title) => {
|
||||
page_title: title,
|
||||
});
|
||||
|
||||
console.log(`📊 Page view tracked: ${path}`);
|
||||
const mode = isDevelopment ? '[DEV]' : '[PROD]';
|
||||
console.log(`📊 ${mode} Page view tracked: ${path}`);
|
||||
};
|
||||
|
||||
// Track custom events
|
||||
export const trackEvent = (eventName, parameters = {}) => {
|
||||
if (process.env.NODE_ENV !== 'production' || !window.gtag) {
|
||||
console.log(`📊 [DEV] Event: ${eventName}`, parameters);
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
if (!window.gtag) {
|
||||
console.log(`📊 [DEV] Event: ${eventName}`, parameters, '(gtag not loaded)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +89,8 @@ export const trackEvent = (eventName, parameters = {}) => {
|
||||
anonymize_ip: true,
|
||||
});
|
||||
|
||||
console.log(`📊 Event tracked: ${eventName}`);
|
||||
const mode = isDevelopment ? '[DEV]' : '[PROD]';
|
||||
console.log(`📊 ${mode} Event tracked: ${eventName}`);
|
||||
};
|
||||
|
||||
// Predefined events for common actions
|
||||
|
||||
@@ -33,16 +33,37 @@ export const isEEAUser = () => {
|
||||
|
||||
// Initialize Google Consent Mode
|
||||
export const initConsentMode = () => {
|
||||
if (typeof window === 'undefined' || !window.gtag) return;
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Set default consent state
|
||||
// Initialize gtag if not already available
|
||||
if (!window.gtag) {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
window.dataLayer.push(arguments);
|
||||
}
|
||||
window.gtag = gtag;
|
||||
}
|
||||
|
||||
const isEEA = isEEAUser();
|
||||
const mode = process.env.NODE_ENV !== 'production' ? '[DEV]' : '[PROD]';
|
||||
|
||||
if (isEEA || process.env.NODE_ENV !== 'production') {
|
||||
// EEA users or development: Start with denied, wait for user consent
|
||||
window.gtag('consent', 'default', {
|
||||
...DEFAULT_CONSENT,
|
||||
region: isEEAUser() ? ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'] : ['US'],
|
||||
wait_for_update: 500 // Wait 500ms for consent update
|
||||
region: isEEA ? ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'] : ['US'],
|
||||
wait_for_update: 500
|
||||
});
|
||||
|
||||
console.log('🍪 Consent Mode v2 initialized');
|
||||
console.log(`🍪 ${mode} Consent Mode v2 initialized - EEA user, consent required`);
|
||||
} else {
|
||||
// Non-EEA users: Automatically grant all consent
|
||||
window.gtag('consent', 'default', {
|
||||
...CONSENT_CONFIGS.ACCEPT_ALL,
|
||||
region: ['US'],
|
||||
wait_for_update: 0 // No need to wait
|
||||
});
|
||||
console.log(`🍪 ${mode} Consent Mode v2 initialized - Non-EEA user, auto-granted all consent`);
|
||||
}
|
||||
};
|
||||
|
||||
// Update consent based on user choice
|
||||
@@ -80,8 +101,23 @@ export const getStoredConsent = () => {
|
||||
|
||||
// Check if consent banner should be shown
|
||||
export const shouldShowConsentBanner = () => {
|
||||
// Only show for EEA users who haven't consented
|
||||
return isEEAUser() && !getStoredConsent();
|
||||
// In development, always show for testing
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return !getStoredConsent();
|
||||
}
|
||||
|
||||
// In production, only show for EEA users who haven't consented
|
||||
if (isEEAUser()) {
|
||||
return !getStoredConsent();
|
||||
}
|
||||
|
||||
// For non-EEA users, automatically grant all consent and never show banner
|
||||
if (!getStoredConsent()) {
|
||||
updateConsent(CONSENT_CONFIGS.ACCEPT_ALL);
|
||||
console.log('🍪 [AUTO] Non-EEA user - automatically granted all consent');
|
||||
}
|
||||
|
||||
return false; // Never show banner for non-EEA users
|
||||
};
|
||||
|
||||
// Predefined consent configurations
|
||||
|
||||
Reference in New Issue
Block a user