import { useState, useEffect } from 'react' import { collabApi } from '../../api/client' /* ── Link Preview ── */ // linkPreview is trip-scoped on the server, so the cache key has to be too. // Capped as well: a long chat session would otherwise keep every preview it ever // rendered for as long as the tab lives. const previewCache = new Map() const cacheKey = (tripId: number, url: string): string => `${tripId} ${url}` const MAX_CACHED_PREVIEWS = 200 interface LinkPreviewProps { url: string tripId: number own: boolean onLoad: (() => void) | undefined } export function LinkPreview({ url, tripId, own, onLoad }: LinkPreviewProps) { const [data, setData] = useState(previewCache.get(cacheKey(tripId, url)) || null) const [loading, setLoading] = useState(!previewCache.has(cacheKey(tripId, url))) useEffect(() => { const key = cacheKey(tripId, url) const cached = previewCache.get(key) if (cached) { setData(cached); setLoading(false); return } let current = true collabApi.linkPreview(tripId, url).then(d => { if (previewCache.size >= MAX_CACHED_PREVIEWS) previewCache.delete(previewCache.keys().next().value) previewCache.set(key, d) if (!current) return setData(d) setLoading(false) if (d?.title || d?.description || d?.image) onLoad?.() }).catch(() => { if (current) setLoading(false) }) return () => { current = false } }, [url, tripId]) if (loading || !data || (!data.title && !data.description && !data.image)) return null const domain = (() => { try { return new URL(url).hostname.replace('www.', '') } catch { return '' } })() return ( e.currentTarget.style.opacity = '0.85'} onMouseLeave={e => e.currentTarget.style.opacity = '1'} > {data.image && ( e.currentTarget.style.display = 'none'} /> )}
{domain && (
{data.site_name || domain}
)} {data.title && (
{data.title}
)} {data.description && (
{data.description}
)}
) }