add yandex metrika improve
This commit is contained in:
parent
f01469c1a5
commit
02bfbb1112
123
ANALYTICS_IMPLEMENTATION.md
Normal file
123
ANALYTICS_IMPLEMENTATION.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Analytics System Implementation
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
В проект `witlab-app` была добавлена система аналитики на основе Yandex Metrika по аналогии с `witlab-funnel`:
|
||||||
|
|
||||||
|
- **Yandex Metrika** - отслеживание просмотров страниц и событий
|
||||||
|
- **PageViewTracker** - автоматическая отслежка переходов между страницами
|
||||||
|
- **AnalyticsService** - управление параметрами пользователя и событиями
|
||||||
|
|
||||||
|
## Структура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/analytics/
|
||||||
|
│ ├── PageViewTracker.tsx # Отслеживание переходов
|
||||||
|
│ ├── YandexMetrika/
|
||||||
|
│ │ ├── YandexMetrika.tsx # Компонент Яндекс Метрики
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ └── index.ts # Экспорты аналитики
|
||||||
|
├── providers/
|
||||||
|
│ └── analytics-provider.tsx # Провайдер аналитики
|
||||||
|
├── services/analytics/
|
||||||
|
│ ├── analyticsService.ts # Сервис аналитики
|
||||||
|
│ └── index.ts
|
||||||
|
└── shared/constants/
|
||||||
|
└── analytics.ts # Константы ID счетчиков
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
### Статичные константы
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/shared/constants/analytics.ts
|
||||||
|
export const YANDEX_METRIKA_ID = "103412914";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Автоматическая отслежка страниц
|
||||||
|
|
||||||
|
Система автоматически отслеживает все переходы между страницами через `PageViewTracker`.
|
||||||
|
|
||||||
|
### Параметры пользователя
|
||||||
|
|
||||||
|
При инициализации система автоматически загружает данные пользователя через `/users/me` и устанавливает параметры в Yandex Metrika:
|
||||||
|
|
||||||
|
- `user_id` - ID пользователя
|
||||||
|
- `email` - email пользователя
|
||||||
|
- `locale` - язык пользователя
|
||||||
|
- `timezone` - часовой пояс
|
||||||
|
- `profile_name`, `profile_gender`, `profile_age`, `profile_sign` - данные профиля
|
||||||
|
- `partner_gender`, `partner_age`, `partner_sign` - данные партнера
|
||||||
|
- `country`, `region`, `city` - геолокация
|
||||||
|
|
||||||
|
### Отслеживание событий
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { analyticsService } from "@/services/analytics";
|
||||||
|
|
||||||
|
// Отправить событие в Yandex Metrika
|
||||||
|
analyticsService.trackEvent("button_click", {
|
||||||
|
button_name: "subscribe_now",
|
||||||
|
screen: "retaining"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Доступ к данным пользователя
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { analyticsService } from "@/services/analytics";
|
||||||
|
|
||||||
|
if (analyticsService.isReady()) {
|
||||||
|
const user = analyticsService.getUser();
|
||||||
|
console.log("Current user:", user);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
|
||||||
|
Система предоставляет детальное логирование в консоли:
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ [YM] Page View Event Sent
|
||||||
|
✅ [Analytics] Service initialized with user data
|
||||||
|
✅ [YM] User parameters set
|
||||||
|
```
|
||||||
|
|
||||||
|
## Интеграция в layout
|
||||||
|
|
||||||
|
Компоненты аналитики добавлены в `src/app/[locale]/layout.tsx`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<YandexMetrika />
|
||||||
|
<AnalyticsProvider>
|
||||||
|
{/* ... */}
|
||||||
|
<PageViewTracker />
|
||||||
|
{/* ... */}
|
||||||
|
</AnalyticsProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Отличия от witlab-funnel
|
||||||
|
|
||||||
|
1. **Только Yandex Metrika** - без Google Analytics
|
||||||
|
2. **Статичный ID** - Яндекс Метрика использует статичный ID вместо динамического из воронки
|
||||||
|
3. **Параметры пользователя** - Автоматическая загрузка и установка параметров из `/me`
|
||||||
|
4. **Унифицированный сервис** - Единый `AnalyticsService` для управления
|
||||||
|
|
||||||
|
## Проверка работы
|
||||||
|
|
||||||
|
1. Откройте консоль разработчика в браузере
|
||||||
|
2. Перейдите между страницами приложения
|
||||||
|
3. Вы увидите сообщения об отправке событий в YM
|
||||||
|
4. Проверьте наличие cookies `_ym_uid`
|
||||||
|
|
||||||
|
## Совместимость
|
||||||
|
|
||||||
|
- ✅ TypeScript
|
||||||
|
- ✅ ESLint (без ошибок, только предупреждения о существующих `any`)
|
||||||
|
- ✅ Next.js 15
|
||||||
|
- ✅ Server-side rendering
|
||||||
|
- ✅ Динамические маршруты
|
||||||
@ -9,10 +9,11 @@ import { hasLocale, NextIntlClientProvider } from "next-intl";
|
|||||||
import { getMessages } from "next-intl/server";
|
import { getMessages } from "next-intl/server";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import YandexMetrika from "@/components/analytics/YandexMetrika";
|
import { PageViewTracker,YandexMetrika } from "@/components/analytics";
|
||||||
import { loadChatsList } from "@/entities/chats/loaders";
|
import { loadChatsList } from "@/entities/chats/loaders";
|
||||||
import { loadUser, loadUserId } from "@/entities/user/loaders";
|
import { loadUser, loadUserId } from "@/entities/user/loaders";
|
||||||
import { routing } from "@/i18n/routing";
|
import { routing } from "@/i18n/routing";
|
||||||
|
import { AnalyticsProvider } from "@/providers/analytics-provider";
|
||||||
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
||||||
import { AudioProvider } from "@/providers/audio-provider";
|
import { AudioProvider } from "@/providers/audio-provider";
|
||||||
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
||||||
@ -68,24 +69,29 @@ export default async function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html lang={locale}>
|
||||||
<body className={clsx(inter.variable, styles.body)}>
|
<body className={clsx(inter.variable, styles.body)}>
|
||||||
|
{/* Analytics Components */}
|
||||||
<YandexMetrika />
|
<YandexMetrika />
|
||||||
<NextIntlClientProvider messages={messages}>
|
|
||||||
<UserProvider user={user}>
|
<AnalyticsProvider>
|
||||||
<SocketProvider userId={userId}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<RetainingStoreProvider>
|
<UserProvider user={user}>
|
||||||
<AudioProvider>
|
<SocketProvider userId={userId}>
|
||||||
<ChatsInitializationProvider>
|
<RetainingStoreProvider>
|
||||||
<ChatsProvider initialChats={chats}>
|
<AudioProvider>
|
||||||
<ToastProvider maxVisible={3}>
|
<ChatsInitializationProvider>
|
||||||
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
<ChatsProvider initialChats={chats}>
|
||||||
</ToastProvider>
|
<ToastProvider maxVisible={3}>
|
||||||
</ChatsProvider>
|
<PageViewTracker />
|
||||||
</ChatsInitializationProvider>
|
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
||||||
</AudioProvider>
|
</ToastProvider>
|
||||||
</RetainingStoreProvider>
|
</ChatsProvider>
|
||||||
</SocketProvider>
|
</ChatsInitializationProvider>
|
||||||
</UserProvider>
|
</AudioProvider>
|
||||||
</NextIntlClientProvider>
|
</RetainingStoreProvider>
|
||||||
|
</SocketProvider>
|
||||||
|
</UserProvider>
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</AnalyticsProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
54
src/components/analytics/PageViewTracker.tsx
Normal file
54
src/components/analytics/PageViewTracker.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for Yandex Metrika to be loaded
|
||||||
|
* Retry mechanism with timeout to handle async script loading
|
||||||
|
*/
|
||||||
|
async function waitForYandexMetrika(maxAttempts = 10, delayMs = 100): Promise<boolean> {
|
||||||
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
|
if (typeof window !== "undefined" &&
|
||||||
|
typeof window.ym === "function" &&
|
||||||
|
window.__YM_COUNTER_ID__) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page View Tracker Component
|
||||||
|
*
|
||||||
|
* Tracks page views in Yandex Metrika
|
||||||
|
* when route changes occur (client-side navigation).
|
||||||
|
*
|
||||||
|
* Must be included in the app layout or root component.
|
||||||
|
*/
|
||||||
|
export function PageViewTracker() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : "");
|
||||||
|
|
||||||
|
// Track page view in Yandex Metrika (with retry logic)
|
||||||
|
const trackYandexMetrika = async () => {
|
||||||
|
const isYmAvailable = await waitForYandexMetrika();
|
||||||
|
|
||||||
|
if (isYmAvailable && typeof window.ym === "function") {
|
||||||
|
const counterId = window.__YM_COUNTER_ID__;
|
||||||
|
if (counterId) {
|
||||||
|
window.ym(counterId, "hit", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute YM tracking
|
||||||
|
trackYandexMetrika();
|
||||||
|
}, [pathname, searchParams]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -1,79 +1,52 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
|
||||||
const YANDEX_METRIKA_ID = 103412914;
|
import { YANDEX_METRIKA_ID } from "@/shared/constants/analytics";
|
||||||
|
|
||||||
export default function YandexMetrika() {
|
|
||||||
useEffect(() => {
|
|
||||||
// Initialize Yandex.Metrika after script loads
|
|
||||||
const initializeYandexMetrika = () => {
|
|
||||||
if (typeof window.ym === "function") {
|
|
||||||
try {
|
|
||||||
window.ym(YANDEX_METRIKA_ID, "init", {
|
|
||||||
webvisor: true,
|
|
||||||
clickmap: true,
|
|
||||||
accurateTrackBounce: true,
|
|
||||||
trackLinks: true,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Silently handle initialization errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if ym is already available or wait for it
|
|
||||||
if (typeof window.ym === "function") {
|
|
||||||
initializeYandexMetrika();
|
|
||||||
} else {
|
|
||||||
// Wait for script to load
|
|
||||||
const checkYm = setInterval(() => {
|
|
||||||
if (typeof window.ym === "function") {
|
|
||||||
initializeYandexMetrika();
|
|
||||||
clearInterval(checkYm);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// Cleanup interval after 10 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(checkYm);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
return () => clearInterval(checkYm);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yandex Metrika Integration Component
|
||||||
|
*
|
||||||
|
* Loads Yandex Metrika tracking script dynamically using static counter ID.
|
||||||
|
*
|
||||||
|
* Initializes with: clickmap, trackLinks, accurateTrackBounce, webvisor.
|
||||||
|
* Page views are tracked by PageViewTracker component on route changes.
|
||||||
|
*/
|
||||||
|
export function YandexMetrika() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Yandex.Metrika counter */}
|
|
||||||
<Script
|
<Script
|
||||||
id="yandex-metrika-site-wide"
|
id="yandex-metrika"
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
(function(m,e,t,r,i,k,a){
|
(function(m,e,t,r,i,k,a){
|
||||||
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
m[i].l=1*new Date();
|
m[i].l=1*new Date();
|
||||||
for (var j = 0; j < document.scripts.length; j++) {
|
for (var j = 0; j < document.scripts.length; j++) {
|
||||||
if (document.scripts[j].src === r) { return; }
|
if (document.scripts[j].src === r) { return; }
|
||||||
}
|
}
|
||||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
||||||
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js', 'ym');
|
})(window, document, "script", "https://cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js", "ym");
|
||||||
|
|
||||||
|
// Store counter ID for analyticsService
|
||||||
|
window.__YM_COUNTER_ID__ = ${YANDEX_METRIKA_ID};
|
||||||
|
|
||||||
|
ym(${YANDEX_METRIKA_ID}, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
webvisor: true
|
||||||
|
});
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Noscript fallback */}
|
|
||||||
<noscript>
|
<noscript>
|
||||||
<div>
|
<div>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={`https://mc.yandex.ru/watch/${YANDEX_METRIKA_ID}`}
|
src={`https://mc.yandex.ru/watch/${YANDEX_METRIKA_ID}`}
|
||||||
style={{
|
style={{ position: "absolute", left: "-9999px" }}
|
||||||
position: "absolute",
|
|
||||||
left: "-9999px",
|
|
||||||
}}
|
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { default } from "./YandexMetrika";
|
export { YandexMetrika } from "./YandexMetrika";
|
||||||
|
|||||||
2
src/components/analytics/index.ts
Normal file
2
src/components/analytics/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { PageViewTracker } from "./PageViewTracker";
|
||||||
|
export { YandexMetrika } from "./YandexMetrika/YandexMetrika";
|
||||||
24
src/providers/analytics-provider.tsx
Normal file
24
src/providers/analytics-provider.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { type ReactNode,useEffect } from "react";
|
||||||
|
|
||||||
|
import { analyticsService } from "@/services/analytics";
|
||||||
|
|
||||||
|
interface AnalyticsProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics Provider Component
|
||||||
|
*
|
||||||
|
* Initializes analytics service with user data when the app loads.
|
||||||
|
* Provides analytics context to the entire application.
|
||||||
|
*/
|
||||||
|
export function AnalyticsProvider({ children }: AnalyticsProviderProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
// Initialize analytics service
|
||||||
|
analyticsService.initialize();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
140
src/services/analytics/analyticsService.ts
Normal file
140
src/services/analytics/analyticsService.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import type { IMeResponse, IUser } from "@/entities/user/types";
|
||||||
|
import { http } from "@/shared/api/httpClient";
|
||||||
|
import { API_ROUTES } from "@/shared/constants/api-routes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics Service
|
||||||
|
*
|
||||||
|
* Provides methods for tracking user parameters and events
|
||||||
|
* in Yandex Metrika.
|
||||||
|
*/
|
||||||
|
export class AnalyticsService {
|
||||||
|
private static instance: AnalyticsService;
|
||||||
|
private user: IUser | null = null;
|
||||||
|
private isInitialized = false;
|
||||||
|
|
||||||
|
static getInstance(): AnalyticsService {
|
||||||
|
if (!AnalyticsService.instance) {
|
||||||
|
AnalyticsService.instance = new AnalyticsService();
|
||||||
|
}
|
||||||
|
return AnalyticsService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize analytics service with user data
|
||||||
|
*/
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await http.get<IMeResponse>(API_ROUTES.usersMe());
|
||||||
|
this.user = response.user;
|
||||||
|
|
||||||
|
// Set user parameters in analytics
|
||||||
|
await this.setUserParameters();
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
} catch {
|
||||||
|
// Initialization failed silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user parameters in Yandex Metrika
|
||||||
|
*/
|
||||||
|
private async setUserParameters(): Promise<void> {
|
||||||
|
if (!this.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userParams = this.extractUserParameters();
|
||||||
|
|
||||||
|
// Set parameters in Yandex Metrika
|
||||||
|
if (typeof window.ym === "function" && window.__YM_COUNTER_ID__) {
|
||||||
|
try {
|
||||||
|
window.ym(window.__YM_COUNTER_ID__, "userParams", userParams);
|
||||||
|
} catch {
|
||||||
|
// Failed to set user parameters silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract relevant user parameters for analytics
|
||||||
|
*/
|
||||||
|
private extractUserParameters(): Record<string, unknown> {
|
||||||
|
if (!this.user) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: Record<string, unknown> = {
|
||||||
|
user_id: this.user._id,
|
||||||
|
email: this.user.email,
|
||||||
|
locale: this.user.locale,
|
||||||
|
timezone: this.user.timezone,
|
||||||
|
source: this.user.source,
|
||||||
|
has_sign: this.user.sign,
|
||||||
|
created_at: this.user.createdAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add profile information if available
|
||||||
|
if (this.user.profile) {
|
||||||
|
params.profile_name = this.user.profile.name;
|
||||||
|
params.profile_gender = this.user.profile.gender;
|
||||||
|
params.profile_age = this.user.profile.age;
|
||||||
|
params.profile_sign = this.user.profile.sign;
|
||||||
|
params.profile_birthdate = this.user.profile.birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add partner information if available
|
||||||
|
if (this.user.partner) {
|
||||||
|
params.partner_gender = this.user.partner.gender;
|
||||||
|
params.partner_age = this.user.partner.age;
|
||||||
|
params.partner_sign = this.user.partner.sign;
|
||||||
|
params.partner_birthdate = this.user.partner.birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add location information if available
|
||||||
|
if (this.user.ipLookup) {
|
||||||
|
params.country = this.user.ipLookup.country;
|
||||||
|
params.region = this.user.ipLookup.region;
|
||||||
|
params.city = this.user.ipLookup.city;
|
||||||
|
params.is_eu = this.user.ipLookup.eu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track custom event in Yandex Metrika
|
||||||
|
*/
|
||||||
|
trackEvent(eventName: string, parameters?: Record<string, unknown>): void {
|
||||||
|
// Track in Yandex Metrika
|
||||||
|
if (typeof window.ym === "function" && window.__YM_COUNTER_ID__) {
|
||||||
|
try {
|
||||||
|
window.ym(window.__YM_COUNTER_ID__, "reachGoal", eventName, parameters);
|
||||||
|
} catch {
|
||||||
|
// Failed to track event silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user data
|
||||||
|
*/
|
||||||
|
getUser(): IUser | null {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if service is initialized
|
||||||
|
*/
|
||||||
|
isReady(): boolean {
|
||||||
|
return this.isInitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const analyticsService = AnalyticsService.getInstance();
|
||||||
1
src/services/analytics/index.ts
Normal file
1
src/services/analytics/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { AnalyticsService,analyticsService } from "./analyticsService";
|
||||||
1
src/shared/constants/analytics.ts
Normal file
1
src/shared/constants/analytics.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const YANDEX_METRIKA_ID = "103412914";
|
||||||
@ -36,8 +36,8 @@ declare global {
|
|||||||
fbq: any;
|
fbq: any;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
CollectJS: any;
|
CollectJS: any;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
gtag: any;
|
__YM_COUNTER_ID__: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user