123 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|