доразметка событий на гпт4.1

This commit is contained in:
kazachilo 2025-04-28 16:07:07 +03:00
parent 181d89e733
commit 3c925708ab
8 changed files with 93 additions and 19 deletions

View File

@ -77,7 +77,7 @@ const AppContent: React.FC = () => {
else if (hasSeenOnboarding && !hasAcceptedTerms && !location.pathname.includes('/onboarding/terms')) { else if (hasSeenOnboarding && !hasAcceptedTerms && !location.pathname.includes('/onboarding/terms')) {
navigate('/onboarding/terms'); navigate('/onboarding/terms');
} }
}, [navigate, location.pathname]); }, []);
// Отслеживаем изменение маршрута для аналитики // Отслеживаем изменение маршрута для аналитики
useEffect(() => { useEffect(() => {

View File

@ -46,6 +46,7 @@ const BlockRenderer: React.FC<BlockRendererProps> = ({ block, onAction, extraPro
); );
return () => URL.revokeObjectURL(tempUrl); return () => URL.revokeObjectURL(tempUrl);
}} }}
onAction={onAction}
/>; />;
case 'divider': case 'divider':
return <DividerBlock block={block as DividerBlockType} />; return <DividerBlock block={block as DividerBlockType} />;

View File

@ -6,9 +6,10 @@ import { getTranslation } from '../../constants/translations';
interface UploadPhotoBlockProps { interface UploadPhotoBlockProps {
onPhotoSelect?: (file: File) => void; onPhotoSelect?: (file: File) => void;
previewUrl?: string; previewUrl?: string;
onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void;
} }
const UploadPhotoBlock: React.FC<UploadPhotoBlockProps> = ({ onPhotoSelect, previewUrl }) => { const UploadPhotoBlock: React.FC<UploadPhotoBlockProps> = ({ onPhotoSelect, previewUrl, onAction }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
@ -19,6 +20,11 @@ const UploadPhotoBlock: React.FC<UploadPhotoBlockProps> = ({ onPhotoSelect, prev
localStorage.removeItem('stickerPreviewUrl'); localStorage.removeItem('stickerPreviewUrl');
localStorage.removeItem('stickerImageData'); localStorage.removeItem('stickerImageData');
// Отправляем событие аналитики, если передан onAction
if (typeof onAction === 'function') {
onAction('uploadPhoto', '', undefined, undefined);
}
onPhotoSelect?.(file); onPhotoSelect?.(file);
navigate('/crop-photo', { state: { file } }); navigate('/crop-photo', { state: { file } });
} }

View File

@ -8,6 +8,9 @@ import { getCurrentUserId } from '../../constants/user';
interface GenerationButtonProps { interface GenerationButtonProps {
onStartGeneration: () => void; onStartGeneration: () => void;
isGenerating: boolean; isGenerating: boolean;
presetName?: string;
memeId?: string;
emotionType?: string;
} }
/** /**
@ -15,7 +18,10 @@ interface GenerationButtonProps {
*/ */
const GenerationButton: React.FC<GenerationButtonProps> = ({ const GenerationButton: React.FC<GenerationButtonProps> = ({
onStartGeneration, onStartGeneration,
isGenerating isGenerating,
presetName,
memeId,
emotionType
}) => { }) => {
// Находим блок кнопки генерации в конфигурации // Находим блок кнопки генерации в конфигурации
const generateButton = homeScreenConfig.homeScreen.blocks.find(block => block.type === 'generateButton'); const generateButton = homeScreenConfig.homeScreen.blocks.find(block => block.type === 'generateButton');
@ -28,11 +34,20 @@ const GenerationButton: React.FC<GenerationButtonProps> = ({
const handleAction = (actionType: string, actionValue: string) => { const handleAction = (actionType: string, actionValue: string) => {
if (actionType === 'function' && actionValue === 'startGeneration' && !isGenerating) { if (actionType === 'function' && actionValue === 'startGeneration' && !isGenerating) {
// Отслеживаем событие нажатия на кнопку генерации // Отслеживаем событие нажатия на кнопку генерации
customAnalyticsService.trackEvent({ // Аналитика: пресет или мем
telegram_id: getCurrentUserId(), if (emotionType === 'memes' && memeId) {
event_category: 'generation', customAnalyticsService.trackEvent({
event_name: 'generate_button_click' 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(); onStartGeneration();
} }

View File

@ -37,6 +37,17 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
// Обработчик выбора пресета // Обработчик выбора пресета
const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => { const handleAction = (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => {
if (actionType === 'selectPreset') { 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); onPresetSelect(actionValue, buttonId);
} else if (actionType === 'function' && actionValue === 'toggleInput') { } else if (actionType === 'function' && actionValue === 'toggleInput') {
onToggleInput(); onToggleInput();

View File

@ -1,10 +1,12 @@
import React, { useCallback } from 'react'; import React, { useCallback, useRef } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import TokenPacksModal from './TokenPacksModal'; import TokenPacksModal from './TokenPacksModal';
import { paymentService } from '../../services/paymentService'; import { paymentService } from '../../services/paymentService';
import { tokenPacks } from '../../constants/tokenPacks'; import { tokenPacks } from '../../constants/tokenPacks';
import { useBalance } from '../../contexts/BalanceContext'; import { useBalance } from '../../contexts/BalanceContext';
import { updateBalanceWithRetries } from '../../utils/balanceUtils'; import { updateBalanceWithRetries } from '../../utils/balanceUtils';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
interface TokenPacksModalContainerProps { interface TokenPacksModalContainerProps {
isVisible: boolean; isVisible: boolean;
@ -24,39 +26,64 @@ const TokenPacksModalContainer: React.FC<TokenPacksModalContainerProps> = ({
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { updateBalance } = useBalance(); const { updateBalance } = useBalance();
const attemptedRef = useRef(false);
/** /**
* Обработчик покупки пакета токенов * Обработчик покупки пакета токенов
*/ */
const handleBuyPack = useCallback((packId: string) => { const handleBuyPack = useCallback((packId: string) => {
const pack = tokenPacks.find(p => p.id === packId); const pack = tokenPacks.find(p => p.id === packId);
if (!pack) return; if (!pack) return;
// Аналитика: попытка оплаты
customAnalyticsService.trackEvent({
telegram_id: getCurrentUserId(),
event_category: 'payment',
event_name: 'purchase_attempt'
});
attemptedRef.current = true;
onClose(); onClose();
paymentService.showBuyTokensPopup(pack, async (userData) => { paymentService.showBuyTokensPopup(pack, async (userData) => {
// Обновляем баланс с повторными попытками // Обновляем баланс с повторными попытками
updateBalanceWithRetries(updateBalance); updateBalanceWithRetries(updateBalance);
// Вызываем колбэк успешной покупки, если он передан // Вызываем колбэк успешной покупки, если он передан
if (onSuccess) { if (onSuccess) {
onSuccess(); onSuccess();
} }
}); });
}, [onClose, updateBalance, 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(() => { const handleShowAllPacks = useCallback(() => {
onClose(); handleClose();
navigate('/profile'); navigate('/profile');
}, [onClose, navigate]); }, [handleClose, navigate]);
return ( return (
<TokenPacksModal <TokenPacksModal
isVisible={isVisible} isVisible={isVisible}
onClose={onClose} onClose={handleClose}
onShowAllPacks={handleShowAllPacks} onShowAllPacks={handleShowAllPacks}
missingTokens={missingTokens} missingTokens={missingTokens}
onBuyPack={handleBuyPack} onBuyPack={handleBuyPack}

View File

@ -264,7 +264,18 @@ const CropPhoto: React.FC = () => {
// Передаем не только URL, но и base64 данные // Передаем не только URL, но и base64 данные
// Убираем префикс data:image/jpeg;base64, оставляем только данные // Убираем префикс data:image/jpeg;base64, оставляем только данные
const imageData = previewUrl.split(',')[1]; 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 для навигации между страницами
localStorage.setItem('stickerPreviewUrl', previewUrl); localStorage.setItem('stickerPreviewUrl', previewUrl);
localStorage.setItem('stickerImageData', imageData); localStorage.setItem('stickerImageData', imageData);

View File

@ -252,6 +252,9 @@ const Home: React.FC = () => {
<GenerationButton <GenerationButton
onStartGeneration={startGeneration} onStartGeneration={startGeneration}
isGenerating={isGenerating} isGenerating={isGenerating}
presetName={selectedPresetId}
memeId={selectedMemeId}
emotionType={selectedEmotionType}
/> />
</div> </div>
</div> </div>