ChakraUI Added

This commit is contained in:
degradin 2025-03-16 13:25:30 +03:00
parent 27280c831a
commit 015d3eb5d1
5 changed files with 38 additions and 201 deletions

View File

@ -1,96 +1,18 @@
'use client';
import dynamic from 'next/dynamic';
import React, { useState, useEffect } from 'react';
import { Box, VStack, Spinner, Center, useToast } from '@chakra-ui/react';
import { auth, getProfile, getShopItems, purchaseItem, transferBalance } from '../utils/api';
import { IUser } from '../../backend/models/User';
import { IShopItem } from '../../backend/models/ShopItem';
import { useEffect, useState } from 'react';
import { Center, Spinner, Text, VStack } from '@chakra-ui/react';
import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
// Динамический импорт компонентов для клиентской стороны
const UserProfile = dynamic(() => import('./UserProfile'), {
loading: () => <Spinner />,
ssr: false
});
const Shop = dynamic(() => import('./Shop'), {
loading: () => <Spinner />,
ssr: false
});
const TransferBalance = dynamic(() => import('./TransferBalance'), {
loading: () => <Spinner />,
ssr: false
});
type SafeUser = Omit<IUser, keyof Document>;
export default function MainApp() {
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<SafeUser | null>(null);
const [shopItems, setShopItems] = useState<IShopItem[]>([]);
const toast = useToast();
const { webApp, error: webAppError, isInitialized } = useTelegramWebApp();
const [isMounted, setIsMounted] = useState(false);
const { webApp, error, isLoading } = useTelegramWebApp();
useEffect(() => {
if (webApp && isInitialized) {
initApp();
}
}, [webApp, isInitialized]);
setIsMounted(true);
}, []);
const initApp = async () => {
try {
setIsLoading(true);
if (!webApp) {
throw new Error('Telegram Web App не инициализирован');
}
// Авторизация пользователя
const telegramUser = webApp.initDataUnsafe?.user;
if (!telegramUser) {
throw new Error('Пользователь не найден');
}
const authResponse = await auth(telegramUser.id.toString(), telegramUser.username || 'anonymous');
// Получение данных пользователя и магазина
const [profileData, shopData] = await Promise.all([
getProfile(),
getShopItems()
]);
setUser(profileData as SafeUser);
setShopItems(shopData);
// Сообщаем Telegram, что приложение готово
webApp.ready?.();
} catch (error: any) {
console.error('Initialization error:', error);
toast({
title: 'Ошибка инициализации',
description: error.message || 'Произошла ошибка при загрузке приложения',
status: 'error',
duration: 5000,
isClosable: true,
});
} finally {
setIsLoading(false);
}
};
if (webAppError) {
return (
<Center h="100vh">
<Box p={4} textAlign="center" color="red.500">
{webAppError}
</Box>
</Center>
);
}
if (isLoading || !webApp || !isInitialized) {
if (!isMounted || isLoading) {
return (
<Center h="100vh">
<Spinner size="xl" />
@ -98,74 +20,28 @@ export default function MainApp() {
);
}
if (!user) {
if (error) {
return (
<Center h="100vh">
<Box p={4} textAlign="center">
Пожалуйста, авторизуйтесь через Telegram или используйте демо-режим
</Box>
<VStack>
<Text color="red.500">Ошибка инициализации</Text>
<Text>{error.message}</Text>
</VStack>
</Center>
);
}
if (!webApp) {
return (
<Center h="100vh">
<Text>WebApp не инициализирован</Text>
</Center>
);
}
return (
<VStack spacing={6} p={4}>
<UserProfile
username={user.username}
level={user.level}
experience={user.experience}
balance={user.balance}
achievements={user.achievements}
/>
<Shop
items={shopItems}
userBalance={user.balance}
onPurchase={async (item: IShopItem) => {
try {
const response = await purchaseItem(item._id.toString());
setUser(response.user as SafeUser);
toast({
title: 'Успешная покупка',
description: `Вы приобрели ${item.name}`,
status: 'success',
duration: 3000,
isClosable: true,
});
} catch (error: any) {
toast({
title: 'Ошибка покупки',
description: error.message || 'Произошла ошибка при покупке',
status: 'error',
duration: 3000,
isClosable: true,
});
}
}}
/>
<TransferBalance
userBalance={user.balance}
onTransfer={async (username, amount) => {
try {
const response = await transferBalance(username, amount);
setUser(prev => ({ ...prev!, balance: response.balance } as SafeUser));
toast({
title: 'Успешный перевод',
description: `Вы перевели ${amount} монет пользователю ${username}`,
status: 'success',
duration: 3000,
isClosable: true,
});
} catch (error: any) {
toast({
title: 'Ошибка перевода',
description: error.message || 'Произошла ошибка при переводе',
status: 'error',
duration: 3000,
isClosable: true,
});
}
}}
/>
</VStack>
<Center h="100vh">
<Text>Добро пожаловать, {webApp.initDataUnsafe.user?.first_name}!</Text>
</Center>
);
}

View File

@ -1,31 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import { Center, Spinner } from '@chakra-ui/react';
const MainApp = dynamic(() => import('./MainApp'), {
loading: () => (
<Center h="100vh">
<Spinner size="xl" />
</Center>
),
});
export default function MainAppWrapper() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted) {
return (
<Center h="100vh">
<Spinner size="xl" />
</Center>
);
}
return <MainApp />;
}

View File

@ -1,12 +1,9 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { Providers } from './providers';
const inter = Inter({ subsets: ['latin', 'cyrillic'] });
export const metadata: Metadata = {
title: 'Campfire ID',
description: 'Ваш цифровой профиль в Telegram',
title: 'CampFire ID',
description: 'Telegram Mini App for user achievements and virtual economy',
};
export default function RootLayout({
@ -16,7 +13,7 @@ export default function RootLayout({
}) {
return (
<html lang="ru">
<body className={inter.className}>
<body>
<Providers>
{children}
</Providers>

View File

@ -1,17 +1,9 @@
import { Suspense } from 'react';
import { Center, Spinner } from '@chakra-ui/react';
import MainAppWrapper from './components/MainAppWrapper';
import dynamic from 'next/dynamic';
const MainAppWithNoSSR = dynamic(() => import('./components/MainApp'), {
ssr: false,
});
export default function Home() {
return (
<Suspense
fallback={
<Center h="100vh">
<Spinner size="xl" />
</Center>
}
>
<MainAppWrapper />
</Suspense>
);
return <MainAppWithNoSSR />;
}

View File

@ -1,5 +1,6 @@
'use client';
import { CacheProvider } from '@chakra-ui/next-js';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
const theme = extendTheme({
@ -15,8 +16,10 @@ const theme = extendTheme({
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ChakraProvider theme={theme}>
{children}
</ChakraProvider>
<CacheProvider>
<ChakraProvider theme={theme}>
{children}
</ChakraProvider>
</CacheProvider>
);
}