feat(chat): add presence metadata and improve web chat core
Some checks failed
CI / test (push) Failing after 22s
Some checks failed
CI / test (push) Failing after 22s
- add user last_seen_at with alembic migration and persist on realtime disconnect - extend chat serialization with private online/last_seen, group members/online, channel subscribers - add Redis batch presence lookup helper - update web chat list/header to display status counters and last-seen labels - improve delivery receipt handling using last_delivered/last_read boundaries - include chat info panel and related API/type updates
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { ChatList } from "../components/ChatList";
|
||||
import { ChatInfoPanel } from "../components/ChatInfoPanel";
|
||||
import { MessageComposer } from "../components/MessageComposer";
|
||||
import { MessageList } from "../components/MessageList";
|
||||
import { useRealtime } from "../hooks/useRealtime";
|
||||
import { useAuthStore } from "../store/authStore";
|
||||
import { useChatStore } from "../store/chatStore";
|
||||
import { useState } from "react";
|
||||
|
||||
export function ChatsPage() {
|
||||
const me = useAuthStore((s) => s.me);
|
||||
@@ -15,6 +17,7 @@ export function ChatsPage() {
|
||||
const setActiveChatId = useChatStore((s) => s.setActiveChatId);
|
||||
const loadMessages = useChatStore((s) => s.loadMessages);
|
||||
const activeChat = chats.find((chat) => chat.id === activeChatId);
|
||||
const [infoOpen, setInfoOpen] = useState(false);
|
||||
|
||||
useRealtime();
|
||||
|
||||
@@ -41,9 +44,14 @@ export function ChatsPage() {
|
||||
<button className="rounded-full bg-slate-700/70 px-2 py-1 text-xs md:hidden" onClick={() => setActiveChatId(null)}>
|
||||
Back
|
||||
</button>
|
||||
{activeChatId ? (
|
||||
<button className="rounded-full bg-slate-700/70 px-2 py-1 text-xs" onClick={() => setInfoOpen(true)}>
|
||||
Info
|
||||
</button>
|
||||
) : null}
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold">{activeChat?.display_title || activeChat?.title || me?.name || `@${me?.username}`}</p>
|
||||
<p className="truncate text-xs text-slate-300/80">{activeChat ? activeChat.type : "Select a chat"}</p>
|
||||
<p className="truncate text-xs text-slate-300/80">{activeChat ? headerMetaLabel(activeChat) : "Select a chat"}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="rounded-full bg-slate-700/70 px-3 py-1.5 text-xs font-semibold hover:bg-slate-600/80" onClick={logout}>
|
||||
@@ -62,6 +70,50 @@ export function ChatsPage() {
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<ChatInfoPanel chatId={activeChatId} open={infoOpen} onClose={() => setInfoOpen(false)} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function headerMetaLabel(chat: {
|
||||
type: "private" | "group" | "channel";
|
||||
is_saved?: boolean;
|
||||
counterpart_is_online?: boolean | null;
|
||||
counterpart_last_seen_at?: string | null;
|
||||
members_count?: number | null;
|
||||
online_count?: number | null;
|
||||
subscribers_count?: number | null;
|
||||
}): string {
|
||||
if (chat.is_saved) {
|
||||
return "Saved Messages";
|
||||
}
|
||||
if (chat.type === "private") {
|
||||
if (chat.counterpart_is_online) {
|
||||
return "online";
|
||||
}
|
||||
if (chat.counterpart_last_seen_at) {
|
||||
return `last seen ${formatLastSeen(chat.counterpart_last_seen_at)}`;
|
||||
}
|
||||
return "offline";
|
||||
}
|
||||
if (chat.type === "group") {
|
||||
const members = chat.members_count ?? 0;
|
||||
const online = chat.online_count ?? 0;
|
||||
return `${members} members, ${online} online`;
|
||||
}
|
||||
const subscribers = chat.subscribers_count ?? chat.members_count ?? 0;
|
||||
return `${subscribers} subscribers`;
|
||||
}
|
||||
|
||||
function formatLastSeen(value: string): string {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return "recently";
|
||||
}
|
||||
return date.toLocaleString(undefined, {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user