From 1e34038b1eb94fe45d72fc97540f8d836c94929e Mon Sep 17 00:00:00 2001 From: Reece Browne <74901996+reecebrowne@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:57:04 +0000 Subject: [PATCH] various cookie banner fixes (#5027) # Description of Changes --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- frontend/src/core/components/AppProviders.tsx | 13 ++- .../onboarding/hooks/useOnboardingFlow.ts | 50 +---------- .../src/core/components/shared/Footer.tsx | 4 +- .../core/contexts/CookieConsentContext.tsx | 45 ---------- frontend/src/core/hooks/useCookieConsent.ts | 85 ++++++------------- frontend/src/core/pages/HomePage.tsx | 6 +- .../components/shared/UpgradeBanner.tsx | 4 - 7 files changed, 38 insertions(+), 169 deletions(-) delete mode 100644 frontend/src/core/contexts/CookieConsentContext.tsx diff --git a/frontend/src/core/components/AppProviders.tsx b/frontend/src/core/components/AppProviders.tsx index 88093d84b..e65388ada 100644 --- a/frontend/src/core/components/AppProviders.tsx +++ b/frontend/src/core/components/AppProviders.tsx @@ -17,7 +17,6 @@ import { TourOrchestrationProvider } from "@app/contexts/TourOrchestrationContex import { AdminTourOrchestrationProvider } from "@app/contexts/AdminTourOrchestrationContext"; import { PageEditorProvider } from "@app/contexts/PageEditorContext"; import { BannerProvider } from "@app/contexts/BannerContext"; -import { CookieConsentProvider } from "@app/contexts/CookieConsentContext"; import ErrorBoundary from "@app/components/shared/ErrorBoundary"; import { useScarfTracking } from "@app/hooks/useScarfTracking"; import { useAppInitialization } from "@app/hooks/useAppInitialization"; @@ -83,12 +82,11 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide retryOptions={appConfigRetryOptions} {...appConfigProviderProps} > - - - - - - + + + + + @@ -114,7 +112,6 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide - diff --git a/frontend/src/core/components/onboarding/hooks/useOnboardingFlow.ts b/frontend/src/core/components/onboarding/hooks/useOnboardingFlow.ts index b2b97b2a3..139e2b58a 100644 --- a/frontend/src/core/components/onboarding/hooks/useOnboardingFlow.ts +++ b/frontend/src/core/components/onboarding/hooks/useOnboardingFlow.ts @@ -1,7 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { usePreferences } from '@app/contexts/PreferencesContext'; import { useAppConfig } from '@app/contexts/AppConfigContext'; -import { useCookieConsentContext } from '@app/contexts/CookieConsentContext'; import { useOnboarding } from '@app/contexts/OnboardingContext'; import type { LicenseNotice } from '@app/types/types'; import { useNavigate, useLocation } from 'react-router-dom'; @@ -39,7 +38,6 @@ interface ServerLicenseModalHandlers { export function useOnboardingFlow() { const { preferences, updatePreference } = usePreferences(); const { config, loading: configLoading } = useAppConfig(); - const { showCookieConsent, isReady: isCookieConsentReady } = useCookieConsentContext(); const { completeTour, tourType, isOpen } = useOnboarding(); const location = useLocation(); @@ -71,7 +69,6 @@ export function useOnboardingFlow() { isOverLimit: false, requiresLicense: false, }); - const [cookieBannerPending, setCookieBannerPending] = useState(false); const [serverLicenseIntent, setServerLicenseIntent] = useState<'idle' | 'pending' | 'deferred'>('idle'); const [serverLicenseSource, setServerLicenseSource] = useState<'config' | 'self-reported' | null>(null); const [isServerLicenseOpen, setIsServerLicenseOpen] = useState(false); @@ -97,29 +94,6 @@ export function useOnboardingFlow() { setToolPromptCompleted(true); }, []); - const maybeShowCookieBanner = useCallback(() => { - if (preferences.hasSeenCookieBanner) { - return; - } - - if (!isCookieConsentReady || isServerLicenseOpen || serverLicenseIntent !== 'idle' || !toolPromptCompleted) { - setCookieBannerPending(true); - return; - } - - setCookieBannerPending(false); - showCookieConsent(); - updatePreference('hasSeenCookieBanner', true); - }, [ - isCookieConsentReady, - isServerLicenseOpen, - preferences.hasSeenCookieBanner, - serverLicenseIntent, - showCookieConsent, - toolPromptCompleted, - updatePreference, - ]); - const requestServerLicense = useCallback( ({ deferUntilTourComplete = false, @@ -191,25 +165,6 @@ export function useOnboardingFlow() { }; }, [requestServerLicense]); - useEffect(() => { - if ( - cookieBannerPending && - isCookieConsentReady && - serverLicenseIntent === 'idle' && - !isServerLicenseOpen && - toolPromptCompleted - ) { - maybeShowCookieBanner(); - } - }, [ - cookieBannerPending, - isCookieConsentReady, - isServerLicenseOpen, - serverLicenseIntent, - toolPromptCompleted, - maybeShowCookieBanner, - ]); - useEffect(() => { const isEligibleAdmin = isAdminUser || serverLicenseSource === 'self-reported' || licenseNotice.requiresLicense; @@ -269,8 +224,7 @@ export function useOnboardingFlow() { setHasShownServerLicense(true); setServerLicenseIntent('idle'); setServerLicenseSource(null); - maybeShowCookieBanner(); - }, [maybeShowCookieBanner]); + }, []); useEffect(() => { if (onboardingSessionMarkedRef.current) { @@ -309,11 +263,9 @@ export function useOnboardingFlow() { setServerLicenseSource((prev) => prev ?? (isAdminUser ? 'config' : 'self-reported')); setServerLicenseIntent((prev) => (prev === 'pending' ? prev : 'pending')); } - maybeShowCookieBanner(); }, [ completeTour, isAdminUser, - maybeShowCookieBanner, serverLicenseIntent, serverLicenseSource, tourType, diff --git a/frontend/src/core/components/shared/Footer.tsx b/frontend/src/core/components/shared/Footer.tsx index 07b5213ba..52ee4165b 100644 --- a/frontend/src/core/components/shared/Footer.tsx +++ b/frontend/src/core/components/shared/Footer.tsx @@ -1,6 +1,6 @@ import { Flex } from '@mantine/core'; import { useTranslation } from 'react-i18next'; -import { useCookieConsentContext } from '@app/contexts/CookieConsentContext'; +import { useCookieConsent } from '@app/hooks/useCookieConsent'; interface FooterProps { privacyPolicy?: string; @@ -20,7 +20,7 @@ export default function Footer({ analyticsEnabled = false }: FooterProps) { const { t } = useTranslation(); - const { showCookiePreferences } = useCookieConsentContext(); + const { showCookiePreferences } = useCookieConsent({ analyticsEnabled }); // Helper to check if a value is valid (not null/undefined/empty string) const isValidLink = (link?: string) => link && link.trim().length > 0; diff --git a/frontend/src/core/contexts/CookieConsentContext.tsx b/frontend/src/core/contexts/CookieConsentContext.tsx deleted file mode 100644 index 530f99a2a..000000000 --- a/frontend/src/core/contexts/CookieConsentContext.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { createContext, useContext, useMemo } from 'react'; -import { useCookieConsent } from '@app/hooks/useCookieConsent'; -import { useAppConfig } from '@app/contexts/AppConfigContext'; - -interface CookieConsentContextValue { - isReady: boolean; - showCookieConsent: () => void; - showCookiePreferences: () => void; - hasResponded: boolean; -} - -const CookieConsentContext = createContext(undefined); - -export const CookieConsentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { config } = useAppConfig(); - const analyticsEnabled = config ? config.enableAnalytics !== false : false; - const { - showCookieConsent, - showCookiePreferences, - isInitialized, - hasResponded, - } = useCookieConsent({ analyticsEnabled }); - - const value = useMemo(() => ({ - isReady: analyticsEnabled && isInitialized, - showCookieConsent, - showCookiePreferences, - hasResponded, - }), [analyticsEnabled, hasResponded, isInitialized, showCookieConsent, showCookiePreferences]); - - return ( - - {children} - - ); -}; - -export const useCookieConsentContext = (): CookieConsentContextValue => { - const context = useContext(CookieConsentContext); - if (!context) { - throw new Error('useCookieConsentContext must be used within a CookieConsentProvider'); - } - return context; -}; - diff --git a/frontend/src/core/hooks/useCookieConsent.ts b/frontend/src/core/hooks/useCookieConsent.ts index 58b7683c6..29eb6d550 100644 --- a/frontend/src/core/hooks/useCookieConsent.ts +++ b/frontend/src/core/hooks/useCookieConsent.ts @@ -10,7 +10,6 @@ declare global { show: (show?: boolean) => void; acceptedCategory: (category: string) => boolean; acceptedService: (serviceName: string, category: string) => boolean; - validConsent?: () => boolean; }; } } @@ -20,66 +19,46 @@ interface CookieConsentConfig { } export const useCookieConsent = ({ - analyticsEnabled = false, + analyticsEnabled = false }: CookieConsentConfig = {}) => { const { t } = useTranslation(); const { config } = useAppConfig(); const [isInitialized, setIsInitialized] = useState(false); - const [hasRespondedInternal, setHasRespondedInternal] = useState(false); useEffect(() => { - if (typeof window === 'undefined') { + if (!analyticsEnabled) { + console.log('Cookie consent not enabled - analyticsEnabled is false'); return; } - const markResponded = () => setHasRespondedInternal(true); - const removeConsentListeners = () => { - window.removeEventListener('cc:onFirstConsent', markResponded); - window.removeEventListener('cc:onConsent', markResponded); - window.removeEventListener('cc:onChange', markResponded); - }; - - window.addEventListener('cc:onFirstConsent', markResponded); - window.addEventListener('cc:onConsent', markResponded); - window.addEventListener('cc:onChange', markResponded); - - if (analyticsEnabled) { - setHasRespondedInternal(window.CookieConsent?.validConsent?.() ?? false); + // Load the cookie consent CSS files first (always needed) + const mainCSS = document.createElement('link'); + mainCSS.rel = 'stylesheet'; + mainCSS.href = `${BASE_PATH}css/cookieconsent.css`; + if (!document.querySelector(`link[href="${mainCSS.href}"]`)) { + document.head.appendChild(mainCSS); } - if (!analyticsEnabled) { - console.log('Cookie consent not enabled - analyticsEnabled is false'); - setHasRespondedInternal(false); - return () => { - removeConsentListeners(); - }; + const customCSS = document.createElement('link'); + customCSS.rel = 'stylesheet'; + customCSS.href = `${BASE_PATH}css/cookieconsentCustomisation.css`; + if (!document.querySelector(`link[href="${customCSS.href}"]`)) { + document.head.appendChild(customCSS); } // Prevent double initialization if (window.CookieConsent) { setIsInitialized(true); - if (window.CookieConsent.validConsent?.()) { - markResponded(); - } - return () => { - removeConsentListeners(); - }; + // Force show the modal if it exists but isn't visible + setTimeout(() => { + window.CookieConsent?.show(); + }, 100); + return; } - // Load the cookie consent CSS files first - const mainCSS = document.createElement('link'); - mainCSS.rel = 'stylesheet'; - mainCSS.href = `${BASE_PATH}/css/cookieconsent.css`; - document.head.appendChild(mainCSS); - - const customCSS = document.createElement('link'); - customCSS.rel = 'stylesheet'; - customCSS.href = `${BASE_PATH}/css/cookieconsentCustomisation.css`; - document.head.appendChild(customCSS); - // Load the cookie consent library const script = document.createElement('script'); - script.src = `${BASE_PATH}/js/thirdParty/cookieconsent.umd.js`; + script.src = `${BASE_PATH}js/thirdParty/cookieconsent.umd.js`; script.onload = () => { // Small delay to ensure DOM is ready setTimeout(() => { @@ -141,7 +120,7 @@ export const useCookieConsent = ({ // Initialize cookie consent with full configuration try { window.CookieConsent.run({ - autoShow: false, + autoShow: true, hideFromBots: false, guiOptions: { consentModal: { @@ -227,21 +206,18 @@ export const useCookieConsent = ({ } } } - }, - onFirstConsent: markResponded, - onConsent: markResponded, - onChange: markResponded, + } }); + // Force show after initialization + setTimeout(() => { + window.CookieConsent?.show(); + }, 200); + } catch (error) { console.error('Error initializing CookieConsent:', error); } - if (window.CookieConsent?.validConsent?.()) { - markResponded(); - } else { - setHasRespondedInternal(false); - } - setIsInitialized(true); + setIsInitialized(true); }, 100); // Small delay to ensure DOM is ready }; @@ -252,8 +228,6 @@ export const useCookieConsent = ({ document.head.appendChild(script); return () => { - // Cleanup event listeners - removeConsentListeners(); // Cleanup script and CSS when component unmounts if (document.head.contains(script)) { document.head.removeChild(script); @@ -286,13 +260,10 @@ export const useCookieConsent = ({ return window.CookieConsent.acceptedService(service, category); }, []); - const effectiveHasResponded = analyticsEnabled ? hasRespondedInternal : true; - return { showCookieConsent, showCookiePreferences, isServiceAccepted, isInitialized, - hasResponded: effectiveHasResponded, }; }; diff --git a/frontend/src/core/pages/HomePage.tsx b/frontend/src/core/pages/HomePage.tsx index e03f46ee2..04174d29a 100644 --- a/frontend/src/core/pages/HomePage.tsx +++ b/frontend/src/core/pages/HomePage.tsx @@ -9,7 +9,6 @@ import { useIsMobile } from "@app/hooks/useIsMobile"; import { useAppConfig } from "@app/contexts/AppConfigContext"; import { useLogoPath } from "@app/hooks/useLogoPath"; import { useLogoAssets } from '@app/hooks/useLogoAssets'; -import { useCookieConsentContext } from "@app/contexts/CookieConsentContext"; import { useFileContext } from "@app/contexts/file/fileHooks"; import { useNavigationActions } from "@app/contexts/NavigationContext"; import { useViewer } from "@app/contexts/ViewerContext"; @@ -49,7 +48,6 @@ export default function HomePage() { const { openFilesModal } = useFilesModalContext(); const { colorScheme } = useMantineColorScheme(); const { config } = useAppConfig(); - const { hasResponded: cookieConsentResponded } = useCookieConsentContext(); const isMobile = useIsMobile(); const sliderRef = useRef(null); const [activeMobileView, setActiveMobileView] = useState("tools"); @@ -64,10 +62,10 @@ export default function HomePage() { // Show admin analytics choice modal if analytics settings not configured useEffect(() => { - if (config && config.enableAnalytics === null && cookieConsentResponded) { + if (config && config.enableAnalytics === null) { setShowAnalyticsModal(true); } - }, [config, cookieConsentResponded]); + }, [config]); // Auto-switch to viewer when going from 0 to 1 file useEffect(() => { diff --git a/frontend/src/proprietary/components/shared/UpgradeBanner.tsx b/frontend/src/proprietary/components/shared/UpgradeBanner.tsx index 4b325106b..c71546d3a 100644 --- a/frontend/src/proprietary/components/shared/UpgradeBanner.tsx +++ b/frontend/src/proprietary/components/shared/UpgradeBanner.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { useCookieConsentContext } from '@app/contexts/CookieConsentContext'; import { useOnboarding } from '@app/contexts/OnboardingContext'; import { useCheckout } from '@app/contexts/CheckoutContext'; import { InfoBanner } from '@app/components/shared/InfoBanner'; @@ -22,7 +21,6 @@ const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; const UpgradeBanner: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); - const { hasResponded: cookieChoiceMade } = useCookieConsentContext(); const { isOpen: tourOpen } = useOnboarding(); const { openCheckout } = useCheckout(); const { @@ -159,7 +157,6 @@ const UpgradeBanner: React.FC = () => { shouldShowFriendlyBase && !licenseLoading && effectiveTotalUsersLoaded && - cookieChoiceMade && !tourOpen && !sessionBlocked, ); @@ -168,7 +165,6 @@ const UpgradeBanner: React.FC = () => { : Boolean( shouldShowUrgentBase && !licenseLoading && - cookieChoiceMade && !tourOpen && !sessionBlocked, );