init param
Добавление проверки isInitialized при инициализации приложения Проверку наличия данных пользователя в initDataUnsafe Вызов webApp.ready() после успешной инициализации Улучшенную обработку ошибок и состояний загрузки
This commit is contained in:
parent
8af9c21cb1
commit
0e304a8b2e
@ -17,13 +17,13 @@ export default function MainApp() {
|
||||
const [user, setUser] = useState<SafeUser | null>(null);
|
||||
const [shopItems, setShopItems] = useState<IShopItem[]>([]);
|
||||
const toast = useToast();
|
||||
const { webApp, error: webAppError } = useTelegramWebApp();
|
||||
const { webApp, error: webAppError, isInitialized } = useTelegramWebApp();
|
||||
|
||||
useEffect(() => {
|
||||
if (webApp) {
|
||||
if (webApp && isInitialized) {
|
||||
initApp();
|
||||
}
|
||||
}, [webApp]);
|
||||
}, [webApp, isInitialized]);
|
||||
|
||||
const initApp = async () => {
|
||||
try {
|
||||
@ -35,6 +35,10 @@ export default function MainApp() {
|
||||
|
||||
// Авторизация пользователя
|
||||
const { user: telegramUser } = webApp.initDataUnsafe;
|
||||
if (!telegramUser) {
|
||||
throw new Error('Пользователь не найден');
|
||||
}
|
||||
|
||||
const authResponse = await auth(telegramUser.id.toString(), telegramUser.username || 'anonymous');
|
||||
|
||||
// Получение данных пользователя и магазина
|
||||
@ -45,6 +49,9 @@ export default function MainApp() {
|
||||
|
||||
setUser(profileData as SafeUser);
|
||||
setShopItems(shopData);
|
||||
|
||||
// Сообщаем Telegram, что приложение готово
|
||||
webApp.ready();
|
||||
} catch (error: any) {
|
||||
console.error('Initialization error:', error);
|
||||
toast({
|
||||
@ -69,7 +76,7 @@ export default function MainApp() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading || !webApp) {
|
||||
if (isLoading || !webApp || !isInitialized) {
|
||||
return (
|
||||
<Center h="100vh">
|
||||
<Spinner size="xl" />
|
||||
|
@ -3,27 +3,73 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDemoMode, getDemoWebApp } from '../utils/demo';
|
||||
|
||||
export type WebApp = {
|
||||
initData: string;
|
||||
initDataUnsafe: {
|
||||
query_id: string;
|
||||
user: {
|
||||
id: string;
|
||||
first_name: string;
|
||||
username?: string;
|
||||
language_code: string;
|
||||
};
|
||||
auth_date: number;
|
||||
hash: string;
|
||||
};
|
||||
platform: string;
|
||||
colorScheme: string;
|
||||
themeParams: {
|
||||
bg_color: string;
|
||||
text_color: string;
|
||||
hint_color: string;
|
||||
link_color: string;
|
||||
button_color: string;
|
||||
button_text_color: string;
|
||||
};
|
||||
isExpanded: boolean;
|
||||
viewportHeight: number;
|
||||
viewportStableHeight: number;
|
||||
headerColor: string;
|
||||
backgroundColor: string;
|
||||
ready: () => void;
|
||||
expand: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
export function useTelegramWebApp() {
|
||||
const [webApp, setWebApp] = useState<any>(null);
|
||||
const [webApp, setWebApp] = useState<WebApp | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initWebApp = async () => {
|
||||
try {
|
||||
if (isDemoMode()) {
|
||||
setWebApp(getDemoWebApp());
|
||||
} else if (typeof window !== 'undefined') {
|
||||
const demoWebApp = getDemoWebApp();
|
||||
setWebApp(demoWebApp);
|
||||
setIsInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const WebAppModule = await import('@twa-dev/sdk');
|
||||
setWebApp(WebAppModule.default);
|
||||
if (WebAppModule.default) {
|
||||
setWebApp(WebAppModule.default);
|
||||
setIsInitialized(true);
|
||||
} else {
|
||||
throw new Error('WebApp не найден');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Ошибка инициализации Telegram Web App');
|
||||
console.error('WebApp initialization error:', err);
|
||||
setError('Ошибка инициализации Telegram Web App');
|
||||
}
|
||||
};
|
||||
|
||||
initWebApp();
|
||||
}, []);
|
||||
if (!isInitialized) {
|
||||
initWebApp();
|
||||
}
|
||||
}, [isInitialized]);
|
||||
|
||||
return { webApp, error };
|
||||
return { webApp, error, isInitialized };
|
||||
}
|
111
app/utils/api.ts
111
app/utils/api.ts
@ -1,73 +1,120 @@
|
||||
import axios from 'axios';
|
||||
import { isDemoMode } from './demo';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
withCredentials: true,
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 10000, // 10 секунд таймаут
|
||||
});
|
||||
|
||||
// Интерцептор для добавления токена к запросам
|
||||
// Демо-данные
|
||||
const demoData = {
|
||||
user: {
|
||||
_id: 'demo_user_id',
|
||||
telegramId: '12345',
|
||||
username: 'demo_user',
|
||||
level: 1,
|
||||
experience: 0,
|
||||
balance: 1000,
|
||||
achievements: [],
|
||||
},
|
||||
shopItems: [
|
||||
{
|
||||
_id: 'demo_item_1',
|
||||
name: 'Демо предмет 1',
|
||||
description: 'Описание демо предмета 1',
|
||||
price: 100,
|
||||
type: 'consumable',
|
||||
},
|
||||
{
|
||||
_id: 'demo_item_2',
|
||||
name: 'Демо предмет 2',
|
||||
description: 'Описание демо предмета 2',
|
||||
price: 200,
|
||||
type: 'permanent',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Интерцептор для добавления токена
|
||||
api.interceptors.request.use((config) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
}, (error) => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
// Интерцептор для обработки ошибок
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
// Ошибка от сервера
|
||||
return Promise.reject(error.response.data);
|
||||
} else if (error.request) {
|
||||
// Ошибка сети
|
||||
return Promise.reject({ error: 'Ошибка сети. Проверьте подключение к интернету.' });
|
||||
} else {
|
||||
// Другие ошибки
|
||||
return Promise.reject({ error: 'Произошла ошибка при выполнении запроса.' });
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export const auth = async (telegramId: string, username: string) => {
|
||||
try {
|
||||
const response = await api.post('/auth', { telegramId, username });
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('token', response.data.token);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Auth error:', error);
|
||||
throw error;
|
||||
if (isDemoMode()) {
|
||||
localStorage.setItem('token', 'demo_token');
|
||||
return { token: 'demo_token', user: demoData.user };
|
||||
}
|
||||
|
||||
const response = await api.post('/auth', { telegramId, username });
|
||||
const { token } = response.data;
|
||||
localStorage.setItem('token', token);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getProfile = async () => {
|
||||
if (isDemoMode()) {
|
||||
return demoData.user;
|
||||
}
|
||||
|
||||
const response = await api.get('/profile');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getShopItems = async () => {
|
||||
if (isDemoMode()) {
|
||||
return demoData.shopItems;
|
||||
}
|
||||
|
||||
const response = await api.get('/shop');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const purchaseItem = async (itemId: string) => {
|
||||
const response = await api.post('/shop/purchase', { itemId });
|
||||
if (isDemoMode()) {
|
||||
const item = demoData.shopItems.find(item => item._id === itemId);
|
||||
if (!item) {
|
||||
throw new Error('Предмет не найден');
|
||||
}
|
||||
if (demoData.user.balance < item.price) {
|
||||
throw new Error('Недостаточно средств');
|
||||
}
|
||||
demoData.user.balance -= item.price;
|
||||
return { user: demoData.user };
|
||||
}
|
||||
|
||||
const response = await api.post(`/shop/purchase/${itemId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const transferBalance = async (recipientUsername: string, amount: number) => {
|
||||
const response = await api.post('/transfer', { recipientUsername, amount });
|
||||
export const transferBalance = async (username: string, amount: number) => {
|
||||
if (isDemoMode()) {
|
||||
if (demoData.user.balance < amount) {
|
||||
throw new Error('Недостаточно средств');
|
||||
}
|
||||
demoData.user.balance -= amount;
|
||||
return { balance: demoData.user.balance };
|
||||
}
|
||||
|
||||
const response = await api.post('/transfer', { username, amount });
|
||||
return response.data;
|
||||
};
|
@ -1,39 +1,64 @@
|
||||
'use client';
|
||||
|
||||
// Демо данные для тестирования без Telegram
|
||||
const demoUser = {
|
||||
id: 'demo_user',
|
||||
id: 'demo_user_123',
|
||||
first_name: 'Demo',
|
||||
username: 'demo_user',
|
||||
language_code: 'ru'
|
||||
};
|
||||
|
||||
const demoInitData = {
|
||||
query_id: 'demo_query',
|
||||
user: demoUser,
|
||||
auth_date: Date.now(),
|
||||
hash: 'demo_hash'
|
||||
};
|
||||
|
||||
export const isDemoMode = () => {
|
||||
return typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('demo');
|
||||
};
|
||||
|
||||
export const getDemoWebApp = () => {
|
||||
return {
|
||||
initData: 'demo_mode',
|
||||
initDataUnsafe: {
|
||||
user: demoUser,
|
||||
start_param: 'demo'
|
||||
},
|
||||
platform: 'demo',
|
||||
colorScheme: 'light',
|
||||
themeParams: {
|
||||
bg_color: '#ffffff',
|
||||
text_color: '#000000',
|
||||
hint_color: '#999999',
|
||||
link_color: '#2481cc',
|
||||
button_color: '#2481cc',
|
||||
button_text_color: '#ffffff'
|
||||
},
|
||||
isExpanded: true,
|
||||
viewportHeight: window.innerHeight,
|
||||
viewportStableHeight: window.innerHeight,
|
||||
headerColor: '#ffffff',
|
||||
backgroundColor: '#ffffff',
|
||||
ready: () => {},
|
||||
expand: () => {},
|
||||
close: () => {}
|
||||
};
|
||||
};
|
||||
export const getDemoWebApp = () => ({
|
||||
initData: 'demo_mode',
|
||||
initDataUnsafe: demoInitData,
|
||||
platform: 'demo',
|
||||
colorScheme: 'light',
|
||||
themeParams: {
|
||||
bg_color: '#ffffff',
|
||||
text_color: '#000000',
|
||||
hint_color: '#999999',
|
||||
link_color: '#2481cc',
|
||||
button_color: '#2481cc',
|
||||
button_text_color: '#ffffff'
|
||||
},
|
||||
isExpanded: true,
|
||||
viewportHeight: typeof window !== 'undefined' ? window.innerHeight : 800,
|
||||
viewportStableHeight: typeof window !== 'undefined' ? window.innerHeight : 800,
|
||||
headerColor: '#ffffff',
|
||||
backgroundColor: '#ffffff',
|
||||
isClosingConfirmationEnabled: true,
|
||||
BackButton: {
|
||||
isVisible: false,
|
||||
onClick: () => {},
|
||||
},
|
||||
MainButton: {
|
||||
text: '',
|
||||
color: '#2481cc',
|
||||
textColor: '#ffffff',
|
||||
isVisible: false,
|
||||
isProgressVisible: false,
|
||||
isActive: true,
|
||||
setText: () => {},
|
||||
onClick: () => {},
|
||||
show: () => {},
|
||||
hide: () => {},
|
||||
enable: () => {},
|
||||
disable: () => {},
|
||||
showProgress: () => {},
|
||||
hideProgress: () => {},
|
||||
},
|
||||
ready: () => {},
|
||||
expand: () => {},
|
||||
close: () => {},
|
||||
});
|
Loading…
Reference in New Issue
Block a user