commit
6dad7c97a1
7
package-lock.json
generated
7
package-lock.json
generated
@ -30,6 +30,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-circular-progressbar": "^2.2.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-hook-form": "^7.63.0",
|
||||
"recharts": "^2.15.4",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
@ -9718,6 +9719,12 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-ga4": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
|
||||
"integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.63.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-circular-progressbar": "^2.2.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-hook-form": "^7.63.0",
|
||||
"recharts": "^2.15.4",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
|
||||
132
src/components/providers/MetricsProvider.tsx
Normal file
132
src/components/providers/MetricsProvider.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
interface MetricsProviderProps {
|
||||
children: ReactNode;
|
||||
googleAnalyticsId?: string;
|
||||
yandexMetrikaId?: string;
|
||||
facebookPixels?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Metrics Provider Component
|
||||
*
|
||||
* Инициализирует все метрики СИНХРОННО при загрузке приложения
|
||||
* по аналогии с aura-webapp для гарантии создания куки ДО авторизации.
|
||||
*
|
||||
* Метрики:
|
||||
* - Google Analytics (через react-ga4) - синхронная инициализация
|
||||
* - Yandex Metrika - синхронная загрузка скрипта
|
||||
* - Facebook Pixel - синхронная загрузка скрипта
|
||||
*
|
||||
* Это гарантирует что куки (_ga, _gid, _fbp, _fbc, _ym_uid) создаются
|
||||
* ДО того как пользователь достигнет экрана авторизации.
|
||||
*/
|
||||
export function MetricsProvider({
|
||||
children,
|
||||
googleAnalyticsId,
|
||||
yandexMetrikaId,
|
||||
facebookPixels = []
|
||||
}: MetricsProviderProps) {
|
||||
|
||||
// Инициализация Google Analytics (синхронно через react-ga4)
|
||||
useEffect(() => {
|
||||
if (!googleAnalyticsId) return;
|
||||
|
||||
try {
|
||||
ReactGA.initialize(googleAnalyticsId);
|
||||
console.log('[Metrics] Google Analytics initialized:', googleAnalyticsId);
|
||||
} catch (error) {
|
||||
console.error('[Metrics] Failed to initialize Google Analytics:', error);
|
||||
}
|
||||
}, [googleAnalyticsId]);
|
||||
|
||||
// Инициализация Yandex Metrika (синхронно через скрипт)
|
||||
useEffect(() => {
|
||||
if (!yandexMetrikaId) return;
|
||||
|
||||
try {
|
||||
// Проверяем что скрипт еще не загружен
|
||||
if (typeof window.ym === 'function') {
|
||||
console.log('[Metrics] Yandex Metrika already loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем скрипт Yandex Metrika
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = 'https://mc.yandex.ru/metrika/tag.js';
|
||||
|
||||
script.onload = () => {
|
||||
// Инициализируем счетчик после загрузки скрипта
|
||||
if (typeof window.ym === 'function') {
|
||||
window.ym(Number(yandexMetrikaId), 'init', {
|
||||
clickmap: true,
|
||||
trackLinks: true,
|
||||
accurateTrackBounce: true,
|
||||
webvisor: true,
|
||||
});
|
||||
|
||||
// Сохраняем ID счетчика для использования в analytics service
|
||||
window.__YM_COUNTER_ID__ = Number(yandexMetrikaId);
|
||||
|
||||
console.log('[Metrics] Yandex Metrika initialized:', yandexMetrikaId);
|
||||
}
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
} catch (error) {
|
||||
console.error('[Metrics] Failed to initialize Yandex Metrika:', error);
|
||||
}
|
||||
}, [yandexMetrikaId]);
|
||||
|
||||
// Инициализация Facebook Pixel (синхронно через скрипт)
|
||||
useEffect(() => {
|
||||
if (!facebookPixels || facebookPixels.length === 0) return;
|
||||
|
||||
try {
|
||||
// Проверяем что fbq еще не загружен
|
||||
if (typeof window.fbq === 'function') {
|
||||
console.log('[Metrics] Facebook Pixel already loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// Базовый код Facebook Pixel
|
||||
const fbqScript = `
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
`;
|
||||
|
||||
// Выполняем базовый код
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.innerHTML = fbqScript;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Инициализируем каждый пиксель
|
||||
if (typeof window.fbq === 'function') {
|
||||
const fbq = window.fbq as ((...args: unknown[]) => void);
|
||||
facebookPixels.forEach((pixelId) => {
|
||||
fbq('init', pixelId);
|
||||
console.log('[Metrics] Facebook Pixel initialized:', pixelId);
|
||||
});
|
||||
|
||||
// Отправляем PageView для всех пикселей
|
||||
fbq('track', 'PageView');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Metrics] Failed to initialize Facebook Pixel:', error);
|
||||
}
|
||||
}, [facebookPixels]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { FacebookPixels, GoogleAnalytics, YandexMetrika, PageViewTracker } from "@/components/analytics";
|
||||
import { PageViewTracker } from "@/components/analytics";
|
||||
import { getPixels } from "@/entities/session/actions";
|
||||
import { getSourceByPathname } from "@/shared/utils/source";
|
||||
import { MetricsProvider } from "./MetricsProvider";
|
||||
|
||||
interface PixelsProviderProps {
|
||||
children: ReactNode;
|
||||
@ -14,7 +15,8 @@ interface PixelsProviderProps {
|
||||
/**
|
||||
* Pixels Provider Component
|
||||
*
|
||||
* Loads tracking scripts for Facebook Pixels, Google Analytics, and Yandex Metrika.
|
||||
* Инициализирует все метрики синхронно через MetricsProvider
|
||||
* по аналогии с aura-webapp для гарантии создания куки ДО авторизации.
|
||||
*
|
||||
* IMPORTANT: This component should be placed in a layout (not in components that re-render on navigation)
|
||||
* to avoid duplicate API requests. Currently used in app/[funnelId]/layout.tsx
|
||||
@ -26,11 +28,10 @@ interface PixelsProviderProps {
|
||||
* 1. Check localStorage for cached FB pixels
|
||||
* 2. If not cached, request from backend (errors are handled gracefully)
|
||||
* 3. Save to localStorage if pixels received
|
||||
* 4. Render GA and YM if IDs provided in funnel config
|
||||
* 4. Pass all IDs to MetricsProvider for synchronous initialization
|
||||
*/
|
||||
export function PixelsProvider({ children, googleAnalyticsId, yandexMetrikaId }: PixelsProviderProps) {
|
||||
const [pixels, setPixels] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPixels = async () => {
|
||||
@ -40,7 +41,6 @@ export function PixelsProvider({ children, googleAnalyticsId, yandexMetrikaId }:
|
||||
if (cachedPixels) {
|
||||
const parsed = JSON.parse(cachedPixels);
|
||||
setPixels(parsed);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,8 +67,6 @@ export function PixelsProvider({ children, googleAnalyticsId, yandexMetrikaId }:
|
||||
// Silently handle errors - pixels are optional
|
||||
console.warn("Facebook pixels not available:", error instanceof Error ? error.message : error);
|
||||
setPixels([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -76,12 +74,13 @@ export function PixelsProvider({ children, googleAnalyticsId, yandexMetrikaId }:
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isLoading && <FacebookPixels pixels={pixels} />}
|
||||
<GoogleAnalytics measurementId={googleAnalyticsId} />
|
||||
<YandexMetrika counterId={yandexMetrikaId} />
|
||||
<MetricsProvider
|
||||
googleAnalyticsId={googleAnalyticsId}
|
||||
yandexMetrikaId={yandexMetrikaId}
|
||||
facebookPixels={pixels}
|
||||
>
|
||||
<PageViewTracker />
|
||||
{children}
|
||||
</>
|
||||
</MetricsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user