"use client"; import { cn } from "~/lib/utils"; import { useCallback, useEffect, useRef, useState, type FormEvent, } from "react"; import { api } from "~/utils/api"; import toast from "react-hot-toast"; import Input from "~/components/custom_ui/input"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { Button } from "~/components/ui/button"; import { Pencil, Trash2 } from "lucide-react"; import type { MessagesMessageid } from "~/schemas/public/Messages"; import { AnimatePresence, motion } from "framer-motion"; import { ChatBubble, ChatBubbleAction, ChatBubbleActionWrapper, ChatBubbleMessage, ChatBubbleReadStatus, ChatBubbleTimestamp, } from "~/components/chat/chat-bubble"; import { ChatMessageList } from "~/components/chat/chat-message-list"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "~/components/ui/alert-dialog"; import { useTranslation } from "~/providers/I18nProvider"; import type { ChatMessage } from "~/server/services/messages.service"; import type { UsersId } from "~/schemas/public/Users"; import type { WhoIsTyping } from "~/server/sse"; import type { Session } from "~/server/api/trpc"; import type { ChatsChatid } from "~/schemas/public/Chats"; import type { LiveChat } from "~/hooks/chatHooks"; import LoadingButton from "~/components/custom_ui/loading-button"; import { getUserColor, UserAvatar } from "~/components/user_avatar"; type ChatListProps = { chatId: ChatsChatid; messages: ChatMessage[]; userData: Session; query: LiveChat["query"]; typingStatus: WhoIsTyping[]; }; export const ChatList = ({ chatId, messages, userData, query, typingStatus, }: ChatListProps) => { const messagesContainerRef = useRef(null); const [editModalOpen, setEditModalOpen] = useState(false); const [msgSelected, setMsgSelected] = useState(null); const [deleteModalOpen, setDeleteModalOpen] = useState(false); const handleEditOpen = (status: boolean) => { setEditModalOpen(status); }; const handleDeleteOpen = (status: boolean) => { setDeleteModalOpen(status); }; const { mutate: deleteMutation } = api.messagesSSE.delete.useMutation({ onMutate: () => { toast("Deleting message...", { icon: "🗑️", id: "deleteMessage" }); }, onSuccess: () => { toast("Message deleted", { icon: "🗑️", id: "deleteMessage" }); }, onError: () => { toast.error("Error deleting message", { id: "deleteMessage" }); }, }); const { mutate: editMutation } = api.messagesSSE.edit.useMutation({ onMutate: () => { toast("Editing message...", { icon: "📝", id: "editMessage" }); }, onSuccess: () => { toast("Message edited", { icon: "📝", id: "editMessage" }); }, onError: () => { toast.error("Error editing message", { id: "editMessage" }); }, }); const handleEditSubmit = useCallback( (e: FormEvent) => { e.preventDefault(); const form = e.currentTarget; const formData = new FormData(form); if (!formData.get("message-edit") || !formData.get("message-id")) { return; } editMutation({ chatId, messageId: formData.get("message-id") as MessagesMessageid, message: formData.get("message-edit") as string, }); setEditModalOpen(false); }, [editMutation], ); const [locked, setLocked] = useState(false); useEffect(() => { if ( messagesContainerRef.current && !locked && !query.isLoading && !query.isFetching ) { messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight; } setLocked(false); }, [messages, typingStatus]); return (
{query.hasNextPage && ( { setLocked(true); await query.fetchNextPage(); }} className="mb-3" > {query.isFetchingNextPage ? "Caricamento..." : "Carica messaggi precedenti"} )} {messages.map((message, index) => { const variant = getMessageVariant(message.sender, userData.id); const isNextSameSender = index + 1 < messages.length && messages[index + 1]?.sender == message.sender; const isPreviousSameSender = index - 1 >= 0 && messages[index - 1]?.sender == message.sender; const isPrevSameDay = index - 1 >= 0 && new Date(messages[index - 1]!.time).toDateString() === new Date(message.time).toDateString(); return ( {!isPrevSameDay && ( {new Date(message.time).toLocaleDateString("it", { day: "2-digit", month: "2-digit", year: "2-digit", })} )} {isNextSameSender ? (
) : ( )}
{!isPreviousSameSender && (

{message.sender_nome}

)} {message.message}
{userData.id == message.sender && ( )} {message.time && ( )}
} onClick={() => { setMsgSelected(message); handleDeleteOpen(true); }} /> } onClick={() => { setMsgSelected(message); handleEditOpen(true); }} />
); })}
{ deleteMutation({ chatId, messageId }); }} />
); }; const Typers = ({ currentTypers, messageLenght, userId, }: { currentTypers: WhoIsTyping[]; messageLenght: number; userId: UsersId; }) => { return ( <> {currentTypers.map((typer, index) => { if (typer.userId === userId) return null; const variant = "received"; return ( ); })} ); }; const EditMessageDialog = ({ open, onOpenChange, msgEdit, handleEditSubmit, }: { open: boolean; onOpenChange: (status: boolean) => void; msgEdit: ChatMessage | null; handleEditSubmit: (e: FormEvent) => void; }) => { const { t } = useTranslation(); return ( {t.modifica} Edit {msgEdit && (
)}
); }; const DeleteMessageDialog = ({ open, onOpenChange, msgDelete, handleDeleteSubmit, }: { open: boolean; onOpenChange: (status: boolean) => void; msgDelete: ChatMessage | null; handleDeleteSubmit: (msgId: MessagesMessageid) => void; }) => { const { t } = useTranslation(); return ( <> {msgDelete && ( Vuoi eliminare il messaggio? Questa azione non può essere annullata. {t.annulla} handleDeleteSubmit(msgDelete.messageid)} > {t.procedi} )} ); }; const getMessageVariant = (messageName: string, selectedUserName: string) => messageName !== selectedUserName ? "received" : "sent";