import { useState, useRef, useEffect } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { useChatMessages, usePartnerInfo, STAFF_GROUP_ID } from "@/hooks/useDirectMessages"; import { supabase } from "@/integrations/supabase/client"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { FilePlus, Send, Loader2, MessageSquare, Trash2 } from "lucide-react"; import { format, isToday, isYesterday } from "date-fns"; import { cn } from "@/lib/utils"; import { useToast } from "@/hooks/use-toast"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; interface Props { partnerId: string | null; partnerName: string; onDeleted?: () => void; } function getInitials(name: string) { return name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2); } function formatMessageDate(dateStr: string) { const d = new Date(dateStr); if (isToday(d)) return format(d, "h:mm a"); if (isYesterday(d)) return "Yesterday " + format(d, "h:mm a"); return format(d, "MMM d, h:mm a"); } export default function ChatView({ partnerId, partnerName, onDeleted }: Props) { const { user, isAdmin, isStaff } = useAuth(); const { messages, loading, sendMessage, deleteMessage } = useChatMessages(partnerId); const partnerInfo = usePartnerInfo(partnerId); const [draft, setDraft] = useState(""); const [sending, setSending] = useState(false); const [deleteConvOpen, setDeleteConvOpen] = useState(false); const canDeleteConversation = !!partnerId && partnerId !== STAFF_GROUP_ID; const deleteConversation = async () => { if (!user || !partnerId) return; const { error } = await supabase .from("direct_messages") .delete() .or(`and(sender_id.eq.${user.id},recipient_id.eq.${partnerId}),and(sender_id.eq.${partnerId},recipient_id.eq.${user.id})`); setDeleteConvOpen(false); if (error) { toast({ title: "Couldn't delete conversation", description: error.message, variant: "destructive" }); return; } toast({ title: "Conversation deleted" }); onDeleted?.(); }; const [creatingRequestId, setCreatingRequestId] = useState(null); const [deletingId, setDeletingId] = useState(null); const scrollRef = useRef(null); const { toast } = useToast(); const canCreateRequests = isAdmin || isStaff; // Auto-scroll to bottom on new messages useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages]); const handleSend = async () => { if (!draft.trim() || sending) return; setSending(true); try { await sendMessage(draft); setDraft(""); } catch (err: any) { toast({ title: "Error sending message", description: err.message, variant: "destructive" }); } finally { setSending(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const createHomeownerRequest = async (messageId: string, message: string, createdAt: string) => { if (!partnerId || !canCreateRequests) return; setCreatingRequestId(messageId); try { const { data: owner, error: ownerError } = await supabase .from("owners") .select("id, association_id, first_name, last_name, property_address, units(address)") .eq("user_id", partnerId) .neq("status", "archived") .limit(1) .maybeSingle(); if (ownerError) throw ownerError; if (!owner) throw new Error("No homeowner record is linked to this sender."); const title = message.replace(/\s+/g, " ").trim().slice(0, 80) || `Message from ${partnerName}`; const ownerName = `${owner.first_name || ""} ${owner.last_name || ""}`.trim() || partnerName; const description = [ `Created from homeowner message on ${format(new Date(createdAt), "MMM d, yyyy h:mm a")}.`, `Homeowner: ${ownerName}`, owner.property_address || (owner.units as any)?.address ? `Property: ${owner.property_address || (owner.units as any)?.address}` : null, "", message, ].filter(Boolean).join("\n"); const { error } = await supabase.from("homeowner_requests").insert({ association_id: owner.association_id, owner_id: owner.id, title, description, category: "general", priority: "medium", status: "open", }); if (error) throw error; toast({ title: "Homeowner request created" }); } catch (err: any) { toast({ title: "Request not created", description: err.message, variant: "destructive" }); } finally { setCreatingRequestId(null); } }; if (!partnerId) { return (

Select a conversation or start a new message

); } return (
{/* Header */}
{getInitials(partnerName)}
{partnerName}
{partnerInfo && (partnerInfo.ownerName || partnerInfo.unit || partnerInfo.associationName) && (
{[partnerInfo.ownerName, partnerInfo.unit, partnerInfo.associationName].filter(Boolean).join(" • ")}
)}
{canDeleteConversation && ( )}
Delete this conversation? This permanently deletes the conversation with {partnerName} for everyone. This can't be undone. Cancel Delete {/* Messages */}
{loading ? (
) : messages.length === 0 ? (

No messages yet. Say hello!

) : ( messages.map((msg) => { const isMe = msg.sender_id === user?.id; const canDelete = isMe || isStaff || isAdmin; return (
{isMe && canDelete && ( Delete message? This will remove the message permanently. Cancel { setDeletingId(msg.id); try { await deleteMessage(msg.id); } catch (e: any) { toast({ title: "Delete failed", description: e.message, variant: "destructive" }); } finally { setDeletingId(null); } }} > Delete )}

{msg.message}

{formatMessageDate(msg.created_at)}

{!isMe && canCreateRequests && ( )}
{!isMe && canDelete && ( Delete message? This will remove the message permanently. Cancel { setDeletingId(msg.id); try { await deleteMessage(msg.id); } catch (e: any) { toast({ title: "Delete failed", description: e.message, variant: "destructive" }); } finally { setDeletingId(null); } }} > Delete )}
); }) )}
{/* Input */}