mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
Migrate to Telegram Navigator integration
Replace manual BackButton management with official @telegram-apps/react-router-integration to fix navigation issues (back button cycling and double-click). Changes: - Migrate from @tma.js/sdk-react to @telegram-apps/sdk-react - Downgrade react-router-dom to v6 for package compatibility - Add AppWithNavigator component with Telegram navigator integration - Use browser history mode (hashMode: null) to prevent hash URLs - Add multi-level platform detection (user data, platform, version) - Add try-catch error handling for navigator attach/detach - Simplify useBackButton hook to no-op (navigator handles automatically) - Update vite config with new Telegram packages The navigator automatically manages BackButton based on router history, falling back to BrowserRouter in regular browser environment.
This commit is contained in:
102
src/AppWithNavigator.tsx
Normal file
102
src/AppWithNavigator.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { BrowserRouter, Router } from 'react-router-dom';
|
||||
import { useIntegration } from '@telegram-apps/react-router-integration';
|
||||
import { initNavigator } from '@telegram-apps/sdk';
|
||||
import App from './App';
|
||||
import { PlatformProvider } from './platform/PlatformProvider';
|
||||
import { ThemeColorsProvider } from './providers/ThemeColorsProvider';
|
||||
import { WebSocketProvider } from './providers/WebSocketProvider';
|
||||
import { ToastProvider } from './components/Toast';
|
||||
import { TooltipProvider } from './components/primitives/Tooltip';
|
||||
|
||||
/**
|
||||
* Check if running inside Telegram Mini App
|
||||
* Uses multiple checks to reliably detect Telegram environment
|
||||
*/
|
||||
function isTelegramMiniApp(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
const webApp = window.Telegram?.WebApp;
|
||||
if (!webApp) return false;
|
||||
|
||||
// Check 1: initDataUnsafe should have user data in real Telegram
|
||||
const hasUserData = webApp.initDataUnsafe?.user?.id !== undefined;
|
||||
|
||||
// Check 2: Platform should not be 'unknown' (which is default in browser)
|
||||
const validPlatform = webApp.platform !== 'unknown' && webApp.platform !== '';
|
||||
|
||||
// Check 3: Version should be present (SDK loads in Telegram only)
|
||||
const hasVersion = webApp.version !== undefined && webApp.version !== '';
|
||||
|
||||
return hasUserData || (validPlatform && hasVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component wrapper for Telegram navigator setup.
|
||||
* Integrates Telegram Mini Apps navigator with React Router to provide
|
||||
* automatic BackButton management based on navigation history.
|
||||
* Falls back to BrowserRouter when not in Telegram Mini App.
|
||||
*/
|
||||
/**
|
||||
* Navigator-based router for Telegram Mini App
|
||||
*/
|
||||
function TelegramRouter({ children }: { children: React.ReactNode }) {
|
||||
const navigator = useMemo(() => initNavigator('app-navigation-state', { hashMode: null }), []);
|
||||
const [location, reactNavigator] = useIntegration(navigator);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
navigator.attach();
|
||||
return () => {
|
||||
try {
|
||||
navigator.detach();
|
||||
} catch (err) {
|
||||
console.warn('Failed to detach navigator:', err);
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('Failed to attach navigator:', err);
|
||||
}
|
||||
}, [navigator]);
|
||||
|
||||
return (
|
||||
<Router location={location} navigator={reactNavigator}>
|
||||
{children}
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppWithNavigator() {
|
||||
const isTelegram = useMemo(() => {
|
||||
const result = isTelegramMiniApp();
|
||||
console.log('[AppWithNavigator] Platform detection:', {
|
||||
isTelegram: result,
|
||||
platform: window.Telegram?.WebApp?.platform,
|
||||
hasUser: window.Telegram?.WebApp?.initDataUnsafe?.user?.id !== undefined,
|
||||
version: window.Telegram?.WebApp?.version,
|
||||
});
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
// Common app content
|
||||
const appContent = (
|
||||
<PlatformProvider>
|
||||
<ThemeColorsProvider>
|
||||
<TooltipProvider>
|
||||
<ToastProvider>
|
||||
<WebSocketProvider>
|
||||
<App />
|
||||
</WebSocketProvider>
|
||||
</ToastProvider>
|
||||
</TooltipProvider>
|
||||
</ThemeColorsProvider>
|
||||
</PlatformProvider>
|
||||
);
|
||||
|
||||
// Use Telegram navigator in Mini App, BrowserRouter elsewhere
|
||||
if (isTelegram) {
|
||||
return <TelegramRouter>{appContent}</TelegramRouter>;
|
||||
}
|
||||
|
||||
return <BrowserRouter>{appContent}</BrowserRouter>;
|
||||
}
|
||||
22
src/main.tsx
22
src/main.tsx
@@ -1,13 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import App from './App';
|
||||
import { PlatformProvider } from './platform/PlatformProvider';
|
||||
import { ThemeColorsProvider } from './providers/ThemeColorsProvider';
|
||||
import { WebSocketProvider } from './providers/WebSocketProvider';
|
||||
import { ToastProvider } from './components/Toast';
|
||||
import { TooltipProvider } from './components/primitives/Tooltip';
|
||||
import { AppWithNavigator } from './AppWithNavigator';
|
||||
import { initLogoPreload } from './api/branding';
|
||||
import { initTelegramSDK } from './hooks/useTelegramSDK';
|
||||
import './i18n';
|
||||
@@ -31,19 +25,7 @@ const queryClient = new QueryClient({
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<PlatformProvider>
|
||||
<ThemeColorsProvider>
|
||||
<TooltipProvider>
|
||||
<ToastProvider>
|
||||
<WebSocketProvider>
|
||||
<App />
|
||||
</WebSocketProvider>
|
||||
</ToastProvider>
|
||||
</TooltipProvider>
|
||||
</ThemeColorsProvider>
|
||||
</PlatformProvider>
|
||||
</BrowserRouter>
|
||||
<AppWithNavigator />
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { usePlatform } from '@/platform/hooks/usePlatform';
|
||||
|
||||
interface UseBackButtonOptions {
|
||||
/**
|
||||
* Whether the back button should be visible
|
||||
@@ -11,65 +8,46 @@ interface UseBackButtonOptions {
|
||||
|
||||
/**
|
||||
* Hook to manage the Telegram BackButton
|
||||
* Automatically shows/hides based on component lifecycle
|
||||
*
|
||||
* @param onBack - Callback when back button is pressed
|
||||
* @param options - Configuration options
|
||||
* NOTE: This hook is now a no-op for backward compatibility.
|
||||
* The Telegram Navigator (@telegram-apps/react-router-integration) handles
|
||||
* BackButton visibility and navigation automatically based on router history.
|
||||
*
|
||||
* The navigator:
|
||||
* - Shows BackButton when there's navigation history
|
||||
* - Hides BackButton on root page
|
||||
* - Handles back button clicks automatically
|
||||
*
|
||||
* @param onBack - (Ignored) Callback when back button is pressed
|
||||
* @param options - (Ignored) Configuration options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function MyPage() {
|
||||
* const navigate = useNavigate();
|
||||
* useBackButton(() => navigate(-1));
|
||||
* useBackButton(() => navigate('/admin')); // No-op: navigator handles this
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useBackButton(
|
||||
onBack: (() => void) | null | undefined,
|
||||
options: UseBackButtonOptions = {},
|
||||
_onBack: (() => void) | null | undefined,
|
||||
_options: UseBackButtonOptions = {},
|
||||
): void {
|
||||
const { backButton, capabilities } = usePlatform();
|
||||
const { visible = true } = options;
|
||||
|
||||
// Use ref to prevent callback recreation issues
|
||||
const callbackRef = useRef(onBack);
|
||||
callbackRef.current = onBack;
|
||||
|
||||
// Stable callback wrapper
|
||||
const handleBack = useCallback(() => {
|
||||
callbackRef.current?.();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// If no native back button support, do nothing
|
||||
if (!capabilities.hasBackButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If callback is null/undefined or visible is false, hide button
|
||||
if (!onBack || !visible) {
|
||||
backButton.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the back button with our handler
|
||||
backButton.show(handleBack);
|
||||
|
||||
// Cleanup: hide button when component unmounts
|
||||
return () => {
|
||||
backButton.hide();
|
||||
};
|
||||
}, [backButton, capabilities.hasBackButton, handleBack, onBack, visible]);
|
||||
// No-op: Navigator handles BackButton automatically
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to conditionally show back button based on navigation depth
|
||||
* Useful for showing back button only when there's history to go back to
|
||||
*
|
||||
* @param canGoBack - Whether navigation back is possible
|
||||
* @param onBack - Callback when back button is pressed
|
||||
* NOTE: This hook is now a no-op for backward compatibility.
|
||||
* The navigator handles conditional visibility automatically.
|
||||
*
|
||||
* @param canGoBack - (Ignored) Whether navigation back is possible
|
||||
* @param onBack - (Ignored) Callback when back button is pressed
|
||||
*/
|
||||
export function useConditionalBackButton(canGoBack: boolean, onBack: () => void): void {
|
||||
useBackButton(canGoBack ? onBack : null);
|
||||
export function useConditionalBackButton(_canGoBack: boolean, _onBack: () => void): void {
|
||||
// No-op: Navigator handles BackButton automatically
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user