"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { Send, Plus, Users, Search, ArrowLeft } from "lucide-react"; import { createBrowserClient } from "@supabase/ssr"; import { fetchConversations, fetchMessages, sendMessage, createOrFindConversation, fetchOrgMembers, type ConversationSummary, type Message, } from "@/lib/supabase/comms-actions"; import PageSkeleton from "./PageSkeleton"; const supabase = createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); function formatTime(dateStr: string): string { const d = new Date(dateStr); const now = new Date(); const diff = now.getTime() - d.getTime(); if (diff < 86400000) return d.toLocaleTimeString("en-AU", { hour: "2-digit", minute: "2-digit" }); return d.toLocaleDateString("en-AU", { day: "numeric", month: "short" }); } function getInitials(name: string): string { return name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2); } export default function CommsPage() { const [conversations, setConversations] = useState([]); const [activeConvo, setActiveConvo] = useState(null); const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(""); const [showPeoplePicker, setShowPeoplePicker] = useState(false); const [members, setMembers] = useState<{ id: string; name: string; email: string; role: string }[]>([]); const [loading, setLoading] = useState(true); const [searchMember, setSearchMember] = useState(""); const [userId, setUserId] = useState(""); const messagesEndRef = useRef(null); useEffect(() => { let cancelled = false; // Get Clerk user ID fetch("/api/me") .then((r) => r.json()) .then((d) => { if (d.userId) setUserId(d.userId); }) .catch(() => {}); fetchConversations() .then((convos) => { if (!cancelled) setConversations(convos); }) .catch(() => {}) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); // Real-time subscription for new messages useEffect(() => { if (!activeConvo) return; const channel = supabase .channel(`messages:${activeConvo}`) .on( "postgres_changes", { event: "INSERT", schema: "public", table: "messages", filter: `conversation_id=eq.${activeConvo}` }, (payload) => { const newMsg = payload.new as any; setMessages((prev) => [...prev, { id: newMsg.id, conversation_id: newMsg.conversation_id, sender_id: newMsg.sender_id, sender_name: "", sender_role: null, body: newMsg.body, created_at: newMsg.created_at, read_at: null, }]); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [activeConvo]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const openConversation = useCallback(async (convoId: string) => { setActiveConvo(convoId); try { const msgs = await fetchMessages(convoId); setMessages(msgs); } catch {} }, []); const handleSendMessage = async () => { if (!activeConvo || !newMessage.trim()) return; const body = newMessage.trim(); setNewMessage(""); try { await sendMessage(activeConvo, body); } catch {} }; const handleStartConversation = async (participantId: string) => { try { const result = await createOrFindConversation(participantId); if (result.conversationId) { setShowPeoplePicker(false); await openConversation(result.conversationId); // Refresh conversation list const convos = await fetchConversations(); setConversations(convos); } } catch {} }; const openPeoplePicker = async () => { setShowPeoplePicker(true); try { const m = await fetchOrgMembers(); setMembers(m); } catch {} }; const filteredMembers = members.filter((m) => m.name.toLowerCase().includes(searchMember.toLowerCase()) || m.email.toLowerCase().includes(searchMember.toLowerCase()) ); if (loading) return ; return (

Comms

Direct messaging for your team

{/* Conversation List */}

Messages

{conversations.length === 0 ? (

No conversations yet

) : ( conversations.map((c) => ( )) )}
{/* Message Area */}
{activeConvo ? ( <> {/* Messages */}
{messages.map((m) => { const isMine = m.sender_id === userId; return (
{!isMine && (

{m.sender_name}

{m.sender_role && ( {m.sender_role} )}
)}

{m.body}

{formatTime(m.created_at)}

); })}
{/* Message Input */}
setNewMessage(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }} placeholder="Type a message..." className="flex-1 border border-gray-200 rounded-full px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#1D9E75]/20 focus:border-[#1D9E75]" />
) : (

Select a conversation

Or start a new one

)}
{/* People Picker Modal */} {showPeoplePicker && (
setShowPeoplePicker(false)}>
e.stopPropagation()}>

New message

setSearchMember(e.target.value)} placeholder="Search people..." className="w-full pl-10 pr-4 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#1D9E75]/20 focus:border-[#1D9E75]" />
{filteredMembers.map((m) => ( ))} {filteredMembers.length === 0 && (

No one found

)}
)}
); }