Files
app_umbrix/hooks/useTelegramWebApp.ts
Umbrix Dev 33b20b979f 🔒 Аудит: безопасность, TypeScript, UI, BottomNav
Безопасность:
- proxy: белый список путей (только /sub/*), POST заблокирован
- console.log заменён на logger (утечки URL/данных)
- OnboardingFlow: убраны --tg-theme-* (не существуют в проекте)

TypeScript (0 ошибок):
- tsconfig target es5→es2017 (regex /u flag fix)
- layout.tsx: viewport перенесён в metadata (Next.js 13.5)
- telegram-webhook: fix text possibly undefined
- hooks/useTelegramWebApp: fix Object possibly undefined
- types/telegram: убрана дублирующая Window декларация

UI:
- BottomNav: новый компонент (Назад/Главная/Помощь)
- safe-area-bottom CSS класс добавлен в globals.css
- dashboard: spacer h-20, toast поднят над BottomNav
- OnboardingFlow: цены 149/249/350₽ (были 200/350/500₽)

Очистка:
- page_NEW.tsx удалён локально (не был в git)
2026-02-08 18:59:02 +03:00

274 lines
7.7 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.
// hooks/useTelegramWebApp.ts
// Hook для интеграции с Telegram WebApp API
import { useEffect, useState } from 'react';
declare global {
interface Window {
Telegram?: {
WebApp: {
ready: () => void;
expand: () => void;
close: () => void;
isExpanded: boolean;
viewportHeight: number;
viewportStableHeight: number;
headerColor: string;
backgroundColor: string;
isClosingConfirmationEnabled: boolean;
platform: string;
version: string;
MainButton: {
text: string;
color: string;
textColor: string;
isVisible: boolean;
isActive: boolean;
isProgressVisible: boolean;
setText: (text: string) => void;
onClick: (callback: () => void) => void;
offClick: (callback: () => void) => void;
show: () => void;
hide: () => void;
enable: () => void;
disable: () => void;
showProgress: (leaveActive?: boolean) => void;
hideProgress: () => void;
setParams: (params: { text?: string; color?: string; text_color?: string; is_active?: boolean; is_visible?: boolean }) => void;
};
BackButton: {
isVisible: boolean;
onClick: (callback: () => void) => void;
offClick: (callback: () => void) => void;
show: () => void;
hide: () => void;
};
HapticFeedback: {
impactOccurred: (style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft') => void;
notificationOccurred: (type: 'error' | 'success' | 'warning') => void;
selectionChanged: () => void;
};
setHeaderColor: (color: string) => void;
setBackgroundColor: (color: string) => void;
enableClosingConfirmation: () => void;
disableClosingConfirmation: () => void;
showPopup: (params: {
title?: string;
message: string;
buttons: Array<{ id?: string; type: string; text?: string }>;
}, callback?: (buttonId: string) => void) => void;
showAlert: (message: string, callback?: () => void) => void;
showConfirm: (message: string, callback?: (confirmed: boolean) => void) => void;
openLink: (url: string, options?: { try_instant_view?: boolean }) => void;
openTelegramLink: (url: string) => void;
openInvoice: (url: string, callback?: (status: string) => void) => void;
shareToStory: (media_url: string, params?: {
text?: string;
widget_link?: {
url: string;
name?: string;
};
}) => void;
switchInlineQuery: (query: string, choose_chat_types?: string[]) => void;
sendData: (data: string) => void;
initData: string;
initDataUnsafe: {
query_id?: string;
user?: {
id: number;
first_name: string;
last_name?: string;
username?: string;
language_code?: string;
is_premium?: boolean;
photo_url?: string;
};
receiver?: {
id: number;
first_name: string;
last_name?: string;
username?: string;
language_code?: string;
is_premium?: boolean;
};
chat?: {
id: number;
type: string;
title: string;
username?: string;
photo_url?: string;
};
chat_type?: string;
chat_instance?: string;
start_param?: string;
can_send_after?: number;
auth_date: number;
hash: string;
};
themeParams: {
bg_color?: string;
text_color?: string;
hint_color?: string;
link_color?: string;
button_color?: string;
button_text_color?: string;
secondary_bg_color?: string;
header_bg_color?: string;
accent_text_color?: string;
section_bg_color?: string;
section_header_text_color?: string;
subtitle_text_color?: string;
destructive_text_color?: string;
};
colorScheme: 'light' | 'dark';
isVersionAtLeast: (version: string) => boolean;
onEvent: (eventType: string, eventHandler: () => void) => void;
offEvent: (eventType: string, eventHandler: () => void) => void;
};
};
}
}
export function useTelegramWebApp() {
const [webApp, setWebApp] = useState<any>(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
// Проверяем доступность Telegram WebApp только на клиенте
if (typeof window === 'undefined') return;
const tg = window.Telegram?.WebApp;
if (!tg) {
console.warn('[TelegramWebApp] Not running in Telegram');
setIsReady(true); // Всё равно помечаем как ready для dev
return;
}
console.log('[TelegramWebApp] Initializing...', {
platform: tg.platform,
version: tg.version,
colorScheme: tg.colorScheme,
user: tg.initDataUnsafe.user,
});
// Инициализация Telegram WebApp
tg.ready();
tg.expand();
// Настройка темы
tg.setHeaderColor('bg_color');
tg.setBackgroundColor('#18222d');
// Отключить подтверждение закрытия (можно включить при необходимости)
tg.disableClosingConfirmation();
setWebApp(tg);
setIsReady(true);
// Cleanup
return () => {
tg.MainButton.hide();
tg.BackButton.hide();
};
}, []);
return {
webApp,
isReady,
isTelegram: !!webApp,
user: webApp?.initDataUnsafe.user,
platform: webApp?.platform,
colorScheme: webApp?.colorScheme,
themeParams: webApp?.themeParams,
};
}
// Helper функции для удобства
export function useTelegramMainButton(
text: string,
onClick: () => void,
options?: {
color?: string;
textColor?: string;
enabled?: boolean;
}
) {
const { webApp } = useTelegramWebApp();
useEffect(() => {
if (!webApp) return;
webApp.MainButton.setText(text);
webApp.MainButton.onClick(onClick);
if (options?.color) {
webApp.MainButton.color = options.color;
}
if (options?.textColor) {
webApp.MainButton.textColor = options.textColor;
}
if (options?.enabled !== undefined) {
if (options.enabled) {
webApp.MainButton.enable();
} else {
webApp.MainButton.disable();
}
}
webApp.MainButton.show();
return () => {
webApp.MainButton.offClick(onClick);
webApp.MainButton.hide();
};
}, [webApp, text, onClick, options]);
}
export function useTelegramBackButton(onClick: () => void) {
const { webApp } = useTelegramWebApp();
useEffect(() => {
if (!webApp) return;
webApp.BackButton.onClick(onClick);
webApp.BackButton.show();
return () => {
webApp.BackButton.offClick(onClick);
webApp.BackButton.hide();
};
}, [webApp, onClick]);
}
export function useTelegramHaptic() {
const { webApp } = useTelegramWebApp();
return {
impact: (style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft' = 'medium') => {
webApp?.HapticFeedback.impactOccurred(style);
},
notification: (type: 'error' | 'success' | 'warning') => {
webApp?.HapticFeedback.notificationOccurred(type);
},
selection: () => {
webApp?.HapticFeedback.selectionChanged();
},
};
}