various cookie banner fixes (#5027)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## 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.
This commit is contained in:
Reece Browne 2025-11-26 15:57:04 +00:00 committed by GitHub
parent d4765938a8
commit 1e34038b1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 38 additions and 169 deletions

View File

@ -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}
>
<CookieConsentProvider>
<ScarfTrackingInitializer />
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
<AppInitializer />
<BrandingAssetManager />
<ToolRegistryProvider>
<ScarfTrackingInitializer />
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
<AppInitializer />
<BrandingAssetManager />
<ToolRegistryProvider>
<NavigationProvider>
<FilesModalProvider>
<ToolWorkflowProvider>
@ -114,7 +112,6 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide
</NavigationProvider>
</ToolRegistryProvider>
</FileContextProvider>
</CookieConsentProvider>
</AppConfigProvider>
</OnboardingProvider>
</BannerProvider>

View File

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

View File

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

View File

@ -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<CookieConsentContextValue | undefined>(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<CookieConsentContextValue>(() => ({
isReady: analyticsEnabled && isInitialized,
showCookieConsent,
showCookiePreferences,
hasResponded,
}), [analyticsEnabled, hasResponded, isInitialized, showCookieConsent, showCookiePreferences]);
return (
<CookieConsentContext.Provider value={value}>
{children}
</CookieConsentContext.Provider>
);
};
export const useCookieConsentContext = (): CookieConsentContextValue => {
const context = useContext(CookieConsentContext);
if (!context) {
throw new Error('useCookieConsentContext must be used within a CookieConsentProvider');
}
return context;
};

View File

@ -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,
};
};

View File

@ -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<HTMLDivElement | null>(null);
const [activeMobileView, setActiveMobileView] = useState<MobileView>("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(() => {

View File

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