Files
IdP/apps/frontend/components/chat/bot-inline-keyboard.tsx
2026-06-25 23:48:57 +03:00

123 lines
3.5 KiB
TypeScript

'use client';
import { Loader2 } from 'lucide-react';
import { InlineKeyboardButton, parseInlineKeyboardMarkup } from '@/lib/bot-reply-markup';
import { cn } from '@/lib/utils';
export function buildInlineButtonKey(messageId: number, rowIndex: number, buttonIndex: number) {
return `${messageId}:${rowIndex}:${buttonIndex}`;
}
interface BotInlineKeyboardProps {
markup: unknown;
messageId: number;
loadingButtonKey?: string | null;
disabled?: boolean;
onCallbackClick?: (callbackData: string, buttonKey: string) => void | Promise<void>;
onOpenMiniApp?: (url: string) => void;
onOpenUrl?: (url: string) => void;
}
export function BotInlineKeyboard({
markup,
messageId,
loadingButtonKey = null,
disabled = false,
onCallbackClick,
onOpenMiniApp,
onOpenUrl
}: BotInlineKeyboardProps) {
const rows = parseInlineKeyboardMarkup(markup);
if (!rows.length) return null;
return (
<div className="mt-2 space-y-1.5">
{rows.map((row, rowIndex) => (
<div
key={`row-${rowIndex}`}
className={cn('flex gap-1.5', row.length === 1 ? 'flex-col' : 'flex-row flex-wrap')}
>
{row.map((button, buttonIndex) => (
<InlineKeyboardButtonView
key={`btn-${rowIndex}-${buttonIndex}`}
button={button}
buttonKey={buildInlineButtonKey(messageId, rowIndex, buttonIndex)}
fullWidth={row.length === 1}
loading={loadingButtonKey === buildInlineButtonKey(messageId, rowIndex, buttonIndex)}
disabled={disabled || (loadingButtonKey !== null && loadingButtonKey !== buildInlineButtonKey(messageId, rowIndex, buttonIndex))}
onCallbackClick={onCallbackClick}
onOpenMiniApp={onOpenMiniApp}
onOpenUrl={onOpenUrl}
/>
))}
</div>
))}
</div>
);
}
function InlineKeyboardButtonView({
button,
buttonKey,
fullWidth,
loading,
disabled,
onCallbackClick,
onOpenMiniApp,
onOpenUrl
}: {
button: InlineKeyboardButton;
buttonKey: string;
fullWidth: boolean;
loading: boolean;
disabled: boolean;
onCallbackClick?: (callbackData: string, buttonKey: string) => void | Promise<void>;
onOpenMiniApp?: (url: string) => void;
onOpenUrl?: (url: string) => void;
}) {
const webAppUrl = button.web_app?.url;
const callbackData = button.callback_data ?? button.callbackData;
async function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
event.preventDefault();
if (disabled || loading) return;
if (webAppUrl) {
if (onOpenMiniApp) {
onOpenMiniApp(webAppUrl);
} else {
window.open(webAppUrl, '_blank', 'noopener,noreferrer');
}
return;
}
if (button.url) {
if (onOpenUrl) {
onOpenUrl(button.url);
} else {
window.open(button.url, '_blank', 'noopener,noreferrer');
}
return;
}
if (callbackData && onCallbackClick) {
await onCallbackClick(callbackData, buttonKey);
}
}
return (
<button
type="button"
disabled={disabled || loading}
className={cn(
'inline-flex min-h-9 items-center justify-center rounded-xl border border-[#dce3ec] bg-[#f4f7fb] px-3 py-2 text-xs font-medium text-[#1f2430] transition',
'hover:bg-[#e8eef6] disabled:cursor-not-allowed disabled:opacity-60',
fullWidth ? 'w-full' : 'min-w-0 flex-1'
)}
onClick={(event) => void handleClick(event)}
>
{loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : button.text}
</button>
);
}