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:
dwindown
2025-09-24 01:15:20 +07:00
parent 2e67a2bca2
commit 57655410ab
5 changed files with 90 additions and 29 deletions

View File

@@ -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>

View File

@@ -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 */}

View File

@@ -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>

View File

@@ -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

View File

@@ -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;
// Initialize gtag if not already available
if (!window.gtag) {
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
window.gtag = gtag;
}
// Set default consent state
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
});
console.log('🍪 Consent Mode v2 initialized');
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: 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(`🍪 ${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