"use client"; import { formatDistanceToNow } from "date-fns"; import { fr } from "date-fns/locale"; import { ArrowLeft, Check, CheckCheck, Search, Send, UserPlus, X, } from "lucide-react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import * as React from "react"; import { toast } from "sonner"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useAuth } from "@/providers/auth-provider"; import { useSocket } from "@/providers/socket-provider"; import { type Conversation, type Message, MessageService, } from "@/services/message.service"; import { UserService } from "@/services/user.service"; import type { User } from "@/types/user"; export default function MessagesPage() { const { user } = useAuth(); const { socket } = useSocket(); const _router = useRouter(); const searchParams = useSearchParams(); const targetUserId = searchParams.get("user"); const [conversations, setConversations] = React.useState([]); const [activeConv, setActiveConv] = React.useState(null); const [messages, setMessages] = React.useState([]); const [newMessage, setNewMessage] = React.useState(""); const typingTimeoutRef = React.useRef(null); const handleTyping = () => { if (!socket || !activeConv) return; socket.emit("typing", { recipientId: activeConv.recipient.uuid, isTyping: true, }); if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current); typingTimeoutRef.current = setTimeout(() => { socket.emit("typing", { recipientId: activeConv.recipient.uuid, isTyping: false, }); }, 3000); }; const [isLoadingConvs, setIsLoadingConvs] = React.useState(true); const [isLoadingMsgs, setIsLoadingMsgs] = React.useState(false); const [isOtherTyping, setIsOtherTyping] = React.useState(false); const [onlineUsers, setOnlineUsers] = React.useState>(new Set()); const [searchQuery, setSearchQuery] = React.useState(""); const [searchResults, setSearchResults] = React.useState([]); const [isSearching, setIsSearching] = React.useState(false); const scrollRef = React.useRef(null); // Charger les conversations initiales React.useEffect(() => { const fetchConvs = async () => { try { const data = await MessageService.getConversations(); setConversations(data); // Si un utilisateur est spécifié dans l'URL, essayer de trouver la conversation if (targetUserId) { const existing = data.find((c) => c.recipient.uuid === targetUserId); if (existing) { setActiveConv(existing); } else { // Chercher les infos de l'utilisateur pour afficher une interface de chat vide try { const conv = await MessageService.getConversationWith(targetUserId); if (conv) { setConversations((prev) => [conv, ...prev]); setActiveConv(conv); } } catch (_e) { // Peut-être que l'utilisateur n'existe pas ou erreur } } } } catch (_error) { toast.error("Erreur lors du chargement des conversations"); } finally { setIsLoadingConvs(false); } }; fetchConvs(); }, [targetUserId]); // Recherche d'utilisateurs React.useEffect(() => { const delayDebounceFn = setTimeout(async () => { if (searchQuery.length > 1) { setIsSearching(true); try { const results = await UserService.search(searchQuery); setSearchResults(results.filter((u) => u.uuid !== user?.uuid)); } catch (_error) { console.error("Search failed"); } finally { setIsSearching(false); } } else { setSearchResults([]); } }, 300); return () => clearTimeout(delayDebounceFn); }, [searchQuery, user?.uuid]); React.useEffect(() => { if (activeConv) { const fetchMsgs = async () => { setIsLoadingMsgs(true); try { const data = await MessageService.getMessages(activeConv.id); setMessages(data.reverse()); // Plus ancien au plus récent } catch (_error) { toast.error("Erreur lors du chargement des messages"); } finally { setIsLoadingMsgs(false); } }; fetchMsgs(); } }, [activeConv]); React.useEffect(() => { if (socket) { socket.on( "new_message", (data: { conversationId: string; message: Message }) => { if (activeConv?.id === data.conversationId) { setMessages((prev) => [...prev, data.message]); setIsOtherTyping(false); // S'il a envoyé un message, il ne tape plus // Marquer comme lu immédiatement si on est sur la conversation MessageService.markAsRead(data.conversationId).catch(console.error); } // Mettre à jour la liste des conversations setConversations((prev) => { const index = prev.findIndex((c) => c.id === data.conversationId); if (index !== -1) { const updated = [...prev]; updated[index] = { ...updated[index], lastMessage: { text: data.message.text, createdAt: data.message.createdAt, }, updatedAt: data.message.createdAt, }; return updated.sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), ); } return prev; }); }, ); socket.on("user_status", (data: { userId: string; status: string }) => { setOnlineUsers((prev) => { const next = new Set(prev); if (data.status === "online") { next.add(data.userId); } else { next.delete(data.userId); } return next; }); }); socket.on("user_typing", (data: { userId: string; isTyping: boolean }) => { if (activeConv?.recipient.uuid === data.userId) { setIsOtherTyping(data.isTyping); } }); socket.on( "messages_read", (data: { conversationId: string; readerId: string }) => { if (activeConv?.id === data.conversationId) { setMessages((prev) => prev.map((msg) => msg.senderId !== data.readerId && !msg.readAt ? { ...msg, readAt: new Date().toISOString() } : msg, ), ); } }, ); return () => { socket.off("new_message"); socket.off("user_status"); socket.off("user_typing"); socket.off("messages_read"); }; } }, [socket, activeConv]); React.useEffect(() => { if (scrollRef.current) { const scrollContainer = scrollRef.current.querySelector( "[data-slot='scroll-area-viewport']", ); if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } } }, []); const handleSendMessage = async (e: React.FormEvent) => { e.preventDefault(); if (!newMessage.trim() || !activeConv) return; const text = newMessage.trim(); setNewMessage(""); try { const msg = await MessageService.sendMessage( activeConv.recipient.uuid, text, ); // Si c'était une conv temporaire, on la remplace par la vraie if (activeConv.id.startsWith("temp-")) { const fetchConvs = async () => { const data = await MessageService.getConversations(); setConversations(data); const realConv = data.find( (c) => c.recipient.uuid === activeConv.recipient.uuid, ); if (realConv) setActiveConv(realConv); }; fetchConvs(); } else { setMessages((prev) => [...prev, msg]); } } catch (_error) { toast.error("Erreur lors de l'envoi"); } }; return (
{/* Sidebar - Liste des conversations */}

Messages

setSearchQuery(e.target.value)} /> {searchQuery && ( )}
{searchQuery.length > 0 ? ( <>

Membres

{isSearching ? (
Recherche...
) : searchResults.length === 0 ? (
Aucun membre trouvé.
) : ( searchResults.map((result) => ( )) )} ) : isLoadingConvs ? (
Chargement...
) : conversations.length === 0 ? (
Aucune conversation.
) : ( conversations.map((conv) => ( )) )}
{/* Zone de chat */}
{activeConv ? ( <> {/* Header */}
{activeConv.recipient.username[0].toUpperCase()}

{activeConv.recipient.displayName || activeConv.recipient.username}

{onlineUsers.has(activeConv.recipient.uuid) ? "En ligne" : "Hors ligne"}
{/* Messages */}
{isLoadingMsgs ? (
Chargement...
) : ( messages.map((msg) => (

{msg.text}

{new Date(msg.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} {msg.senderId === user?.uuid && ( {msg.readAt ? ( ) : ( )} )}
)) )} {isOtherTyping && (
)}
{/* Input */}
{ setNewMessage(e.target.value); handleTyping(); }} className="rounded-full px-4" />
) : (

Vos messages

Sélectionnez une conversation ou démarrez-en une nouvelle pour commencer à discuter.

)}
); }