Improve messaging and dashboard reliability
This commit is contained in:
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))}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in new issue
Block a user