Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src-gui/src/renderer/components/pages/help/ConversationsBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ import { Message, PrimitiveDateTimeString } from "models/apiModel";
import { formatDateTime } from "utils/conversionUtils";
import { Theme } from "renderer/components/theme";

const EMPTY_MESSAGES: Message[] = [];

// 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));
const seenMessages = useAppSelector((s) => s.conversations.seenMessages);
return useMemo(() => {
const seen = new Set(seenMessages);
const arr = ids.map((id) => {
const msgs = conv[id] || [];
const msgs = conv[id] ?? EMPTY_MESSAGES;
const unread = msgs.filter(
(m) => m.is_from_staff && !seen.has(m.id.toString()),
).length;
Expand All @@ -74,7 +77,7 @@ function useSortedFeedbackIds() {
b.latest - a.latest || (b.unread > 0 ? 1 : 0) - (a.unread > 0 ? 1 : 0),
);
return arr.map((x) => x.id);
}, [ids, conv, seen]);
}, [ids, conv, seenMessages]);
}

// Main component
Expand Down Expand Up @@ -173,7 +176,7 @@ function ConversationRow({
onOpen: (id: string) => void;
}) {
const msgs = useAppSelector(
(s) => s.conversations.conversations[feedbackId] || [],
(s) => s.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES,
);
const unread = useUnreadMessagesCount(feedbackId);
const sorted = useMemo(
Expand Down Expand Up @@ -245,7 +248,7 @@ function ConversationModal({
}) {
const dispatch = useAppDispatch();
const msgs = useAppSelector(
(s) => s.conversations.conversations[feedbackId] || [],
(s) => s.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES,
);
const [newMessage, setNewMessage] = useState("");
const [sending, setSending] = useState(false);
Expand Down
61 changes: 35 additions & 26 deletions src-gui/src/store/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
TauriBackgroundProgress,
TauriBitcoinSyncProgress,
} from "models/tauriModel";
import { Alert } from "models/apiModel";
import { Alert, Message } from "models/apiModel";
import { fnv1a } from "utils/hash";
import {
selectAllSwapInfos,
Expand All @@ -43,6 +43,8 @@ import {
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

const EMPTY_MESSAGES: Message[] = [];

export function useResumeableSwapsCount(
additionalFilter?: (s: GetSwapInfoResponseExt) => boolean,
) {
Expand Down Expand Up @@ -300,41 +302,48 @@ export function useConservativeBitcoinSyncProgress(): TauriBitcoinSyncProgress |
* @returns The number of unread staff messages.
*/
export function useUnreadMessagesCount(feedbackId: string): number {
const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({
conversationsMap: state.conversations.conversations,
// Convert seenMessages array to a Set for efficient lookup
seenMessagesSet: new Set(state.conversations.seenMessages),
}));

const messages = conversationsMap[feedbackId] || [];

const unreadStaffMessages = messages.filter(
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
const messages = useAppSelector(
(state) => state.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES,
);
const seenMessages = useAppSelector(
(state) => state.conversations.seenMessages,
);

return unreadStaffMessages.length;
return useMemo(() => {
const seenMessagesSet = new Set(seenMessages);

return messages.filter(
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
).length;
}, [messages, seenMessages]);
}

/**
* Calculates the total number of unread messages from staff across all feedback conversations.
* @returns The total number of unread staff messages.
*/
export function useTotalUnreadMessagesCount(): number {
const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({
conversationsMap: state.conversations.conversations,
seenMessagesSet: new Set(state.conversations.seenMessages),
}));

let totalUnreadCount = 0;
for (const feedbackId in conversationsMap) {
const messages = conversationsMap[feedbackId] || [];
const unreadStaffMessages = messages.filter(
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
);
totalUnreadCount += unreadStaffMessages.length;
}
const conversationsMap = useAppSelector(
(state) => state.conversations.conversations,
);
const seenMessages = useAppSelector(
(state) => state.conversations.seenMessages,
);

return useMemo(() => {
const seenMessagesSet = new Set(seenMessages);

let totalUnreadCount = 0;
for (const feedbackId in conversationsMap) {
const messages = conversationsMap[feedbackId] ?? EMPTY_MESSAGES;
const unreadStaffMessages = messages.filter(
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
);
totalUnreadCount += unreadStaffMessages.length;
}

return totalUnreadCount;
return totalUnreadCount;
}, [conversationsMap, seenMessages]);
}

/// Returns all the alerts that have not been acknowledged
Expand Down