diff --git a/frontend/src/app/(dashboard)/messages/page.tsx b/frontend/src/app/(dashboard)/messages/page.tsx index af9de6a..60c2106 100644 --- a/frontend/src/app/(dashboard)/messages/page.tsx +++ b/frontend/src/app/(dashboard)/messages/page.tsx @@ -2,7 +2,8 @@ import { formatDistanceToNow } from "date-fns"; import { fr } from "date-fns/locale"; -import { Search, Send } from "lucide-react"; +import { Search, Send, UserPlus, X } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; import * as React from "react"; import { toast } from "sonner"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -16,23 +17,54 @@ import { 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 [isLoadingConvs, setIsLoadingConvs] = React.useState(true); const [isLoadingMsgs, setIsLoadingMsgs] = React.useState(false); + + 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 { @@ -40,7 +72,28 @@ export default function MessagesPage() { } }; 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) { @@ -114,7 +167,21 @@ export default function MessagesPage() { activeConv.recipient.uuid, text, ); - setMessages((prev) => [...prev, msg]); + + // 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"); } @@ -125,15 +192,94 @@ export default function MessagesPage() { {/* Sidebar - Liste des conversations */}
-

Messages

+
+

Messages

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

+ Membres +

+ {isSearching ? ( +
+ Recherche... +
+ ) : searchResults.length === 0 ? ( +
+ Aucun membre trouvé. +
+ ) : ( + searchResults.map((result) => ( + + )) + )} + + ) : isLoadingConvs ? (
Chargement...
@@ -226,18 +372,25 @@ export default function MessagesPage() { }`} >

{msg.text}

-

- {new Date(msg.createdAt).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - })} -

+ + {new Date(msg.createdAt).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} + + {msg.senderId === user?.uuid && ( + + {msg.readAt ? "• Lu" : "• Envoyé"} + + )} +
)) diff --git a/frontend/src/app/(dashboard)/user/[username]/page.tsx b/frontend/src/app/(dashboard)/user/[username]/page.tsx index a77e263..fe6c6fc 100644 --- a/frontend/src/app/(dashboard)/user/[username]/page.tsx +++ b/frontend/src/app/(dashboard)/user/[username]/page.tsx @@ -1,12 +1,19 @@ "use client"; -import { Calendar, Share2, User as UserIcon } from "lucide-react"; +import { + Calendar, + MessageCircle, + Share2, + User as UserIcon, +} from "lucide-react"; +import Link from "next/link"; import * as React from "react"; import { toast } from "sonner"; import { ContentList } from "@/components/content-list"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; +import { useAuth } from "@/providers/auth-provider"; import { ContentService } from "@/services/content.service"; import { UserService } from "@/services/user.service"; import type { User } from "@/types/user"; @@ -17,9 +24,12 @@ export default function PublicProfilePage({ params: Promise<{ username: string }>; }) { const { username } = React.use(params); + const { user: currentUser, isAuthenticated } = useAuth(); const [user, setUser] = React.useState(null); const [loading, setLoading] = React.useState(true); + const isOwnProfile = currentUser?.username === username; + React.useEffect(() => { UserService.getProfile(username) .then(setUser) @@ -93,7 +103,15 @@ export default function PublicProfilePage({ })} -
+
+ {!isOwnProfile && isAuthenticated && ( + + )}