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 [user, setUser] = useState<SafeUser | null>(null);
|
||||||
const [shopItems, setShopItems] = useState<IShopItem[]>([]);
|
const [shopItems, setShopItems] = useState<IShopItem[]>([]);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { webApp, error: webAppError } = useTelegramWebApp();
|
const { webApp, error: webAppError, isInitialized } = useTelegramWebApp();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (webApp) {
|
if (webApp && isInitialized) {
|
||||||
initApp();
|
initApp();
|
||||||
}
|
}
|
||||||
}, [webApp]);
|
}, [webApp, isInitialized]);
|
||||||
|
|
||||||
const initApp = async () => {
|
const initApp = async () => {
|
||||||
try {
|
try {
|
||||||
@ -35,6 +35,10 @@ export default function MainApp() {
|
|||||||
|
|
||||||
// Авторизация пользователя
|
// Авторизация пользователя
|
||||||
const { user: telegramUser } = webApp.initDataUnsafe;
|
const { user: telegramUser } = webApp.initDataUnsafe;
|
||||||
|
if (!telegramUser) {
|
||||||
|
throw new Error('Пользователь не найден');
|
||||||
|
}
|
||||||
|
|
||||||
const authResponse = await auth(telegramUser.id.toString(), telegramUser.username || 'anonymous');
|
const authResponse = await auth(telegramUser.id.toString(), telegramUser.username || 'anonymous');
|
||||||
|
|
||||||
// Получение данных пользователя и магазина
|
// Получение данных пользователя и магазина
|
||||||
@ -45,6 +49,9 @@ export default function MainApp() {
|
|||||||
|
|
||||||
setUser(profileData as SafeUser);
|
setUser(profileData as SafeUser);
|
||||||
setShopItems(shopData);
|
setShopItems(shopData);
|
||||||
|
|
||||||
|
// Сообщаем Telegram, что приложение готово
|
||||||
|
webApp.ready();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Initialization error:', error);
|
console.error('Initialization error:', error);
|
||||||
toast({
|
toast({
|
||||||
@ -69,7 +76,7 @@ export default function MainApp() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading || !webApp) {
|
if (isLoading || !webApp || !isInitialized) {
|
||||||
return (
|
return (
|
||||||
<Center h="100vh">
|
<Center h="100vh">
|
||||||
<Spinner size="xl" />
|
<Spinner size="xl" />
|
||||||
|
@ -3,27 +3,73 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { isDemoMode, getDemoWebApp } from '../utils/demo';
|
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() {
|
export function useTelegramWebApp() {
|
||||||
const [webApp, setWebApp] = useState<any>(null);
|
const [webApp, setWebApp] = useState<WebApp | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initWebApp = async () => {
|
const initWebApp = async () => {
|
||||||
try {
|
try {
|
||||||
if (isDemoMode()) {
|
if (isDemoMode()) {
|
||||||
setWebApp(getDemoWebApp());
|
const demoWebApp = getDemoWebApp();
|
||||||
} else if (typeof window !== 'undefined') {
|
setWebApp(demoWebApp);
|
||||||
|
setIsInitialized(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
const WebAppModule = await import('@twa-dev/sdk');
|
const WebAppModule = await import('@twa-dev/sdk');
|
||||||
|
if (WebAppModule.default) {
|
||||||
setWebApp(WebAppModule.default);
|
setWebApp(WebAppModule.default);
|
||||||
|
setIsInitialized(true);
|
||||||
|
} else {
|
||||||
|
throw new Error('WebApp не найден');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Ошибка инициализации Telegram Web App');
|
|
||||||
console.error('WebApp initialization error:', err);
|
console.error('WebApp initialization error:', err);
|
||||||
|
setError('Ошибка инициализации Telegram Web App');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isInitialized) {
|
||||||
initWebApp();
|
initWebApp();
|
||||||
}, []);
|
}
|
||||||
|
}, [isInitialized]);
|
||||||
|
|
||||||
return { webApp, error };
|
return { webApp, error, isInitialized };
|
||||||
}
|
}
|
103
app/utils/api.ts
103
app/utils/api.ts
@ -1,73 +1,120 @@
|
|||||||
import axios from 'axios';
|
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({
|
const api = axios.create({
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
baseURL: API_URL,
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'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) => {
|
api.interceptors.request.use((config) => {
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return config;
|
return config;
|
||||||
}, (error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Интерцептор для обработки ошибок
|
// Интерцептор для обработки ошибок
|
||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response) {
|
if (error.response?.status === 401) {
|
||||||
// Ошибка от сервера
|
localStorage.removeItem('token');
|
||||||
return Promise.reject(error.response.data);
|
|
||||||
} else if (error.request) {
|
|
||||||
// Ошибка сети
|
|
||||||
return Promise.reject({ error: 'Ошибка сети. Проверьте подключение к интернету.' });
|
|
||||||
} else {
|
|
||||||
// Другие ошибки
|
|
||||||
return Promise.reject({ error: 'Произошла ошибка при выполнении запроса.' });
|
|
||||||
}
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const auth = async (telegramId: string, username: string) => {
|
export const auth = async (telegramId: string, username: string) => {
|
||||||
try {
|
if (isDemoMode()) {
|
||||||
|
localStorage.setItem('token', 'demo_token');
|
||||||
|
return { token: 'demo_token', user: demoData.user };
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.post('/auth', { telegramId, username });
|
const response = await api.post('/auth', { telegramId, username });
|
||||||
if (typeof window !== 'undefined') {
|
const { token } = response.data;
|
||||||
localStorage.setItem('token', response.data.token);
|
localStorage.setItem('token', token);
|
||||||
}
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
|
||||||
console.error('Auth error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProfile = async () => {
|
export const getProfile = async () => {
|
||||||
|
if (isDemoMode()) {
|
||||||
|
return demoData.user;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.get('/profile');
|
const response = await api.get('/profile');
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getShopItems = async () => {
|
export const getShopItems = async () => {
|
||||||
|
if (isDemoMode()) {
|
||||||
|
return demoData.shopItems;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.get('/shop');
|
const response = await api.get('/shop');
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const purchaseItem = async (itemId: string) => {
|
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;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transferBalance = async (recipientUsername: string, amount: number) => {
|
export const transferBalance = async (username: string, amount: number) => {
|
||||||
const response = await api.post('/transfer', { recipientUsername, amount });
|
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;
|
return response.data;
|
||||||
};
|
};
|
@ -1,22 +1,27 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
// Демо данные для тестирования без Telegram
|
// Демо данные для тестирования без Telegram
|
||||||
const demoUser = {
|
const demoUser = {
|
||||||
id: 'demo_user',
|
id: 'demo_user_123',
|
||||||
first_name: 'Demo',
|
first_name: 'Demo',
|
||||||
username: 'demo_user',
|
username: 'demo_user',
|
||||||
language_code: 'ru'
|
language_code: 'ru'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const demoInitData = {
|
||||||
|
query_id: 'demo_query',
|
||||||
|
user: demoUser,
|
||||||
|
auth_date: Date.now(),
|
||||||
|
hash: 'demo_hash'
|
||||||
|
};
|
||||||
|
|
||||||
export const isDemoMode = () => {
|
export const isDemoMode = () => {
|
||||||
return typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('demo');
|
return typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('demo');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDemoWebApp = () => {
|
export const getDemoWebApp = () => ({
|
||||||
return {
|
|
||||||
initData: 'demo_mode',
|
initData: 'demo_mode',
|
||||||
initDataUnsafe: {
|
initDataUnsafe: demoInitData,
|
||||||
user: demoUser,
|
|
||||||
start_param: 'demo'
|
|
||||||
},
|
|
||||||
platform: 'demo',
|
platform: 'demo',
|
||||||
colorScheme: 'light',
|
colorScheme: 'light',
|
||||||
themeParams: {
|
themeParams: {
|
||||||
@ -28,12 +33,32 @@ export const getDemoWebApp = () => {
|
|||||||
button_text_color: '#ffffff'
|
button_text_color: '#ffffff'
|
||||||
},
|
},
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
viewportHeight: window.innerHeight,
|
viewportHeight: typeof window !== 'undefined' ? window.innerHeight : 800,
|
||||||
viewportStableHeight: window.innerHeight,
|
viewportStableHeight: typeof window !== 'undefined' ? window.innerHeight : 800,
|
||||||
headerColor: '#ffffff',
|
headerColor: '#ffffff',
|
||||||
backgroundColor: '#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: () => {},
|
ready: () => {},
|
||||||
expand: () => {},
|
expand: () => {},
|
||||||
close: () => {}
|
close: () => {},
|
||||||
};
|
});
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user