diff --git a/src/App.tsx b/src/App.tsx index 3f5ca9f..51ed7c5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -77,7 +77,7 @@ const AppContent: React.FC = () => { else if (hasSeenOnboarding && !hasAcceptedTerms && !location.pathname.includes('/onboarding/terms')) { navigate('/onboarding/terms'); } - }, [navigate, location.pathname]); + }, []); // Отслеживаем изменение маршрута для аналитики useEffect(() => { diff --git a/src/components/blocks/BlockRenderer.tsx b/src/components/blocks/BlockRenderer.tsx index 8ba8a86..d15f2d5 100644 --- a/src/components/blocks/BlockRenderer.tsx +++ b/src/components/blocks/BlockRenderer.tsx @@ -46,6 +46,7 @@ const BlockRenderer: React.FC = ({ block, onAction, extraPro ); return () => URL.revokeObjectURL(tempUrl); }} + onAction={onAction} />; case 'divider': return ; diff --git a/src/components/blocks/UploadPhotoBlock.tsx b/src/components/blocks/UploadPhotoBlock.tsx index 262ddc5..4626dcd 100644 --- a/src/components/blocks/UploadPhotoBlock.tsx +++ b/src/components/blocks/UploadPhotoBlock.tsx @@ -6,9 +6,10 @@ import { getTranslation } from '../../constants/translations'; interface UploadPhotoBlockProps { onPhotoSelect?: (file: File) => void; previewUrl?: string; + onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void; } -const UploadPhotoBlock: React.FC = ({ onPhotoSelect, previewUrl }) => { +const UploadPhotoBlock: React.FC = ({ onPhotoSelect, previewUrl, onAction }) => { const navigate = useNavigate(); const fileInputRef = useRef(null); const [isDragging, setIsDragging] = useState(false); @@ -19,6 +20,11 @@ const UploadPhotoBlock: React.FC = ({ onPhotoSelect, prev localStorage.removeItem('stickerPreviewUrl'); localStorage.removeItem('stickerImageData'); + // Отправляем событие аналитики, если передан onAction + if (typeof onAction === 'function') { + onAction('uploadPhoto', '', undefined, undefined); + } + onPhotoSelect?.(file); navigate('/crop-photo', { state: { file } }); } diff --git a/src/components/generation/GenerationButton.tsx b/src/components/generation/GenerationButton.tsx index 5e6babc..f6db2be 100644 --- a/src/components/generation/GenerationButton.tsx +++ b/src/components/generation/GenerationButton.tsx @@ -8,6 +8,9 @@ import { getCurrentUserId } from '../../constants/user'; interface GenerationButtonProps { onStartGeneration: () => void; isGenerating: boolean; + presetName?: string; + memeId?: string; + emotionType?: string; } /** @@ -15,7 +18,10 @@ interface GenerationButtonProps { */ const GenerationButton: React.FC = ({ onStartGeneration, - isGenerating + isGenerating, + presetName, + memeId, + emotionType }) => { // Находим блок кнопки генерации в конфигурации const generateButton = homeScreenConfig.homeScreen.blocks.find(block => block.type === 'generateButton'); @@ -28,11 +34,20 @@ const GenerationButton: React.FC = ({ 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' - }); + // Аналитика: пресет или мем + if (emotionType === 'memes' && memeId) { + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'generation_meme', + event_name: memeId + }); + } else if (presetName) { + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'generation_preset', + event_name: presetName + }); + } onStartGeneration(); } diff --git a/src/components/generation/PresetSelector.tsx b/src/components/generation/PresetSelector.tsx index bc6dc32..137b1da 100644 --- a/src/components/generation/PresetSelector.tsx +++ b/src/components/generation/PresetSelector.tsx @@ -37,6 +37,17 @@ const PresetSelector: React.FC = ({ // Обработчик выбора пресета const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => { if (actionType === 'selectPreset') { + // Отправляем событие выбора пресета + import('../../services/customAnalyticsService').then(({ default: customAnalyticsService }) => { + import('../../constants/user').then(({ getCurrentUserId }) => { + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'generation', + event_name: 'preset_select', + unit: actionValue + }); + }); + }); onPresetSelect(actionValue, buttonId); } else if (actionType === 'function' && actionValue === 'toggleInput') { onToggleInput(); diff --git a/src/components/tokens/TokenPacksModalContainer.tsx b/src/components/tokens/TokenPacksModalContainer.tsx index de020c8..5575361 100644 --- a/src/components/tokens/TokenPacksModalContainer.tsx +++ b/src/components/tokens/TokenPacksModalContainer.tsx @@ -1,10 +1,12 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import TokenPacksModal from './TokenPacksModal'; import { paymentService } from '../../services/paymentService'; import { tokenPacks } from '../../constants/tokenPacks'; import { useBalance } from '../../contexts/BalanceContext'; import { updateBalanceWithRetries } from '../../utils/balanceUtils'; +import customAnalyticsService from '../../services/customAnalyticsService'; +import { getCurrentUserId } from '../../constants/user'; interface TokenPacksModalContainerProps { isVisible: boolean; @@ -24,39 +26,64 @@ const TokenPacksModalContainer: React.FC = ({ }) => { const navigate = useNavigate(); const { updateBalance } = useBalance(); - + const attemptedRef = useRef(false); + /** * Обработчик покупки пакета токенов */ const handleBuyPack = useCallback((packId: string) => { const pack = tokenPacks.find(p => p.id === packId); if (!pack) return; - + + // Аналитика: попытка оплаты + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'payment', + event_name: 'purchase_attempt' + }); + attemptedRef.current = true; + onClose(); - + paymentService.showBuyTokensPopup(pack, async (userData) => { // Обновляем баланс с повторными попытками updateBalanceWithRetries(updateBalance); - + // Вызываем колбэк успешной покупки, если он передан if (onSuccess) { onSuccess(); } }); }, [onClose, updateBalance, onSuccess]); - + + /** + * Обработчик отмены/закрытия модалки + */ + const handleClose = useCallback(() => { + if (!attemptedRef.current) { + // Аналитика: отмена оплаты + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'payment', + event_name: 'purchase_cancel' + }); + } + onClose(); + attemptedRef.current = false; + }, [onClose]); + /** * Обработчик нажатия на кнопку "Показать все пакеты" */ const handleShowAllPacks = useCallback(() => { - onClose(); + handleClose(); navigate('/profile'); - }, [onClose, navigate]); - + }, [handleClose, navigate]); + return ( { // Передаем не только URL, но и base64 данные // Убираем префикс data:image/jpeg;base64, оставляем только данные const imageData = previewUrl.split(',')[1]; - + + // Отправляем событие применения обрезки + import('../services/customAnalyticsService').then(({ default: customAnalyticsService }) => { + import('../constants/user').then(({ getCurrentUserId }) => { + customAnalyticsService.trackEvent({ + telegram_id: getCurrentUserId(), + event_category: 'photo', + event_name: 'crop_photo_apply' + }); + }); + }); + // Сохраняем данные в localStorage для навигации между страницами localStorage.setItem('stickerPreviewUrl', previewUrl); localStorage.setItem('stickerImageData', imageData); diff --git a/src/screens/Home.tsx b/src/screens/Home.tsx index 0461d65..0da1ba3 100644 --- a/src/screens/Home.tsx +++ b/src/screens/Home.tsx @@ -252,6 +252,9 @@ const Home: React.FC = () => {