diff --git a/docs/pages/_app.js b/docs/pages/_app.js
index 27343682d9161b..abc350a468e75c 100644
--- a/docs/pages/_app.js
+++ b/docs/pages/_app.js
@@ -19,6 +19,7 @@ import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics';
import { CodeCopyProvider } from '@mui/docs/CodeCopy';
import { ThemeProvider } from 'docs/src/modules/components/ThemeContext';
import { CodeVariantProvider } from 'docs/src/modules/utils/codeVariant';
+import { AnalyticsProvider } from 'docs/src/modules/components/AnalyticsProvider';
import DocsStyledEngineProvider from 'docs/src/modules/utils/StyledEngineProvider';
import createEmotionCache from 'docs/src/createEmotionCache';
import findActivePage from 'docs/src/modules/utils/findActivePage';
@@ -345,18 +346,20 @@ function AppWrapper(props) {
defaultUserLanguage={pageProps.userLanguage}
translations={pageProps.translations}
>
-
-
-
-
-
- {children}
-
-
-
-
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
);
diff --git a/docs/pages/_document.js b/docs/pages/_document.js
index 873bbba1fa2bd2..ccf73f6f8fa99c 100644
--- a/docs/pages/_document.js
+++ b/docs/pages/_document.js
@@ -15,6 +15,7 @@ const PRODUCTION_GA =
process.env.DEPLOY_ENV === 'production' || process.env.DEPLOY_ENV === 'staging';
const GOOGLE_ANALYTICS_ID_V4 = PRODUCTION_GA ? 'G-5NXDQLC2ZK' : 'G-XJ83JQEK7J';
+const APOLLO_TRACKING_ID = PRODUCTION_GA ? '68efaf63a6b6a4001571da4a' : 'dev-id';
export default class MyDocument extends Document {
render() {
@@ -104,10 +105,47 @@ export default class MyDocument extends Document {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
window.gtag = gtag;
+
+${/* Set default consent to denied (Google Consent Mode v2) */ ''}
+gtag('consent', 'default', {
+ 'ad_storage': 'denied',
+ 'ad_user_data': 'denied',
+ 'ad_personalization': 'denied',
+ 'analytics_storage': 'denied',
+ 'wait_for_update': 500
+});
+gtag('set', 'ads_data_redaction', true);
+gtag('set', 'url_passthrough', true);
+
gtag('js', new Date());
gtag('config', '${GOOGLE_ANALYTICS_ID_V4}', {
send_page_view: false,
});
+
+${/* Apollo initialization - called by AnalyticsProvider when consent is granted */ ''}
+window.initApollo = function() {
+ if (window.apolloInitialized) return;
+ window.apolloInitialized = true;
+ var n = Math.random().toString(36).substring(7),
+ o = document.createElement('script');
+ o.src = 'https://assets.apollo.io/micro/website-tracker/tracker.iife.js?nocache=' + n;
+ o.async = true;
+ o.defer = true;
+ o.onload = function () {
+ window.trackingFunctions.onLoad({ appId: '${APOLLO_TRACKING_ID}' });
+ };
+ document.head.appendChild(o);
+};
+
+${/* Check localStorage for existing consent and initialize if already granted */ ''}
+(function() {
+ try {
+ var consent = localStorage.getItem('docs-cookie-consent');
+ if (consent === 'analytics') {
+ window.initApollo();
+ }
+ } catch (e) {}
+})();
`,
}}
/>
diff --git a/docs/src/modules/components/AnalyticsProvider.tsx b/docs/src/modules/components/AnalyticsProvider.tsx
new file mode 100644
index 00000000000000..5294e179bfe1b8
--- /dev/null
+++ b/docs/src/modules/components/AnalyticsProvider.tsx
@@ -0,0 +1,164 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Dialog from '@mui/material/Dialog';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import useLocalStorageState from '@mui/utils/useLocalStorageState';
+
+const COOKIE_CONSENT_KEY = 'docs-cookie-consent';
+
+type ConsentStatus = 'analytics' | 'essential' | null;
+
+function getDoNotTrack(): boolean {
+ if (typeof window === 'undefined') {
+ return false;
+ }
+ // Check for Do Not Track (DNT)
+ return navigator.doNotTrack === '1' || (window as { doNotTrack?: string }).doNotTrack === '1';
+}
+
+// DNT doesn't change during a session, so we can use a simple external store
+const subscribeToNothing = () => () => {};
+const getDoNotTrackSnapshot = () => getDoNotTrack();
+const getDoNotTrackServerSnapshot = () => true; // Assume DNT until we know the actual value
+
+function useDoNotTrack(): boolean {
+ return React.useSyncExternalStore(
+ subscribeToNothing,
+ getDoNotTrackSnapshot,
+ getDoNotTrackServerSnapshot,
+ );
+}
+
+interface AnalyticsContextValue {
+ consentStatus: ConsentStatus;
+ hasAnalyticsConsent: boolean;
+}
+
+const AnalyticsContext = React.createContext({
+ consentStatus: null,
+ hasAnalyticsConsent: false,
+});
+
+export function useAnalyticsConsent() {
+ return React.useContext(AnalyticsContext);
+}
+
+function CookieConsentDialog({
+ open,
+ onAnalytics,
+ onEssential,
+}: {
+ open: boolean;
+ onAnalytics: () => void;
+ onEssential: () => void;
+}) {
+ if (!open) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+function updateGoogleConsent(hasAnalytics: boolean) {
+ if (typeof window !== 'undefined' && typeof window.gtag === 'function') {
+ window.gtag('consent', 'update', {
+ ad_storage: 'denied',
+ ad_user_data: 'denied',
+ ad_personalization: 'denied',
+ analytics_storage: hasAnalytics ? 'granted' : 'denied',
+ });
+
+ // Initialize Apollo when analytics consent is granted
+ const win = window as typeof window & { initApollo?: () => void };
+ if (hasAnalytics && typeof win.initApollo === 'function') {
+ win.initApollo();
+ }
+ }
+}
+
+export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
+ const [consentStatus, setConsentStatus] = useLocalStorageState(COOKIE_CONSENT_KEY, null);
+ const doNotTrack = useDoNotTrack();
+
+ // Respect Do Not Track - don't show dialog and treat as essential only
+ const showDialog = consentStatus === null && !doNotTrack;
+
+ // Update Google consent when status changes or on mount if already set
+ React.useEffect(() => {
+ if (doNotTrack) {
+ // DNT is enabled - always deny analytics
+ updateGoogleConsent(false);
+ } else if (consentStatus !== null) {
+ updateGoogleConsent(consentStatus === 'analytics');
+ }
+ }, [consentStatus, doNotTrack]);
+
+ const handleAnalytics = () => {
+ setConsentStatus('analytics');
+ };
+
+ const handleEssential = () => {
+ setConsentStatus('essential');
+ };
+
+ const contextValue = React.useMemo(
+ () => ({
+ consentStatus: doNotTrack ? 'essential' : (consentStatus as ConsentStatus),
+ hasAnalyticsConsent: !doNotTrack && consentStatus === 'analytics',
+ }),
+ [consentStatus, doNotTrack],
+ );
+
+ return (
+
+ {children}
+
+
+ );
+}