import { useState, useEffect, useMemo } from "react"; import { Box, Typography, Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Paper, Badge, TextField, CircularProgress, InputAdornment, Tooltip, List, ListItem, ListItemIcon, Link, } from "@mui/material"; import ChatIcon from "@mui/icons-material/Chat"; import SendIcon from "@mui/icons-material/Send"; import InfoBox from "renderer/components/pages/swap/swap/components/InfoBox"; import TruncatedText from "renderer/components/other/TruncatedText"; import { useAppSelector, useAppDispatch, useUnreadMessagesCount, } from "store/hooks"; import { markMessagesAsSeen } from "store/features/conversationsSlice"; import { appendFeedbackMessageViaHttp, fetchAllConversations, } from "renderer/api"; import { useSnackbar } from "notistack"; import logger from "utils/logger"; import AttachmentIcon from "@mui/icons-material/Attachment"; import { Message, PrimitiveDateTimeString } from "models/apiModel"; import { formatDateTime } from "utils/conversionUtils"; import { Theme } from "renderer/components/theme"; // Hook: sorted feedback IDs by latest activity, then unread function useSortedFeedbackIds() { const ids = useAppSelector((s) => s.conversations.knownFeedbackIds || []); const conv = useAppSelector((s) => s.conversations.conversations); const seen = useAppSelector((s) => new Set(s.conversations.seenMessages)); return useMemo(() => { const arr = ids.map((id) => { const msgs = conv[id] || []; const unread = msgs.filter( (m) => m.is_from_staff && !seen.has(m.id.toString()), ).length; const latest = msgs.reduce((d, m) => { try { const formattedDate = formatDateTime(m.created_at); if (formattedDate.startsWith("Invalid")) return d; const t = new Date(formattedDate).getTime(); return isNaN(t) ? d : Math.max(d, t); } catch (e) { return d; } }, 0); return { id, unread, latest }; }); arr.sort( (a, b) => b.latest - a.latest || (b.unread > 0 ? 1 : 0) - (a.unread > 0 ? 1 : 0), ); return arr.map((x) => x.id); }, [ids, conv, seen]); } // Main component export default function ConversationsBox() { const sortedIds = useSortedFeedbackIds(); const [openId, setOpenId] = useState(null); useEffect(() => { // Fetch conversations via API function (handles its own dispatch) fetchAllConversations(); }, []); return ( View your past feedback submissions and any replies from the development team. {sortedIds.length === 0 ? ( No feedback submitted yet. ) : ( ({ width: "25%", backgroundColor: theme.palette.grey[900], })} > Last Message ({ width: "60%", backgroundColor: theme.palette.grey[900], })} > Preview ({ width: "15%", backgroundColor: theme.palette.grey[900], })} /> {sortedIds.map((id) => ( ))}
)} } additionalContent={ openId && ( setOpenId(null)} feedbackId={openId} /> ) } /> ); } // Single row function ConversationRow({ feedbackId, onOpen, }: { feedbackId: string; onOpen: (id: string) => void; }) { const msgs = useAppSelector( (s) => s.conversations.conversations[feedbackId] || [], ); const unread = useUnreadMessagesCount(feedbackId); const sorted = useMemo( () => [...msgs].sort((a, b) => { try { const formattedDateA = formatDateTime(a.created_at); const formattedDateB = formatDateTime(b.created_at); if (formattedDateA.startsWith("Invalid")) return 1; if (formattedDateB.startsWith("Invalid")) return -1; const dateA = new Date(formattedDateA).getTime(); const dateB = new Date(formattedDateB).getTime(); if (isNaN(dateA)) return 1; if (isNaN(dateB)) return -1; return dateB - dateA; } catch (e) { return 0; } }), [msgs], ); const lastMsg = sorted[0]; const time = lastMsg ? formatDateTime(lastMsg.created_at) : "-"; const content = lastMsg ? lastMsg.content : "No messages yet"; const preview = (() => { return content; })(); const hasStaff = useMemo(() => msgs.some((m) => m.is_from_staff), [msgs]); return ( {time} "{preview}" onOpen(feedbackId)} disabled={!hasStaff} > ); } // Modal function ConversationModal({ open, onClose, feedbackId, }: { open: boolean; onClose: () => void; feedbackId: string; }) { const dispatch = useAppDispatch(); const msgs = useAppSelector( (s) => s.conversations.conversations[feedbackId] || [], ); const [newMessage, setNewMessage] = useState(""); const [sending, setSending] = useState(false); const { enqueueSnackbar } = useSnackbar(); // Mark messages as seen when modal opens useEffect(() => { if (open && msgs.length > 0) { const unreadMessages = msgs.filter((m) => m.is_from_staff); if (unreadMessages.length > 0) { dispatch(markMessagesAsSeen(unreadMessages)); } } }, [open, msgs, dispatch]); // Sort messages chronologically const sortedMsgs = useMemo( () => [...msgs].sort((a, b) => { try { const formattedDateA = formatDateTime(a.created_at); const formattedDateB = formatDateTime(b.created_at); if (formattedDateA.startsWith("Invalid")) return -1; if (formattedDateB.startsWith("Invalid")) return 1; const dateA = new Date(formattedDateA).getTime(); const dateB = new Date(formattedDateB).getTime(); if (isNaN(dateA)) return -1; if (isNaN(dateB)) return 1; return dateA - dateB; } catch (e) { return 0; } }), [msgs], ); const sendMessage = async () => { if (!newMessage.trim() || sending) return; setSending(true); try { await appendFeedbackMessageViaHttp(feedbackId, newMessage.trim()); setNewMessage(""); enqueueSnackbar("Message sent successfully!", { variant: "success" }); // Fetch updated conversations fetchAllConversations(); } catch (error) { logger.error(`Error sending message: ${error}`); enqueueSnackbar("Failed to send message. Please try again.", { variant: "error", }); } finally { setSending(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; return ( Conversation {sortedMsgs.map((msg) => ( ))} setNewMessage(e.target.value)} onKeyPress={handleKeyPress} placeholder="Type your message here..." disabled={sending} InputProps={{ endAdornment: ( {sending ? : } ), }} /> ); } // Message bubble component function MessageBubble({ message }: { message: Message }) { const isStaff = message.is_from_staff; const time = formatDateTime(message.created_at); const attachments = message.attachments || []; return ( ({ padding: 1.5, maxWidth: "75%", wordBreak: "break-word", boxShadow: theme.shadows[1], ...(isStaff ? { border: `1px solid ${theme.palette.divider}`, color: theme.palette.text.primary, borderRadius: 2, } : { backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastText, borderRadius: 2, }), })} > {message.content} {attachments.length > 0 && ( {attachments.map((att, idx) => ( { e.preventDefault(); alert( `Attachment Key: ${att.key}\n\nContent:\n${att.content}`, ); }} > {att.key} ))} )} {time} ); }