mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
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:
parent
d4765938a8
commit
1e34038b1e
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user