Improve messaging and dashboard reliability

This commit is contained in:
jjbrunton committed 2026-08-08 21:09:20 +01:00
1 parent 345ec99348
commit 5a5a30a1d4
20 files changed
+406 -56

No files matched your search

@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import type { ActiveChat } from '../../../../../packages/api-contracts/src/index.js';
import { RedditUsernameLink, redditUsernameLabel } from '../../components/reddit-username-link.js';
import { ConversationDetail } from './conversation-detail.js';
@@ -8,6 +8,7 @@ import { ProcessedUserLookup } from './processed-user-lookup.js';
import { AccountOwnerBadge } from '../../components/account-owner-badge.js';
import './conversations.css';
import { ownedConversationId } from './conversation-identity.js';
import { formatExactTime, formatShortRelativeTime } from './relative-time.js';
export type ConversationFilter = 'all' | 'unread' | 'awaiting-reply' | 'received-images';
@@ -30,9 +31,10 @@ function matchesFilter(conversation: ActiveChat, filter: ConversationFilter): bo
return true;
}
function ConversationListItem({ conversation, isSelected, onSelect }: {
function ConversationListItem({ conversation, isSelected, now, onSelect }: {
conversation: ActiveChat;
isSelected: boolean;
now: number;
onSelect(): void;
}) {
const state = conversation.unread ? 'Unread' : conversation.isFromUs ? 'Awaiting reply' : 'Up to date';
@@ -45,7 +47,15 @@ function ConversationListItem({ conversation, isSelected, onSelect }: {
onClick={onSelect}
type="button"
/>
<span className="conversation-list-heading"><strong><RedditUsernameLink username={conversation.username} /></strong><small>{state}</small></span>
<span className="conversation-list-heading">
<strong><RedditUsernameLink username={conversation.username} /></strong>
<span className="conversation-list-meta">
<small>{state}</small>
<time dateTime={new Date(conversation.lastMessageTime).toISOString()} title={formatExactTime(conversation.lastMessageTime)}>
{formatShortRelativeTime(conversation.lastMessageTime, now)}
</time>
</span>
</span>
<AccountOwnerBadge owner={conversation.owner} />
{conversation.hasReceivedImages ? <span className="conversation-image-marker">Image received</span> : null}
<span>{conversation.lastMessageSnippet || 'No message preview'}</span>
@@ -58,9 +68,15 @@ export function ConversationsPage({ selectedConversationId, onSelectedConversati
const [internalSelectedId, setInternalSelectedId] = useState<string | null>(null);
const [localFilters, setLocalFilters] = useState<ConversationFilters>({ search: '', filter: 'all' });
const [isStartingConversation, setIsStartingConversation] = useState(false);
const [now, setNow] = useState(() => Date.now());
const activeConversationId = selectedConversationId !== undefined ? selectedConversationId : internalSelectedId;
const activeFilters = filters ?? localFilters;
useEffect(() => {
const interval = window.setInterval(() => setNow(Date.now()), 60_000);
return () => window.clearInterval(interval);
}, []);
function updateFilters(next: ConversationFilters) {
if (filters === undefined) setLocalFilters(next);
onFiltersChange?.(next);
@@ -68,12 +84,14 @@ export function ConversationsPage({ selectedConversationId, onSelectedConversati
const visibleConversations = useMemo(() => {
const normalizedSearch = activeFilters.search.trim().toLowerCase();
return (conversationsQuery.data ?? []).filter((conversation) => {
const matchesSearch = normalizedSearch.length === 0
|| conversation.username.toLowerCase().includes(normalizedSearch)
|| conversation.lastMessageSnippet.toLowerCase().includes(normalizedSearch);
return matchesSearch && matchesFilter(conversation, activeFilters.filter);
});
return (conversationsQuery.data ?? [])
.filter((conversation) => {
const matchesSearch = normalizedSearch.length === 0
|| conversation.username.toLowerCase().includes(normalizedSearch)
|| conversation.lastMessageSnippet.toLowerCase().includes(normalizedSearch);
return matchesSearch && matchesFilter(conversation, activeFilters.filter);
})
.sort((left, right) => right.lastMessageTime - left.lastMessageTime);
}, [activeFilters, conversationsQuery.data]);
function selectConversation(id: string | null) {
@@ -144,6 +162,7 @@ export function ConversationsPage({ selectedConversationId, onSelectedConversati
conversation={conversation}
isSelected={ownedConversationId(conversation) === activeConversationId}
key={ownedConversationId(conversation)}
now={now}
onSelect={() => selectConversation(ownedConversationId(conversation))}
/>
))}