добавлена разметка событий аналитики
This commit is contained in:
parent
825be2d0a6
commit
2bf8cb05b2
187
ANALYTICS_EVENTS.md
Normal file
187
ANALYTICS_EVENTS.md
Normal file
@ -0,0 +1,187 @@
|
||||
# Документация по аналитическим событиям
|
||||
|
||||
В этом документе представлен полный список событий для отслеживания в приложении с использованием сервиса `customAnalyticsService.ts`. События сгруппированы по экранам и функциональным блокам.
|
||||
|
||||
## 1. Общие события приложения
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Открытие мини-приложения | `app` | `app_open` | - | - |
|
||||
|
||||
## 2. Навигация
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Открытие экрана Главная | `navigation` | `view_home` | - | - |
|
||||
| Открытие экрана Галерея | `navigation` | `view_gallery` | - | - |
|
||||
| Открытие экрана Стикерпаки | `navigation` | `view_sticker_packs` | - | - |
|
||||
| Открытие экрана Профиль | `navigation` | `view_profile` | - | - |
|
||||
| Открытие экрана Обрезка фото | `navigation` | `view_crop_photo` | - | - |
|
||||
| Открытие экрана Создание стикерпака | `navigation` | `view_create_sticker_pack` | - | - |
|
||||
| Открытие экрана Добавление стикера в пак | `navigation` | `view_add_sticker_to_pack` | - | - |
|
||||
| Открытие экрана Политика конфиденциальности | `navigation` | `view_terms_and_conditions` | - | - |
|
||||
| Открытие экрана Инструкция | `navigation` | `view_how_to` | - | - |
|
||||
|
||||
## 3. Политика конфиденциальности
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на ссылку политики | `terms` | `policy_link_click` | - | `link_url` |
|
||||
| Принятие политики | `terms` | `accept` | - | - |
|
||||
| Отклонение политики | `terms` | `decline` | - | - |
|
||||
|
||||
## 4. Главный экран (Home)
|
||||
|
||||
### 4.1 Верхний блок кнопок
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на кнопку обратной связи | `ui_interaction` | `feedback_button_click` | - | - |
|
||||
| Нажатие на кнопку инструкции | `ui_interaction` | `instruction_button_click` | - | - |
|
||||
| Нажатие на кнопку другого бота | `ui_interaction` | `other_bot_button_click` | - | `bot_url` |
|
||||
|
||||
### 4.2 Хедер
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на кнопку баланса | `ui_interaction` | `balance_button_click` | - | - |
|
||||
|
||||
### 4.3 Загрузка и обработка фото
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на загрузить фото | `photo` | `upload_photo_click` | - | - |
|
||||
| Применение обрезки фото | `photo` | `crop_photo_apply` | - | - |
|
||||
|
||||
### 4.4 Выбор стиля и параметров
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие кнопки Чиби стиль | `style` | `chibi_style_click` | - | - |
|
||||
| Нажатие кнопки Эмодзи стиль | `style` | `emoji_style_click` | - | - |
|
||||
| Выбор подкатегории Мем | `style` | `meme_subcategory_select` | - | `meme_id` |
|
||||
| Выбор подкатегории Коллекция | `style` | `collection_subcategory_select` | - | `collection_id` |
|
||||
|
||||
### 4.5 Генерация стикера
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Удачная отправка на генерацию | `generation` | `generation_success` | 1 | `preset_name` |
|
||||
| Неудачная отправка на генерацию | `generation` | `generation_failure` | - | `error_type` |
|
||||
|
||||
### 4.6 Футер
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на кнопку Главная | `footer` | `home_button_click` | - | - |
|
||||
| Нажатие на кнопку Галерея | `footer` | `gallery_button_click` | - | - |
|
||||
| Нажатие на кнопку Стикерпаки | `footer` | `sticker_packs_button_click` | - | - |
|
||||
| Нажатие на кнопку Профиль | `footer` | `profile_button_click` | - | - |
|
||||
|
||||
## 5. Экран Галерея
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Долгое удерживание на изображении | `gallery` | `image_long_press` | - | `image_id` |
|
||||
| Удаление изображения | `gallery` | `image_delete` | - | `image_id` |
|
||||
| Нажатие кнопки "Создать стикерпак" | `gallery` | `create_sticker_pack_click` | - | `from_gallery` |
|
||||
|
||||
## 6. Экран Стикерпаки
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие кнопки "Создать стикерпак" | `sticker_packs` | `create_sticker_pack_click` | - | `from_sticker_packs` |
|
||||
| Создание стикерпака | `sticker_packs` | `sticker_pack_created` | - | `pack_url` |
|
||||
| Ошибка создания стикерпака | `sticker_packs` | `sticker_pack_creation_error` | - | `error_type` |
|
||||
| Удаление стикерпака | `sticker_packs` | `sticker_pack_deleted` | - | `pack_id` |
|
||||
|
||||
## 7. Экран Профиль и попап с офферами
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Нажатие на оффер на экране профиля | `offers` | `profile_offer_click` | - | `offer_id` |
|
||||
| Нажатие на оффер в попапе | `offers` | `popup_offer_click` | - | `offer_id` |
|
||||
| Успешная покупка | `payment` | `purchase_success` | `stars_amount` | `star` |
|
||||
|
||||
## 8. Экраны онбординга
|
||||
|
||||
### 8.1 Экран приветствия (OnboardingWelcome)
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Открытие экрана приветствия | `navigation` | `view_onboarding_welcome` | - | - |
|
||||
| Нажатие кнопки "Далее" | `onboarding` | `welcome_next_click` | - | - |
|
||||
| Нажатие кнопки "Пропустить" | `onboarding` | `welcome_skip_click` | - | - |
|
||||
|
||||
### 8.2 Экран инструкции (OnboardingHowTo)
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Открытие экрана инструкции | `navigation` | `view_onboarding_how_to` | - | - |
|
||||
| Нажатие кнопки "Далее" | `onboarding` | `how_to_next_click` | - | - |
|
||||
| Нажатие кнопки "Назад" | `onboarding` | `how_to_back_click` | - | - |
|
||||
| Нажатие кнопки "Пропустить" | `onboarding` | `how_to_skip_click` | - | - |
|
||||
| Переключение слайда инструкции | `onboarding` | `how_to_slide_change` | - | `slide_index` |
|
||||
|
||||
### 8.3 Экран стикерпаков (OnboardingStickerPacks)
|
||||
|
||||
| Событие | Категория | Название события | Значение | Единица измерения |
|
||||
|---------|-----------|------------------|----------|-------------------|
|
||||
| Открытие экрана стикерпаков | `navigation` | `view_onboarding_sticker_packs` | - | - |
|
||||
| Нажатие кнопки "Начать" | `onboarding` | `sticker_packs_start_click` | - | - |
|
||||
| Нажатие кнопки "Назад" | `onboarding` | `sticker_packs_back_click` | - | - |
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Отслеживание открытия приложения
|
||||
|
||||
```typescript
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
// В компоненте App.tsx при монтировании
|
||||
useEffect(() => {
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'app',
|
||||
event_name: 'app_open'
|
||||
});
|
||||
}, []);
|
||||
```
|
||||
|
||||
### Отслеживание навигации
|
||||
|
||||
```typescript
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
// В компоненте страницы
|
||||
useEffect(() => {
|
||||
customAnalyticsService.trackNavigation('home');
|
||||
}, []);
|
||||
```
|
||||
|
||||
### Отслеживание генерации стикера
|
||||
|
||||
```typescript
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
// При успешной генерации стикера
|
||||
const handleGenerationSuccess = (presetName) => {
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'generation',
|
||||
event_name: 'generation_success',
|
||||
value: 1,
|
||||
unit: presetName
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Отслеживание покупки
|
||||
|
||||
```typescript
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
// При успешной покупке
|
||||
const handlePurchaseSuccess = (starsAmount) => {
|
||||
customAnalyticsService.trackPayment('purchase_success', starsAmount, 'star');
|
||||
};
|
||||
21
src/App.tsx
21
src/App.tsx
@ -2,8 +2,9 @@ import React, { lazy, Suspense, useEffect } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate, Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import Layout from './components/layout/Layout';
|
||||
import Home from './screens/Home';
|
||||
import { initializeUserId } from './constants/user';
|
||||
import { initializeUserId, getCurrentUserId } from './constants/user';
|
||||
import { trackScreenView } from './services/analyticsService';
|
||||
import customAnalyticsService from './services/customAnalyticsService';
|
||||
import { BalanceProvider } from './contexts/BalanceContext';
|
||||
|
||||
// Ленивая загрузка компонентов
|
||||
@ -39,9 +40,18 @@ const AppContent: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
// Инициализируем ID пользователя при запуске приложения
|
||||
initializeUserId().catch(error => {
|
||||
console.error('Ошибка при инициализации пользователя:', error);
|
||||
});
|
||||
initializeUserId()
|
||||
.then(() => {
|
||||
// Отправляем событие открытия приложения после успешной инициализации
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'app',
|
||||
event_name: 'app_open'
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при инициализации пользователя:', error);
|
||||
});
|
||||
|
||||
// Стабилизируем окно и отключаем вертикальные свайпы
|
||||
if (window.Telegram?.WebApp) {
|
||||
@ -98,6 +108,9 @@ const AppContent: React.FC = () => {
|
||||
|
||||
// Отправляем событие просмотра экрана
|
||||
trackScreenView(screenName);
|
||||
|
||||
// Отправляем событие навигации в нашу новую аналитику
|
||||
customAnalyticsService.trackNavigation(location.pathname.replace('/', ''));
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import BlockRenderer from '../blocks/BlockRenderer';
|
||||
import { homeScreenConfig } from '../../config/homeScreen';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
interface EmotionTypeSelectorProps {
|
||||
selectedEmotionTypeButtonId?: string;
|
||||
@ -30,6 +32,14 @@ const EmotionTypeSelector: React.FC<EmotionTypeSelectorProps> = ({
|
||||
// Обработчик выбора типа эмоций
|
||||
const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => {
|
||||
if (actionType === 'selectEmotionType') {
|
||||
// Отслеживаем событие выбора типа эмоций
|
||||
const eventName = actionValue === 'memes' ? 'memes_category_click' : 'prompts_category_click';
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'emotion_type',
|
||||
event_name: eventName
|
||||
});
|
||||
|
||||
onEmotionTypeSelect(actionValue as 'memes' | 'prompts', buttonId);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import BlockRenderer from '../blocks/BlockRenderer';
|
||||
import { homeScreenConfig } from '../../config/homeScreen';
|
||||
import styles from '../../screens/Home.module.css';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
interface GenerationButtonProps {
|
||||
onStartGeneration: () => void;
|
||||
@ -25,6 +27,13 @@ const GenerationButton: React.FC<GenerationButtonProps> = ({
|
||||
// Обработчик нажатия на кнопку генерации
|
||||
const handleAction = (actionType: string, actionValue: string) => {
|
||||
if (actionType === 'function' && actionValue === 'startGeneration' && !isGenerating) {
|
||||
// Отслеживаем событие нажатия на кнопку генерации
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'generation',
|
||||
event_name: 'generate_button_click'
|
||||
});
|
||||
|
||||
onStartGeneration();
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import BlockRenderer from '../blocks/BlockRenderer';
|
||||
import { homeScreenConfig } from '../../config/homeScreen';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
interface MainActionsProps {
|
||||
onSendFeedback: () => void;
|
||||
@ -26,8 +28,21 @@ const MainActions: React.FC<MainActionsProps> = ({
|
||||
const handleAction = (actionType: string, actionValue: string) => {
|
||||
if (actionType === 'function') {
|
||||
if (actionValue === 'sendFeedback') {
|
||||
// Отслеживаем событие нажатия на кнопку обратной связи
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'ui_interaction',
|
||||
event_name: 'feedback_button_click'
|
||||
});
|
||||
onSendFeedback();
|
||||
} else if (actionValue === 'openTelegramBot') {
|
||||
// Отслеживаем событие нажатия на кнопку другого бота
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'ui_interaction',
|
||||
event_name: 'other_bot_button_click',
|
||||
unit: 'https://t.me/youtube_s_loader_bot'
|
||||
});
|
||||
onOpenTelegramBot();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import BlockRenderer from '../blocks/BlockRenderer';
|
||||
import { homeScreenConfig } from '../../config/homeScreen';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
interface PhotoUploadProps {
|
||||
onImageDataChange: (imageData: string) => void;
|
||||
@ -22,8 +24,17 @@ const PhotoUpload: React.FC<PhotoUploadProps> = ({
|
||||
|
||||
// Обработчик загрузки фото
|
||||
const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string, extraData?: any) => {
|
||||
if (actionType === 'uploadPhoto' && extraData?.imageData) {
|
||||
onImageDataChange(extraData.imageData);
|
||||
if (actionType === 'uploadPhoto') {
|
||||
// Отслеживаем событие нажатия на загрузить фото
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'photo',
|
||||
event_name: 'upload_photo_click'
|
||||
});
|
||||
|
||||
if (extraData?.imageData) {
|
||||
onImageDataChange(extraData.imageData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import BlockRenderer from '../blocks/BlockRenderer';
|
||||
import { homeScreenConfig } from '../../config/homeScreen';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
interface StyleSelectorProps {
|
||||
selectedStyleButtonId?: string;
|
||||
@ -25,6 +27,14 @@ const StyleSelector: React.FC<StyleSelectorProps> = ({
|
||||
// Обработчик выбора стиля
|
||||
const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => {
|
||||
if (actionType === 'selectStyle') {
|
||||
// Отслеживаем событие выбора стиля
|
||||
const eventName = actionValue === 'chibi' ? 'chibi_style_click' : 'emoji_style_click';
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'style',
|
||||
event_name: eventName
|
||||
});
|
||||
|
||||
onStyleSelect(actionValue, buttonId);
|
||||
}
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ import { paymentService } from '../../services/paymentService';
|
||||
import { tokenPacks } from '../../constants/tokenPacks';
|
||||
import NotificationModal from '../shared/NotificationModal';
|
||||
import { useBalance } from '../../contexts/BalanceContext';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
@ -70,7 +71,15 @@ const Header: React.FC = () => {
|
||||
{/* Баланс токенов */}
|
||||
<button
|
||||
className={styles.balance}
|
||||
onClick={() => setShowTokensModal(true)}
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на кнопку баланса
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'ui_interaction',
|
||||
event_name: 'balance_button_click'
|
||||
});
|
||||
setShowTokensModal(true);
|
||||
}}
|
||||
title="Нажмите чтобы пополнить баланс"
|
||||
>
|
||||
<span className={styles.balanceIcon}>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import styles from './Navigation.module.css';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
const NavigationComponent: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -13,7 +15,15 @@ const NavigationComponent: React.FC = () => {
|
||||
<div className={styles.container}>
|
||||
<div className={styles.list}>
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на кнопку Главная
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'footer',
|
||||
event_name: 'home_button_click'
|
||||
});
|
||||
navigate('/');
|
||||
}}
|
||||
className={`${styles.item} ${isActive('/') ? styles.active : ''}`}
|
||||
>
|
||||
<span className={styles.icon}>
|
||||
@ -26,7 +36,15 @@ const NavigationComponent: React.FC = () => {
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/gallery')}
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на кнопку Галерея
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'footer',
|
||||
event_name: 'gallery_button_click'
|
||||
});
|
||||
navigate('/gallery');
|
||||
}}
|
||||
className={`${styles.item} ${isActive('/gallery') ? styles.active : ''}`}
|
||||
>
|
||||
<span className={styles.icon}>
|
||||
@ -40,7 +58,15 @@ const NavigationComponent: React.FC = () => {
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/packs')}
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на кнопку Стикерпаки
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'footer',
|
||||
event_name: 'sticker_packs_button_click'
|
||||
});
|
||||
navigate('/packs');
|
||||
}}
|
||||
className={`${styles.item} ${isActive('/packs') ? styles.active : ''}`}
|
||||
>
|
||||
<span className={styles.icon}>
|
||||
@ -53,7 +79,15 @@ const NavigationComponent: React.FC = () => {
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/profile')}
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на кнопку Профиль
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'footer',
|
||||
event_name: 'profile_button_click'
|
||||
});
|
||||
navigate('/profile');
|
||||
}}
|
||||
className={`${styles.item} ${isActive('/profile') ? styles.active : ''}`}
|
||||
>
|
||||
<span className={styles.icon}>
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import { images } from '../../assets';
|
||||
import { TokenPack } from '../../constants/tokenPacks';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
import styles from './TokenPackCard.module.css';
|
||||
|
||||
interface TokenPackCardProps extends TokenPack {
|
||||
onBuy: () => void;
|
||||
className?: string;
|
||||
compact?: boolean;
|
||||
source?: 'popup' | 'profile'; // Источник отображения карточки
|
||||
}
|
||||
|
||||
const TokenPackCard: React.FC<TokenPackCardProps> = ({
|
||||
@ -20,12 +23,22 @@ const TokenPackCard: React.FC<TokenPackCardProps> = ({
|
||||
isPopular,
|
||||
isBestValue,
|
||||
onBuy,
|
||||
className = ''
|
||||
className = '',
|
||||
source = 'popup' // По умолчанию считаем, что карточка отображается в попапе
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.card} ${isPopular ? styles.popular : ''} ${isBestValue ? styles.bestValue : ''} ${className}`}
|
||||
onClick={onBuy} // Добавляем обработчик клика на всю карточку
|
||||
onClick={() => {
|
||||
// Отслеживаем событие нажатия на оффер
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'offers',
|
||||
event_name: source === 'popup' ? 'popup_offer_click' : 'profile_offer_click',
|
||||
unit: title
|
||||
});
|
||||
onBuy();
|
||||
}} // Добавляем обработчик клика на всю карточку
|
||||
style={{ cursor: 'pointer' }} // Добавляем стиль курсора, чтобы показать, что карточка кликабельна
|
||||
>
|
||||
{isPopular && (
|
||||
@ -67,6 +80,13 @@ const TokenPackCard: React.FC<TokenPackCardProps> = ({
|
||||
className={styles.buyButton}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Предотвращаем всплытие события
|
||||
// Отслеживаем событие нажатия на кнопку "КУПИТЬ"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'offers',
|
||||
event_name: source === 'popup' ? 'popup_offer_click' : 'profile_offer_click',
|
||||
unit: title
|
||||
});
|
||||
onBuy();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -7,12 +7,14 @@ interface TokenPacksListProps {
|
||||
onBuyPack: (packId: string) => void;
|
||||
className?: string;
|
||||
compact?: boolean;
|
||||
source?: 'popup' | 'profile'; // Источник отображения списка
|
||||
}
|
||||
|
||||
const TokenPacksList: React.FC<TokenPacksListProps> = ({
|
||||
onBuyPack,
|
||||
className = '',
|
||||
compact
|
||||
compact,
|
||||
source = 'popup' // По умолчанию считаем, что список отображается в попапе
|
||||
}) => {
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -40,6 +42,7 @@ const TokenPacksList: React.FC<TokenPacksListProps> = ({
|
||||
key={pack.id}
|
||||
{...pack}
|
||||
compact={compact}
|
||||
source={source}
|
||||
onBuy={() => onBuyPack(pack.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -5,6 +5,8 @@ import { checkSufficientBalance, updateBalanceWithRetries } from '../utils/balan
|
||||
import apiService from '../services/api';
|
||||
import { useBalance } from '../contexts/BalanceContext';
|
||||
import { WorkflowType } from '../constants/workflows';
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
|
||||
/**
|
||||
* Хук для управления состоянием генерации
|
||||
@ -232,6 +234,14 @@ export const useGenerationState = (
|
||||
// Устанавливаем таймер для обнаружения проблем с подключением (6 секунд)
|
||||
connectionTimeoutId = setTimeout(() => {
|
||||
console.log('Connection timeout triggered');
|
||||
|
||||
// Отслеживаем событие проблем с подключением
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'generation',
|
||||
event_name: 'connection_timeout'
|
||||
});
|
||||
|
||||
// Вызываем handleRequestTimeout из useNotifications через showNotification
|
||||
showNotification(
|
||||
'Генерация стикера',
|
||||
@ -318,6 +328,15 @@ export const useGenerationState = (
|
||||
// Получаем результат
|
||||
const { result } = response;
|
||||
|
||||
// Отслеживаем событие успешной отправки на генерацию
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'generation',
|
||||
event_name: 'generation_request_success',
|
||||
value: 1,
|
||||
unit: workflowType
|
||||
});
|
||||
|
||||
// Обновляем уведомление с информацией о позиции в очереди
|
||||
if (result.queue_position !== undefined) {
|
||||
const estimatedTime = apiService.calculateEstimatedWaitTime(result.queue_position);
|
||||
|
||||
@ -7,6 +7,7 @@ import { GeneratedImage } from '../types/api';
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
|
||||
import ValidationModal from '../components/shared/ValidationModal';
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
/**
|
||||
* Транслитерирует кириллический текст в латиницу.
|
||||
@ -201,6 +202,14 @@ const CreateStickerPack: React.FC = () => {
|
||||
packName
|
||||
);
|
||||
|
||||
// Отслеживаем событие успешного создания стикерпака
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'sticker_packs',
|
||||
event_name: 'sticker_pack_created',
|
||||
unit: packName
|
||||
});
|
||||
|
||||
// Переходим на страницу стикерпаков
|
||||
navigate('/packs');
|
||||
} catch (err) {
|
||||
@ -209,6 +218,14 @@ const CreateStickerPack: React.FC = () => {
|
||||
// Преобразуем ошибку в строку для поиска
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
|
||||
// Отслеживаем событие ошибки создания стикерпака
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'sticker_packs',
|
||||
event_name: 'sticker_pack_creation_error',
|
||||
unit: errorMessage
|
||||
});
|
||||
|
||||
// Проверяем, содержит ли сообщение об ошибке информацию о занятом имени
|
||||
if (errorMessage.includes('sticker set name is already occupied')) {
|
||||
setValidationTitle('Имя стикерпака уже занято');
|
||||
|
||||
@ -6,6 +6,8 @@ import { GeneratedImage, PendingTask } from '../types/api';
|
||||
import ImageViewer from '../components/shared/ImageViewer';
|
||||
import ImageWithFallback from '../components/shared/ImageWithFallback';
|
||||
import NotificationModal from '../components/shared/NotificationModal';
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
|
||||
const GalleryScreen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -38,6 +40,14 @@ const GalleryScreen: React.FC = () => {
|
||||
|
||||
// Обработчики для режима удаления
|
||||
const handleLongPress = useCallback((image: GeneratedImage) => {
|
||||
// Отслеживаем событие долгого нажатия на изображение
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'gallery',
|
||||
event_name: 'image_long_press',
|
||||
unit: String(image.id)
|
||||
});
|
||||
|
||||
setIsDeleteMode(true);
|
||||
}, []);
|
||||
|
||||
@ -64,6 +74,14 @@ const GalleryScreen: React.FC = () => {
|
||||
setIsDeleting(true);
|
||||
await apiService.deleteImage(selectedForDelete.link);
|
||||
|
||||
// Отслеживаем событие удаления изображения
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'gallery',
|
||||
event_name: 'image_delete',
|
||||
unit: String(selectedForDelete.id)
|
||||
});
|
||||
|
||||
// Обновляем список изображений
|
||||
setImages(prevImages =>
|
||||
prevImages.filter(img => img.id !== selectedForDelete.id)
|
||||
@ -85,6 +103,14 @@ const GalleryScreen: React.FC = () => {
|
||||
|
||||
// Обработчик для кнопки "Создать стикерпак"
|
||||
const handleCreateStickerPack = useCallback(() => {
|
||||
// Отслеживаем событие нажатия на кнопку "Создать стикерпак"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'gallery',
|
||||
event_name: 'create_sticker_pack_click',
|
||||
unit: 'from_gallery'
|
||||
});
|
||||
|
||||
navigate('/create-sticker-pack');
|
||||
}, [navigate]);
|
||||
|
||||
|
||||
@ -105,6 +105,7 @@ const Profile: React.FC = () => {
|
||||
<TokenPacksList
|
||||
onBuyPack={handleBuyPack}
|
||||
className={styles.tokenPacks}
|
||||
source="profile"
|
||||
/>
|
||||
|
||||
{/* Модальное окно успешной оплаты */}
|
||||
|
||||
@ -4,6 +4,7 @@ import styles from './StickerPacks.module.css';
|
||||
import { stickerService } from '../services/stickerService';
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
import NotificationModal from '../components/shared/NotificationModal';
|
||||
import customAnalyticsService from '../services/customAnalyticsService';
|
||||
|
||||
// Функция для удаления дописанной части из названия стикерпака
|
||||
const cleanPackTitle = (title: string): string => {
|
||||
@ -70,6 +71,14 @@ const StickerPacks: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const handleCreateStickerPack = () => {
|
||||
// Отслеживаем событие нажатия на кнопку "Создать стикерпак"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'sticker_packs',
|
||||
event_name: 'create_sticker_pack_click',
|
||||
unit: 'from_sticker_packs'
|
||||
});
|
||||
|
||||
navigate('/create-sticker-pack');
|
||||
};
|
||||
|
||||
@ -90,6 +99,15 @@ const StickerPacks: React.FC = () => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await stickerService.deleteStickerPack(packToDelete);
|
||||
|
||||
// Отслеживаем событие удаления стикерпака
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'sticker_packs',
|
||||
event_name: 'sticker_pack_deleted',
|
||||
unit: packToDelete
|
||||
});
|
||||
|
||||
setStickerPacks(prevPacks => prevPacks.filter(pack => pack.name !== packToDelete));
|
||||
setSelectedPack(null);
|
||||
setIsDeleting(false);
|
||||
|
||||
@ -1,16 +1,41 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import OnboardingLayout from '../../components/shared/OnboardingLayout';
|
||||
import styles from './OnboardingHowTo.module.css';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
const OnboardingHowTo: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Отслеживаем событие открытия экрана инструкции
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'navigation',
|
||||
event_name: 'view_onboarding_how_to'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleNext = () => {
|
||||
// Отслеживаем событие нажатия кнопки "Далее"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'onboarding',
|
||||
event_name: 'how_to_next_click'
|
||||
});
|
||||
|
||||
navigate('/onboarding/sticker-packs');
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
// Отслеживаем событие нажатия кнопки "Пропустить"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'onboarding',
|
||||
event_name: 'how_to_skip_click'
|
||||
});
|
||||
|
||||
localStorage.setItem('hasSeenOnboarding', 'true');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
@ -1,12 +1,30 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import OnboardingLayout from '../../components/shared/OnboardingLayout';
|
||||
import styles from './OnboardingStickerPacks.module.css';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
const OnboardingStickerPacks: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Отслеживаем событие открытия экрана стикерпаков
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'navigation',
|
||||
event_name: 'view_onboarding_sticker_packs'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleStart = () => {
|
||||
// Отслеживаем событие нажатия кнопки "Начать"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'onboarding',
|
||||
event_name: 'sticker_packs_start_click'
|
||||
});
|
||||
|
||||
// Устанавливаем флаг, что пользователь видел онбординг
|
||||
localStorage.setItem('hasSeenOnboarding', 'true');
|
||||
|
||||
|
||||
@ -1,16 +1,41 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import OnboardingLayout from '../../components/shared/OnboardingLayout';
|
||||
import { images } from '../../assets';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
const OnboardingWelcome: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Отслеживаем событие открытия экрана приветствия
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'navigation',
|
||||
event_name: 'view_onboarding_welcome'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleNext = () => {
|
||||
// Отслеживаем событие нажатия кнопки "Далее"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'onboarding',
|
||||
event_name: 'welcome_next_click'
|
||||
});
|
||||
|
||||
navigate('/onboarding/how-to');
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
// Отслеживаем событие нажатия кнопки "Пропустить"
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'onboarding',
|
||||
event_name: 'welcome_skip_click'
|
||||
});
|
||||
|
||||
localStorage.setItem('hasSeenOnboarding', 'true');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
@ -1,12 +1,30 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styles from './TermsAndConditions.module.css';
|
||||
import { images } from '../../assets';
|
||||
import customAnalyticsService from '../../services/customAnalyticsService';
|
||||
import { getCurrentUserId } from '../../constants/user';
|
||||
|
||||
const TermsAndConditions: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Отслеживаем событие открытия экрана условий
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'navigation',
|
||||
event_name: 'view_terms_and_conditions'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
// Отслеживаем событие принятия условий
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'terms',
|
||||
event_name: 'accept'
|
||||
});
|
||||
|
||||
// Устанавливаем флаги, что пользователь видел онбординг и принял условия
|
||||
localStorage.setItem('hasSeenOnboarding', 'true');
|
||||
localStorage.setItem('hasAcceptedTerms', 'true');
|
||||
@ -15,12 +33,29 @@ const TermsAndConditions: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
// Отслеживаем событие отклонения условий
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'terms',
|
||||
event_name: 'decline'
|
||||
});
|
||||
|
||||
// Закрываем приложение
|
||||
if (window.Telegram?.WebApp?.close) {
|
||||
window.Telegram.WebApp.close();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePolicyLinkClick = (linkUrl: string) => {
|
||||
// Отслеживаем событие нажатия на ссылку политики
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'terms',
|
||||
event_name: 'policy_link_click',
|
||||
unit: linkUrl
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
@ -40,6 +75,7 @@ const TermsAndConditions: React.FC = () => {
|
||||
className={styles.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => handlePolicyLinkClick("https://telegra.ph/Polzovatelskoe-soglashenie-03-19-13")}
|
||||
>
|
||||
Условия использования
|
||||
</a>
|
||||
@ -48,6 +84,7 @@ const TermsAndConditions: React.FC = () => {
|
||||
className={styles.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => handlePolicyLinkClick("https://telegra.ph/Politika-konfidencialnosti-03-19-10")}
|
||||
>
|
||||
Политика конфиденциальности
|
||||
</a>
|
||||
|
||||
107
src/services/customAnalyticsService.ts
Normal file
107
src/services/customAnalyticsService.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
|
||||
// Базовый URL API
|
||||
const API_BASE_URL = 'https://stickerserver.gymnasticstuff.uk';
|
||||
|
||||
// Интерфейс для данных события
|
||||
export interface EventData {
|
||||
telegram_id: number;
|
||||
event_category: string;
|
||||
event_name: string;
|
||||
value?: number;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет событие аналитики на сервер
|
||||
* @param eventData Данные события
|
||||
* @returns Promise<boolean> Успешность операции
|
||||
*/
|
||||
export const trackEvent = async (eventData: EventData): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/events/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(eventData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Событие аналитики успешно отправлено:', result);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке события аналитики:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Отслеживает событие использования функционала
|
||||
* @param eventName Название события
|
||||
* @param value Числовое значение (опционально)
|
||||
* @param unit Единица измерения (опционально)
|
||||
* @returns Promise<boolean> Успешность операции
|
||||
*/
|
||||
export const trackUsage = async (
|
||||
eventName: string,
|
||||
value?: number,
|
||||
unit?: string
|
||||
): Promise<boolean> => {
|
||||
return trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'usage',
|
||||
event_name: eventName,
|
||||
value,
|
||||
unit
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Отслеживает событие платежа
|
||||
* @param eventName Название события
|
||||
* @param amount Сумма платежа
|
||||
* @param currency Валюта платежа (например, 'stars', 'USD')
|
||||
* @returns Promise<boolean> Успешность операции
|
||||
*/
|
||||
export const trackPayment = async (
|
||||
eventName: string,
|
||||
amount: number,
|
||||
currency: string
|
||||
): Promise<boolean> => {
|
||||
return trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'payment',
|
||||
event_name: eventName,
|
||||
value: amount,
|
||||
unit: currency
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Отслеживает событие навигации по приложению
|
||||
* @param screenName Название экрана
|
||||
* @returns Promise<boolean> Успешность операции
|
||||
*/
|
||||
export const trackNavigation = async (screenName: string): Promise<boolean> => {
|
||||
return trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'navigation',
|
||||
event_name: `view_${screenName}`
|
||||
});
|
||||
};
|
||||
|
||||
// Экспортируем все функции по умолчанию
|
||||
const customAnalyticsService = {
|
||||
trackEvent,
|
||||
trackUsage,
|
||||
trackPayment,
|
||||
trackNavigation
|
||||
};
|
||||
|
||||
export default customAnalyticsService;
|
||||
@ -1,7 +1,7 @@
|
||||
import { TokenPack } from '../constants/tokenPacks';
|
||||
import apiService from '../services/api';
|
||||
import { getCurrentUserId } from '../constants/user';
|
||||
import { sendTargetEvent } from './analyticsService';
|
||||
import customAnalyticsService from './customAnalyticsService';
|
||||
|
||||
export const paymentService = {
|
||||
showBuyTokensPopup: async (pack: TokenPack, onSuccess?: (userData?: any) => void) => {
|
||||
@ -25,6 +25,15 @@ export const paymentService = {
|
||||
// Открываем встроенный платеж Telegram без предварительного подтверждения
|
||||
webApp.openInvoice(invoiceLink, async (status: 'paid' | 'cancelled' | 'failed' | 'pending') => {
|
||||
if (status === 'paid') {
|
||||
// Отслеживаем событие успешной покупки
|
||||
customAnalyticsService.trackEvent({
|
||||
telegram_id: getCurrentUserId(),
|
||||
event_category: 'payment',
|
||||
event_name: 'purchase_success',
|
||||
value: pack.tokens + pack.bonusTokens,
|
||||
unit: 'star'
|
||||
});
|
||||
|
||||
// Функция для выполнения одной попытки получения данных пользователя
|
||||
const fetchUserData = async (attempt: number) => {
|
||||
try {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user