CampFireID/app/components/MainApp.tsx
degradin 22cc9ef144 SSR Fixes
Добавили динамический импорт Telegram Web App SDK с помощью import(), чтобы он загружался только на клиенте
Добавили состояние загрузки и компонент Spinner для лучшего UX
Исправили типы в компонентах:
Используем IShopItem вместо собственного интерфейса ShopItem
Создали тип SafeUser, который исключает свойства mongoose Document из типа пользователя
Добавили безопасную проверку на наличие пользователя в данных Telegram WebApp
2025-03-16 11:37:54 +03:00

150 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}