Merge pull request #48 from WIT-LAB-LLC/develop

add new scripts logic
This commit is contained in:
pennyteenycat 2025-10-28 01:59:08 +01:00 committed by GitHub
commit 6dad7c97a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 151 additions and 12 deletions

7
package-lock.json generated
View File

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

View File

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

View 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}</>;
}

View File

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