diff --git a/app/hooks/useTelegramWebApp.ts b/app/hooks/useTelegramWebApp.ts index 89e1dc1..9e281ed 100644 --- a/app/hooks/useTelegramWebApp.ts +++ b/app/hooks/useTelegramWebApp.ts @@ -1,8 +1,8 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { isDemoMode, getDemoWebApp } from '../utils/demo'; +import { useEffect, useState, useCallback } from 'react'; import type { WebApp } from '@twa-dev/types'; +import { isDemoMode, getDemoWebApp } from '../utils/demo'; declare global { interface Window { @@ -12,84 +12,68 @@ declare global { } } -type SafeWebApp = Partial>; +export interface UseTelegramWebAppResult { + webApp: WebApp | null; + error: Error | null; + isLoading: boolean; + isInitialized: boolean; +} -export function useTelegramWebApp() { - const [webApp, setWebApp] = useState(null); - const [error, setError] = useState(null); +export function useTelegramWebApp(): UseTelegramWebAppResult { + const [webApp, setWebApp] = useState(null); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); const [isInitialized, setIsInitialized] = useState(false); - useEffect(() => { - let isMounted = true; + const initWebApp = useCallback(async () => { + try { + setIsLoading(true); + setError(null); - const initWebApp = async () => { - try { - if (isDemoMode()) { - if (isMounted) { - const demoWebApp = getDemoWebApp(); - setWebApp(demoWebApp); - setIsInitialized(true); - } - return; - } + if (isDemoMode()) { + const demoWebApp = getDemoWebApp(); + setWebApp(demoWebApp); + setIsInitialized(true); + return; + } - if (typeof window !== 'undefined') { - // Проверяем наличие Telegram WebApp в глобальном объекте - if (window.Telegram?.WebApp) { - if (isMounted) { - setWebApp(window.Telegram.WebApp as SafeWebApp); - setIsInitialized(true); - } - return; - } - - try { - // Если WebApp не найден в глобальном объекте, пробуем импортировать SDK - const WebAppModule = await import('@twa-dev/sdk'); - if (isMounted && WebAppModule.default) { - setWebApp(WebAppModule.default as SafeWebApp); - setIsInitialized(true); - } else { - throw new Error('WebApp не найден'); - } - } catch (importError) { - console.error('SDK import error:', importError); - throw new Error('Ошибка импорта Telegram Web App SDK'); - } - } - } catch (err) { - console.error('WebApp initialization error:', err); - if (isMounted) { - setError('Ошибка инициализации Telegram Web App'); + if (typeof window !== 'undefined') { + if (window.Telegram?.WebApp) { + setWebApp(window.Telegram.WebApp); + setIsInitialized(true); + } else { + throw new Error('Telegram WebApp не найден'); } } + } catch (err) { + console.error('Ошибка инициализации WebApp:', err); + setError(err instanceof Error ? err : new Error('Неизвестная ошибка')); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + initWebApp(); + }, [initWebApp]); + + useEffect(() => { + if (!webApp) return; + + const handleThemeChange = () => { + // Обновляем состояние при изменении темы + setWebApp(prevWebApp => { + if (!prevWebApp) return null; + return { ...prevWebApp }; + }); }; - if (!isInitialized) { - initWebApp(); - } + webApp.onEvent('themeChanged', handleThemeChange); return () => { - isMounted = false; + webApp.offEvent('themeChanged', handleThemeChange); }; - }, [isInitialized]); + }, [webApp]); - return { webApp, error, isInitialized }; + return { webApp, error, isLoading, isInitialized }; } \ No newline at end of file diff --git a/app/utils/demo.ts b/app/utils/demo.ts index eb8dd06..03dc484 100644 --- a/app/utils/demo.ts +++ b/app/utils/demo.ts @@ -1,6 +1,6 @@ 'use client'; -import type { WebApp, WebAppUser, WebAppInitData, Platforms } from '@twa-dev/types'; +import type { WebApp, WebAppUser, WebAppInitData, Platforms, HapticFeedback } from '@twa-dev/types'; // Демо данные для тестирования без Telegram const demoUser: WebAppUser = { @@ -28,26 +28,14 @@ export const isDemoMode = () => { // Создаем заглушки для методов WebApp const createNoopFunction = () => () => {}; -type SafeWebApp = Pick; +// Создаем заглушку для HapticFeedback +const demoHapticFeedback: HapticFeedback = { + impactOccurred: (_style: "light" | "medium" | "heavy" | "rigid" | "soft") => demoHapticFeedback, + notificationOccurred: (_type: "error" | "success" | "warning") => demoHapticFeedback, + selectionChanged: () => demoHapticFeedback +}; -export const getDemoWebApp = (): SafeWebApp => ({ +export const getDemoWebApp = (): WebApp => ({ initData: JSON.stringify(demoInitData), initDataUnsafe: demoInitData, platform: 'WEBVIEW' as Platforms, @@ -92,6 +80,36 @@ export const getDemoWebApp = (): SafeWebApp => ({ hideProgress: createNoopFunction(), setParams: createNoopFunction() }, + HapticFeedback: demoHapticFeedback, + CloudStorage: { + setItem: createNoopFunction(), + getItem: createNoopFunction(), + getItems: createNoopFunction(), + removeItem: createNoopFunction(), + removeItems: createNoopFunction(), + getKeys: createNoopFunction() + }, + version: '7.0', + isVersionAtLeast: (version: string) => true, + setHeaderColor: createNoopFunction(), + setBackgroundColor: createNoopFunction(), + enableClosingConfirmation: createNoopFunction(), + disableClosingConfirmation: createNoopFunction(), + onEvent: createNoopFunction(), + offEvent: createNoopFunction(), + sendData: createNoopFunction(), + switchInlineQuery: createNoopFunction(), + openLink: createNoopFunction(), + openTelegramLink: createNoopFunction(), + openInvoice: createNoopFunction(), + showPopup: createNoopFunction(), + showAlert: createNoopFunction(), + showConfirm: createNoopFunction(), + showScanQrPopup: createNoopFunction(), + closeScanQrPopup: createNoopFunction(), + readTextFromClipboard: createNoopFunction(), + requestWriteAccess: createNoopFunction(), + requestContact: createNoopFunction(), ready: createNoopFunction(), expand: createNoopFunction(), close: createNoopFunction(), diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..69c0934 --- /dev/null +++ b/next.config.js @@ -0,0 +1,20 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + output: 'standalone', + experimental: { + serverActions: { + allowedOrigins: ['telegram.org', 't.me'], + }, + }, + webpack: (config) => { + config.externals.push({ + 'utf-8-validate': 'commonjs utf-8-validate', + 'bufferutil': 'commonjs bufferutil', + }); + return config; + }, +} + +module.exports = nextConfig \ No newline at end of file diff --git a/package.json b/package.json index 46f4370..5757c2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "campfire-id", "version": "1.0.0", + "private": true, "description": "Telegram Mini App for user achievements and virtual economy", "scripts": { "dev": "next dev", @@ -9,30 +10,34 @@ "lint": "next lint" }, "dependencies": { - "@chakra-ui/react": "^2.8.1", - "@emotion/react": "^11.11.1", + "@chakra-ui/next-js": "^2.2.0", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", - "@twa-dev/sdk": "^6.9.0", + "@twa-dev/sdk": "^7.0.0", + "@twa-dev/types": "^7.0.0", "@types/cors": "^2.8.17", - "axios": "^1.5.1", + "axios": "^1.6.7", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", - "framer-motion": "^10.16.4", + "framer-motion": "^11.0.8", "jsonwebtoken": "^9.0.2", - "mongoose": "^7.5.3", - "next": "^13.5.4", + "mongoose": "^8.2.1", + "next": "15.2.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zod": "^3.22.4" }, "devDependencies": { "@types/express": "^4.17.18", - "@types/node": "^20.8.3", - "@types/react": "^18.2.25", - "eslint": "^8.51.0", - "eslint-config-next": "13.5.4", + "@types/node": "^20.11.24", + "@types/react": "^18.2.61", + "@types/react-dom": "^18.2.19", + "eslint": "^8.57.0", + "eslint-config-next": "15.2.2", "prettier": "^3.0.3", - "typescript": "^5.2.2" + "typescript": "^5.3.3" } }