110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
'use client';
|
||
|
||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||
import { BotInlineKeyboard } from '@/components/chat/bot-inline-keyboard';
|
||
import { useToast } from '@/components/id/toast-provider';
|
||
import { useRealtime } from '@/components/notifications/realtime-provider';
|
||
import { submitBotCallback } from '@/lib/api';
|
||
|
||
const CALLBACK_ANSWER_TIMEOUT_MS = 30_000;
|
||
|
||
interface BotMessageKeyboardProps {
|
||
botRef: string;
|
||
messageId: number;
|
||
markup: unknown;
|
||
token: string | null;
|
||
botId?: string;
|
||
onOpenMiniApp?: (url: string) => void;
|
||
}
|
||
|
||
export function BotMessageKeyboard({
|
||
botRef,
|
||
messageId,
|
||
markup,
|
||
token,
|
||
botId,
|
||
onOpenMiniApp
|
||
}: BotMessageKeyboardProps) {
|
||
const { showToast } = useToast();
|
||
const { subscribe } = useRealtime();
|
||
const [loadingButtonKey, setLoadingButtonKey] = useState<string | null>(null);
|
||
const pendingQueriesRef = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
||
|
||
const clearPendingQuery = useCallback((callbackQueryId: string) => {
|
||
const timer = pendingQueriesRef.current.get(callbackQueryId);
|
||
if (timer) {
|
||
clearTimeout(timer);
|
||
pendingQueriesRef.current.delete(callbackQueryId);
|
||
}
|
||
setLoadingButtonKey(null);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
return () => {
|
||
pendingQueriesRef.current.forEach((timer) => clearTimeout(timer));
|
||
pendingQueriesRef.current.clear();
|
||
};
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
return subscribe((event) => {
|
||
if (event.type !== 'bot_callback_answer') return;
|
||
|
||
const payload = event.payload ?? {};
|
||
const callbackQueryId = typeof payload.callbackQueryId === 'string' ? payload.callbackQueryId : null;
|
||
if (!callbackQueryId || !pendingQueriesRef.current.has(callbackQueryId)) return;
|
||
|
||
const eventBotId = typeof payload.botId === 'string' ? payload.botId : null;
|
||
if (botId && eventBotId && eventBotId !== botId) return;
|
||
|
||
clearPendingQuery(callbackQueryId);
|
||
|
||
const text = typeof payload.text === 'string' ? payload.text.trim() : '';
|
||
const showAlert = Boolean(payload.showAlert);
|
||
const url = typeof payload.url === 'string' ? payload.url.trim() : '';
|
||
|
||
if (url && onOpenMiniApp) {
|
||
onOpenMiniApp(url);
|
||
}
|
||
|
||
if (text) {
|
||
if (showAlert) {
|
||
window.alert(text);
|
||
} else {
|
||
showToast(text);
|
||
}
|
||
}
|
||
});
|
||
}, [botId, clearPendingQuery, onOpenMiniApp, showToast, subscribe]);
|
||
|
||
async function handleCallbackClick(callbackData: string, buttonKey: string) {
|
||
if (!token) return;
|
||
|
||
setLoadingButtonKey(buttonKey);
|
||
try {
|
||
const response = await submitBotCallback(botRef, messageId, callbackData, token);
|
||
const callbackQueryId = response.callbackQueryId;
|
||
if (!callbackQueryId) {
|
||
setLoadingButtonKey(null);
|
||
return;
|
||
}
|
||
|
||
const timer = setTimeout(() => clearPendingQuery(callbackQueryId), CALLBACK_ANSWER_TIMEOUT_MS);
|
||
pendingQueriesRef.current.set(callbackQueryId, timer);
|
||
} catch (error) {
|
||
setLoadingButtonKey(null);
|
||
showToast(error instanceof Error ? error.message : 'Не удалось отправить действие');
|
||
}
|
||
}
|
||
|
||
return (
|
||
<BotInlineKeyboard
|
||
markup={markup}
|
||
messageId={messageId}
|
||
loadingButtonKey={loadingButtonKey}
|
||
onCallbackClick={handleCallbackClick}
|
||
onOpenMiniApp={onOpenMiniApp}
|
||
/>
|
||
);
|
||
}
|