"use client"; import { useState, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import type { Message, MessageReaction } from "@/types"; import { Clock, Check, CheckCheck, XCircle, FileText, MapPin, LayoutTemplate, ImageOff, } from "lucide-react"; import { format } from "date-fns"; import { ReplyQuote } from "./reply-quote"; import { MessageReactions } from "./message-reactions"; interface MessageBubbleProps { message: Message; /** Pre-computed quote info for messages that reply to another. */ reply?: { authorLabel: string; preview: string } | null; reactions?: MessageReaction[]; currentUserId?: string; onToggleReaction?: (emoji: string) => void; } function StatusIcon({ status }: { status: Message["status"] }) { switch (status) { case "sending": return ; case "sent": return ; case "delivered": return ; case "read": return ; case "failed": return ; default: return null; } } function MediaUnavailable({ label }: { label: string }) { return (
{label} unavailable
); } function MediaImage({ url, alt }: { url: string; alt: string }) { const [src, setSrc] = useState(null); const [error, setError] = useState(false); const [loading, setLoading] = useState(true); const loadImage = useCallback(async () => { if (!url) return; // Proxy URLs need auth fetch to create blob URL if (url.startsWith("/api/whatsapp/media/")) { try { const res = await fetch(url); if (!res.ok) throw new Error("Failed to load media"); const blob = await res.blob(); const blobUrl = URL.createObjectURL(blob); setSrc(blobUrl); } catch { setError(true); } finally { setLoading(false); } } else { setSrc(url); setLoading(false); } }, [url]); useEffect(() => { loadImage(); return () => { if (src?.startsWith("blob:")) { URL.revokeObjectURL(src); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [loadImage]); if (error) { return (
); } if (loading) { return (
); } return ( {alt} setError(true)} /> ); } function MessageContent({ message }: { message: Message }) { switch (message.content_type) { case "text": return (

{message.content_text}

); case "image": return (
{message.media_url ? ( ) : ( )} {message.content_text && (

{message.content_text}

)}
); case "video": return (
{message.media_url ? (
); case "audio": return (
{message.media_url ? (
); case "document": if (!message.media_url) { return ; } return ( {message.content_text || "Document"} ); case "template": return (
Template {message.content_text && (

{message.content_text}

)}
); case "location": return (
{message.content_text || "Location shared"}
); default: return (

{message.content_text || "[Unsupported message type]"}

); } } export function MessageBubble({ message, reply, reactions, currentUserId, onToggleReaction, }: MessageBubbleProps) { const isAgent = message.sender_type === "agent" || message.sender_type === "bot"; const time = format(new Date(message.created_at), "HH:mm"); return (
{reply && ( )}
{time} {isAgent && }
{reactions && reactions.length > 0 && onToggleReaction && ( )}
); }