vojo/src/app/features/bots/BotCard.tsx

86 lines
3.1 KiB
TypeScript

import React from 'react';
import { Avatar, AvatarImage, Box, Text, toRem } from 'folds';
import { NavItem, NavItemContent, NavLink } from '../../components/nav';
import { getBotPath } from '../../pages/pathUtils';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useUserProfile } from '../../hooks/useUserProfile';
import { mxcUrlToHttp } from '../../utils/matrix';
import type { BotPreset } from './catalog';
const MONO_FONT = '"JetBrains Mono Variable", ui-monospace, monospace';
const ROW_MIN_HEIGHT = toRem(56);
const AVATAR_BG = '#7ab6d9';
type BotCardProps = {
preset: BotPreset;
selected?: boolean;
};
export function BotCard({ preset, selected }: BotCardProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const initial = preset.name.trim().charAt(0).toUpperCase() || '?';
// Standard Matrix avatar resolution. `useUserProfile` returns the cached
// profile synchronously and subscribes to live `UserEvent.AvatarUrl`
// updates — when the operator sets the bot's `avatar_url` server-side
// (mautrix-telegram bridge config), every BotCard remount picks it up
// without client deploys. The fleet-blue letter square remains as the
// fallback when the bot has no profile avatar yet.
const { avatarUrl: avatarMxc } = useUserProfile(preset.mxid);
const avatarUrl = avatarMxc
? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 56, 56, 'crop') ?? undefined
: undefined;
return (
<NavItem
variant="Background"
radii="400"
aria-selected={selected}
style={{ minHeight: ROW_MIN_HEIGHT }}
>
<NavLink to={getBotPath(preset.id)}>
<NavItemContent>
<Box
as="span"
grow="Yes"
alignItems="Center"
gap="300"
style={{
minHeight: ROW_MIN_HEIGHT,
boxSizing: 'border-box',
padding: `${toRem(6)} 0`,
}}
>
<Avatar size="300" radii="400" style={{ background: AVATAR_BG, color: '#0c0c0e' }}>
{avatarUrl ? (
<AvatarImage src={avatarUrl} alt={preset.name} />
) : (
<Text as="span" size="H6" style={{ color: '#0c0c0e', fontWeight: 700 }}>
{initial}
</Text>
)}
</Avatar>
<Box
as="span"
direction="Column"
grow="Yes"
gap="100"
style={{ minWidth: 0, overflow: 'hidden' }}
>
<Box as="span" alignItems="Center" gap="200" style={{ minWidth: 0 }}>
<Text as="span" size="T300" truncate style={{ fontWeight: 600 }}>
{preset.name}
</Text>
</Box>
<Text as="span" size="T200" truncate style={{ opacity: 0.6, fontFamily: MONO_FONT }}>
{preset.mxid}
</Text>
</Box>
</Box>
</NavItemContent>
</NavLink>
</NavItem>
);
}