'use client'; import { isSupportAttachmentImage, type Passenger } from '@edr/types'; import { FileText, ImageOff } from 'lucide-react'; import { useAttachmentObjectUrl } from './useAttachmentObjectUrl'; type AttachmentDto = Passenger.PassengerSupportAttachmentDto; /** Human-readable size — kept coarse; nobody needs bytes in a chat bubble. */ export function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } /** * One image attachment. Its own component because the bytes are fetched through * the authenticated client (see {@link useAttachmentObjectUrl}) and a hook can't * be called from inside a `.map()`. */ function ImageAttachment({ a, onView, }: { a: AttachmentDto; onView: (a: AttachmentDto, src: string) => void; }) { const { src, failed } = useAttachmentObjectUrl(a.url); if (failed) { return ( Couldn't load {a.name} ); } if (!src) { return (
); } return ( ); } /** * Attachments inside a message bubble: images as thumbnails, everything else as * a labelled file row. Non-images are not fetched until opened — pulling every * document in a thread just to draw a filename would be wasteful. */ export function MessageAttachments({ attachments, mine, onView, onOpenFile, }: { attachments: AttachmentDto[]; mine: boolean; onView: (a: AttachmentDto, src: string) => void; onOpenFile: (a: AttachmentDto) => void; }) { if (attachments.length === 0) return null; return (
{attachments.map((a) => isSupportAttachmentImage(a.mimeType) ? ( ) : ( ), )}
); }