86 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|