import React, { useState } from 'react'; import type { Room } from 'matrix-js-sdk'; import { Icon, IconButton, Icons, PopOut, RectCords } from 'folds'; import FocusTrap from 'focus-trap-react'; import { useTranslation } from 'react-i18next'; import type { BotPreset } from './catalog'; import { BotShellMenu } from './BotShellMenu'; import { BackRouteHandler } from '../../components/BackRouteHandler'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { stopPropagation } from '../../utils/keyboard'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { mxcUrlToHttp } from '../../utils/matrix'; import * as css from './BotShell.css'; type BotShellHeroProps = { preset: BotPreset; room: Room; }; // Initial for the avatar block. Prefer the human name's first character // (matches mockup BOT.name = «build-bot» → «B»); fall back to the mxid // localpart, then to «?» when neither is usable. The previous «T» literal // hardcoded the Telegram preset and fails the moment a second bot ships. const heroInitial = (preset: BotPreset): string => { const fromName = preset.name.trim().charAt(0); if (fromName) return fromName.toUpperCase(); const local = preset.mxid.split(':')[0].replace('@', ''); return local.charAt(0).toUpperCase() || '?'; }; export function BotShellHero({ preset, room }: BotShellHeroProps) { const { t } = useTranslation(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const screenSize = useScreenSizeContext(); const isMobile = screenSize === ScreenSize.Mobile; const [menuAnchor, setMenuAnchor] = useState(); // Standard Matrix avatar resolution. The bot user's profile carries the // canonical `avatar_url` (set server-side by the bridge or homeserver // admin); every Matrix client reads the same source. We pull it via the // bot's RoomMember in this DM rather than `mx.getUser()` because the // member event in this room is guaranteed to be loaded by the time we // render — we wouldn't be on `/bots/` otherwise — and avoids any // edge case where `mx.getUser()` returns a stale or missing entry. const avatarMxc = room.getMember(preset.mxid)?.getMxcAvatarUrl(); const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; const handleOpenMenu: React.MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; // Operator override from /config.json wins; otherwise fall back to the // localized `Bots.description.` key. Empty string suppresses the // line entirely so a missing translation doesn't ship the key. const description = t(`Bots.description.${preset.id}`, { defaultValue: preset.description ?? '', }); const initial = heroInitial(preset); return (
{/* Mobile back chevron — bypasses Cinny's standard RoomViewHeader * (which BotShell deliberately doesn't mount), so the user retains * the «walk up the route tree» affordance the rest of the client * provides on phone. Desktop relies on the sidebar to navigate. */} {isMobile && ( {(onBack) => ( )} )}
{preset.name} {preset.mxid}
{description ?

{description}

: null}
setMenuAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> } />
); }