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,
);