Добавили динамический импорт Telegram Web App SDK с помощью import(), чтобы он загружался только на клиенте Добавили состояние загрузки и компонент Spinner для лучшего UX Исправили типы в компонентах: Используем IShopItem вместо собственного интерфейса ShopItem Создали тип SafeUser, который исключает свойства mongoose Document из типа пользователя Добавили безопасную проверку на наличие пользователя в данных Telegram WebApp
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import { Container, Tabs, TabList, TabPanels, Tab, TabPanel, useToast, Spinner, Center } from '@chakra-ui/react';
|
||
import { UserProfile } from './UserProfile';
|
||
import { Shop } from './Shop';
|
||
import { TransferBalance } from './TransferBalance';
|
||
import * as api from '../utils/api';
|
||
import { IUser } from '../../backend/models/User';
|
||
import { IShopItem } from '../../backend/models/ShopItem';
|
||
|
||
type SafeUser = Omit<IUser, keyof Document>;
|
||
|
||
export function MainApp() {
|
||
const [user, setUser] = useState<SafeUser | null>(null);
|
||
const [shopItems, setShopItems] = useState<IShopItem[]>([]);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const toast = useToast();
|
||
|
||
useEffect(() => {
|
||
const initApp = async () => {
|
||
try {
|
||
setIsLoading(true);
|
||
// Динамически импортируем SDK только на клиенте
|
||
const WebApp = (await import('@twa-dev/sdk')).default;
|
||
|
||
const initData = WebApp.initData;
|
||
if (!initData) {
|
||
throw new Error('Приложение должно быть открыто в Telegram');
|
||
}
|
||
|
||
// Авторизуем пользователя
|
||
const authData = await api.auth(
|
||
WebApp.initDataUnsafe.user?.id.toString() || '',
|
||
WebApp.initDataUnsafe.user?.username || ''
|
||
);
|
||
setUser(authData.user);
|
||
|
||
// Загружаем предметы магазина
|
||
const items = await api.getShopItems();
|
||
setShopItems(items);
|
||
} catch (error: any) {
|
||
toast({
|
||
title: 'Ошибка инициализации',
|
||
description: error.message,
|
||
status: 'error',
|
||
duration: 5000,
|
||
isClosable: true,
|
||
});
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
initApp();
|
||
}, []);
|
||
|
||
const handlePurchase = async (itemId: string) => {
|
||
try {
|
||
const result = await api.purchaseItem(itemId);
|
||
setUser(result.user);
|
||
toast({
|
||
title: 'Покупка успешна!',
|
||
status: 'success',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
} catch (error: any) {
|
||
toast({
|
||
title: 'Ошибка покупки',
|
||
description: error.response?.data?.error || 'Произошла ошибка',
|
||
status: 'error',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleTransfer = async (recipientUsername: string, amount: number) => {
|
||
try {
|
||
const result = await api.transferBalance(recipientUsername, amount);
|
||
setUser(prev => prev ? { ...prev, balance: result.balance } : null);
|
||
toast({
|
||
title: 'Перевод выполнен',
|
||
status: 'success',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
} catch (error: any) {
|
||
toast({
|
||
title: 'Ошибка перевода',
|
||
description: error.response?.data?.error || 'Произошла ошибка',
|
||
status: 'error',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
}
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<Center h="100vh">
|
||
<Spinner size="xl" />
|
||
</Center>
|
||
);
|
||
}
|
||
|
||
if (!user) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<Container maxW="container.xl" py={8}>
|
||
<Tabs isFitted variant="enclosed">
|
||
<TabList mb="1em">
|
||
<Tab>Профиль</Tab>
|
||
<Tab>Магазин</Tab>
|
||
<Tab>Перевод</Tab>
|
||
</TabList>
|
||
|
||
<TabPanels>
|
||
<TabPanel>
|
||
<UserProfile
|
||
username={user.username}
|
||
level={user.level}
|
||
experience={user.experience}
|
||
balance={user.balance}
|
||
achievements={user.achievements}
|
||
/>
|
||
</TabPanel>
|
||
|
||
<TabPanel>
|
||
<Shop
|
||
items={shopItems}
|
||
userBalance={user.balance}
|
||
onPurchase={handlePurchase}
|
||
/>
|
||
</TabPanel>
|
||
|
||
<TabPanel>
|
||
<TransferBalance
|
||
userBalance={user.balance}
|
||
onTransfer={handleTransfer}
|
||
/>
|
||
</TabPanel>
|
||
</TabPanels>
|
||
</Tabs>
|
||
</Container>
|
||
);
|
||
}
|