diff --git a/src/App.tsx b/src/App.tsx index 043fc06..decbaff 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import Layout from './components/layout/Layout'; import Home from './screens/Home'; import { initializeUserId } from './constants/user'; import { trackScreenView } from './services/analyticsService'; +import { BalanceProvider } from './contexts/BalanceContext'; // Ленивая загрузка компонентов const OnboardingWelcome = lazy(() => import('./screens/onboarding/OnboardingWelcome')); @@ -147,7 +148,9 @@ const AppContent: React.FC = () => { const App: React.FC = () => { return ( - + + + ); }; diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 4451d30..386760c 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -7,15 +7,16 @@ import TokenPacksModal from '../tokens/TokenPacksModal'; import { paymentService } from '../../services/paymentService'; import { tokenPacks } from '../../constants/tokenPacks'; import NotificationModal from '../shared/NotificationModal'; +import { useBalance } from '../../contexts/BalanceContext'; import styles from './Header.module.css'; const Header: React.FC = () => { const navigate = useNavigate(); + const { balance, updateBalance } = useBalance(); // Используем контекст баланса const [user, setUser] = useState({ telegramId: 0, username: '', - avatarUrl: '', - balance: 0 + avatarUrl: '' }); const [showTokensModal, setShowTokensModal] = useState(false); const [lastPurchasedPack, setLastPurchasedPack] = useState(null); @@ -27,34 +28,21 @@ const Header: React.FC = () => { // Получаем информацию о пользователе const userInfo = getUserInfo(); - const fetchData = async () => { - try { - // Получаем баланс пользователя - const balance = await apiService.getBalance(getCurrentUserId()); - - // Если есть данные из Telegram, обновляем состояние - if (isTelegramWebAppAvailable()) { - setUser({ - telegramId: userInfo.id, - username: userInfo.first_name + (userInfo.last_name ? ` ${userInfo.last_name}` : ''), - avatarUrl: userInfo.photo_url || '/ava.jpg', // Используем фото из Telegram или дефолтное - balance: balance - }); - } else { - // Для локальной разработки - setUser({ - telegramId: 12345678, - username: "TestUser", - avatarUrl: "/ava.jpg", - balance: balance - }); - } - } catch (error) { - console.error('Error fetching user data:', error); - } - }; - - fetchData(); + // Если есть данные из Telegram, обновляем состояние + if (isTelegramWebAppAvailable()) { + setUser({ + telegramId: userInfo.id, + username: userInfo.first_name + (userInfo.last_name ? ` ${userInfo.last_name}` : ''), + avatarUrl: userInfo.photo_url || '/ava.jpg' // Используем фото из Telegram или дефолтное + }); + } else { + // Для локальной разработки + setUser({ + telegramId: 12345678, + username: "TestUser", + avatarUrl: "/ava.jpg" + }); + } }, []); return ( @@ -89,7 +77,7 @@ const Header: React.FC = () => { Токены - {user.balance} + {balance} @@ -110,13 +98,8 @@ const Header: React.FC = () => { paymentService.showBuyTokensPopup(pack, async (userData) => { if (userData) { - // Обновляем данные на основе полученной информации - if (userData.balance !== undefined) { - setUser(prev => ({ - ...prev, - balance: userData.balance - })); - } + // Обновляем баланс через контекст + updateBalance(); // Показываем модальное окно с информацией об успешной оплате setNotificationTitle('Оплата успешна!'); @@ -128,11 +111,8 @@ const Header: React.FC = () => { // Получаем баланс пользователя const balance = await apiService.getBalance(getCurrentUserId()); - // Обновляем баланс пользователя - setUser(prev => ({ - ...prev, - balance: balance - })); + // Обновляем баланс через контекст + updateBalance(); // Показываем модальное окно с информацией об успешной оплате setNotificationTitle('Оплата успешна!'); diff --git a/src/contexts/BalanceContext.tsx b/src/contexts/BalanceContext.tsx new file mode 100644 index 0000000..4973f14 --- /dev/null +++ b/src/contexts/BalanceContext.tsx @@ -0,0 +1,56 @@ +import React, { createContext, useState, useEffect, useContext, useCallback } from 'react'; +import apiService from '../services/api'; +import { getCurrentUserId } from '../constants/user'; + +interface BalanceContextType { + balance: number; + updateBalance: () => Promise; +} + +const BalanceContext = createContext(undefined); + +export const BalanceProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [balance, setBalance] = useState(0); + + // Функция для обновления баланса + const updateBalance = useCallback(async () => { + try { + console.log('Обновление баланса...'); + const newBalance = await apiService.getBalance(getCurrentUserId()); + console.log(`Текущий баланс: ${newBalance}`); + setBalance(newBalance); + } catch (error) { + console.error('Ошибка при обновлении баланса:', error); + } + }, []); + + // Инициализация баланса при монтировании компонента + useEffect(() => { + updateBalance(); + }, [updateBalance]); + + // Настройка периодического обновления баланса каждые 45 секунд + useEffect(() => { + const intervalId = setInterval(() => { + updateBalance(); + }, 45000); // 45 секунд + + // Очистка интервала при размонтировании компонента + return () => clearInterval(intervalId); + }, [updateBalance]); + + return ( + + {children} + + ); +}; + +// Хук для использования контекста баланса +export const useBalance = (): BalanceContextType => { + const context = useContext(BalanceContext); + if (context === undefined) { + throw new Error('useBalance must be used within a BalanceProvider'); + } + return context; +}; diff --git a/src/screens/Home.tsx b/src/screens/Home.tsx index 0dd32a4..2381b21 100644 --- a/src/screens/Home.tsx +++ b/src/screens/Home.tsx @@ -11,6 +11,7 @@ import TokenPacksModal from '../components/tokens/TokenPacksModal'; import { paymentService } from '../services/paymentService'; import { tokenPacks } from '../constants/tokenPacks'; import { getCurrentUserId } from '../constants/user'; +import { useBalance } from '../contexts/BalanceContext'; // Интерфейс для хранения данных о последней генерации interface LastGenerationData { @@ -23,6 +24,7 @@ interface LastGenerationData { const Home: React.FC = () => { const navigate = useNavigate(); const feedbackHandlerRef = useRef(null); + const { updateBalance } = useBalance(); // Используем контекст баланса // eslint-disable-next-line @typescript-eslint/no-unused-vars const [previewUrl, setPreviewUrl] = useState(() => { @@ -191,6 +193,30 @@ const Home: React.FC = () => { customPrompt: userPrompt }); + // Функция для выполнения серии запросов на обновление баланса + const updateBalanceWithRetries = () => { + // Функция для выполнения одной попытки обновления баланса + const fetchBalance = async (attempt: number) => { + try { + console.log(`Попытка ${attempt}/5 обновления баланса после генерации...`); + await updateBalance(); + } catch (error) { + console.error(`Ошибка при обновлении баланса (попытка ${attempt}/5):`, error); + } + }; + + // Выполняем первую попытку сразу + fetchBalance(1); + + // Выполняем остальные попытки с интервалом в 1 секунду + for (let i = 2; i <= 5; i++) { + setTimeout(() => fetchBalance(i), (i - 1) * 1000); + } + }; + + // Запускаем серию запросов на обновление баланса + updateBalanceWithRetries(); + // Проверяем, была ли ошибка перевода if (response.translationFailed) { setNotificationTitle('Недопустимый промпт'); @@ -385,7 +411,30 @@ const Home: React.FC = () => { paymentService.showBuyTokensPopup(pack, async (userData) => { if (userData) { - // Обновляем данные на основе полученной информации + // Функция для выполнения серии запросов на обновление баланса + const updateBalanceWithRetries = () => { + // Функция для выполнения одной попытки обновления баланса + const fetchBalance = async (attempt: number) => { + try { + console.log(`Попытка ${attempt}/5 обновления баланса после пополнения...`); + await updateBalance(); + } catch (error) { + console.error(`Ошибка при обновлении баланса (попытка ${attempt}/5):`, error); + } + }; + + // Выполняем первую попытку сразу + fetchBalance(1); + + // Выполняем остальные попытки с интервалом в 1 секунду + for (let i = 2; i <= 5; i++) { + setTimeout(() => fetchBalance(i), (i - 1) * 1000); + } + }; + + // Запускаем серию запросов на обновление баланса + updateBalanceWithRetries(); + // Показываем модальное окно с информацией об успешной оплате setNotificationTitle('Оплата успешна!'); setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов.`); @@ -400,6 +449,30 @@ const Home: React.FC = () => { // Получаем баланс пользователя const balance = await apiService.getBalance(getCurrentUserId()); + // Функция для выполнения серии запросов на обновление баланса + const updateBalanceWithRetries = () => { + // Функция для выполнения одной попытки обновления баланса + const fetchBalance = async (attempt: number) => { + try { + console.log(`Попытка ${attempt}/5 обновления баланса после пополнения...`); + await updateBalance(); + } catch (error) { + console.error(`Ошибка при обновлении баланса (попытка ${attempt}/5):`, error); + } + }; + + // Выполняем первую попытку сразу + fetchBalance(1); + + // Выполняем остальные попытки с интервалом в 1 секунду + for (let i = 2; i <= 5; i++) { + setTimeout(() => fetchBalance(i), (i - 1) * 1000); + } + }; + + // Запускаем серию запросов на обновление баланса + updateBalanceWithRetries(); + // Показываем модальное окно с информацией об успешной оплате setNotificationTitle('Оплата успешна!'); setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов. Ваш текущий баланс: ${balance} токенов.`); diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 93a138b..5d10bee 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -24,25 +24,32 @@ export const paymentService = { // Открываем встроенный платеж Telegram без предварительного подтверждения webApp.openInvoice(invoiceLink, async (status: 'paid' | 'cancelled' | 'failed' | 'pending') => { if (status === 'paid') { - // Добавляем задержку перед запросом данных пользователя, - // чтобы дать серверу время на обработку транзакции и обновление баланса - setTimeout(async () => { + // Функция для выполнения одной попытки получения данных пользователя + const fetchUserData = async (attempt: number) => { try { - // Получаем обновленную информацию о пользователе + console.log(`Попытка ${attempt}/5 получения данных пользователя...`); const userData = await apiService.getUserInfo(userId); if (onSuccess) { onSuccess(userData); } } catch (error) { - console.error('Ошибка при получении данных пользователя:', error); + console.error(`Ошибка при получении данных пользователя (попытка ${attempt}/5):`, error); - // Даже если не удалось получить данные, вызываем onSuccess - if (onSuccess) { + // Если это последняя попытка и произошла ошибка, вызываем onSuccess без параметров + if (attempt === 5 && onSuccess) { onSuccess(); } } - }, 1000); // Задержка в 1 секунду + }; + + // Выполняем первую попытку сразу + fetchUserData(1); + + // Выполняем остальные попытки с интервалом в 1 секунду + for (let i = 2; i <= 5; i++) { + setTimeout(() => fetchUserData(i), (i - 1) * 1000); + } } }); } catch (error) {