Безопасность: - 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)
274 lines
7.7 KiB
TypeScript
274 lines
7.7 KiB
TypeScript
// 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();
|
||
},
|
||
};
|
||
}
|