доразметка событий на гпт4.1
This commit is contained in:
parent
181d89e733
commit
3c925708ab
@ -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(() => {
|
||||||
|
|||||||
@ -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} />;
|
||||||
|
|||||||
@ -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 } });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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,6 +26,7 @@ const TokenPacksModalContainer: React.FC<TokenPacksModalContainerProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { updateBalance } = useBalance();
|
const { updateBalance } = useBalance();
|
||||||
|
const attemptedRef = useRef(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик покупки пакета токенов
|
* Обработчик покупки пакета токенов
|
||||||
@ -32,6 +35,14 @@ const TokenPacksModalContainer: React.FC<TokenPacksModalContainerProps> = ({
|
|||||||
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) => {
|
||||||
@ -45,18 +56,34 @@ const TokenPacksModalContainer: React.FC<TokenPacksModalContainerProps> = ({
|
|||||||
});
|
});
|
||||||
}, [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}
|
||||||
|
|||||||
@ -265,6 +265,17 @@ const CropPhoto: React.FC = () => {
|
|||||||
// Убираем префикс 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);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user