fix: chat UI

This commit is contained in:
gosangam
2026-08-29 17:30:32 +05:30
parent 3e6a1c6f39
commit 011b5bba91
11 changed files with 250 additions and 76 deletions
@@ -50,6 +50,15 @@ const EVENTS: {
label: "Sādhana reminders",
description: "Daily nudges to chant your rounds and share a verse.",
},
{
key: "messages",
label: "Messages",
description: "New direct messages and group chats.",
// Push only. In-app delivery for chat is the inbox itself — a message
// highlights its row rather than entering the notification tray — and
// there is no such thing as an emailed chat message.
channels: ["push"],
},
{
key: "community",
label: "Community",
@@ -34,6 +34,7 @@ import { useAuth } from "@/components/auth/auth-provider";
import { channelAuthorizer } from "@/lib/realtime";
import { feed, notifications } from "@/lib/social";
import { sadhna } from "@/lib/sadhna";
import { chat } from "@/lib/chat";
type NotificationsContextValue = {
/** Live unread count for the header bell. */
@@ -51,6 +52,11 @@ type NotificationsContextValue = {
* event name and is surfaced here rather than bumping `unread`.
*/
lastChatActivity: ChatActivity | null;
/** Threads holding unread messages — the Chats nav badge. Muted threads are
* excluded by the BFF, so muting a busy group clears the badge. */
chatUnread: number;
/** Re-read the authoritative chat count (after opening a thread). */
refreshChatUnread: () => void;
};
/** Payload of the BFF's `chat_activity` event. */
@@ -108,6 +114,7 @@ export function NotificationsProvider({
}) {
const { user } = useAuth();
const [unread, setUnread] = useState(0);
const [chatUnread, setChatUnread] = useState(0);
const [lastEventAt, setLastEventAt] = useState(0);
const [lastChatActivity, setLastChatActivity] = useState<ChatActivity | null>(
null,
@@ -124,12 +131,22 @@ export function NotificationsProvider({
const clearUnread = useCallback(() => setUnread(0), []);
const refreshChatUnread = useCallback(() => {
chat
.unreadCount()
.then(setChatUnread)
.catch(() => {
/* badge is non-critical */
});
}, []);
// Seed the count whenever the signed-in user changes (async — no
// synchronous setState in the effect body).
useEffect(() => {
if (!user?.id) return;
refreshUnread();
}, [user?.id, refreshUnread]);
refreshChatUnread();
}, [user?.id, refreshUnread, refreshChatUnread]);
// Report the browser's timezone (once per signed-in session) so sādhana
// reminders fire at the user's local clock time. Best-effort.
@@ -158,7 +175,7 @@ export function NotificationsProvider({
};
window.addEventListener("pagehide", onLeave);
return () => window.removeEventListener("pagehide", onLeave);
}, [userId]);
}, [userId, refreshChatUnread]);
// Open the realtime connection while signed in.
useEffect(() => {
@@ -197,13 +214,24 @@ export function NotificationsProvider({
channel.bind("pusher:subscription_error", (status: unknown) => {
console.warn("notifications: realtime subscription failed", status);
});
// Chat activity: no toast, no unread bump, nothing in the tray. The
// inbox subscribes to this to highlight the affected row.
// Chat activity: no toast, nothing in the tray. The inbox subscribes to
// this to highlight the affected row, and the nav badge re-reads its
// count — refetched rather than incremented, because the badge counts
// THREADS: a second message in a thread already counted must not bump
// it, and the sender's own message must not either.
channel.bind(
"chat_activity",
(data: Omit<ChatActivity, "at">) => {
if (!data?.conversation_id) return;
setLastChatActivity({ ...data, at: Date.now() });
refreshChatUnread();
// Ack delivery here rather than in the thread, so "delivered" means
// "reached their device" rather than "they happened to have the chat
// open". This event only reaches non-senders, so nobody acks their
// own message.
if (data.seq) {
chat.markDelivered(data.conversation_id, data.seq).catch(() => {});
}
},
);
channel.bind("notification", (data: IncomingEvent) => {
@@ -232,7 +260,7 @@ export function NotificationsProvider({
cancelled = true;
cleanup?.();
};
}, [userId]);
}, [userId, refreshChatUnread]);
return (
<NotificationsContext.Provider
@@ -242,6 +270,8 @@ export function NotificationsProvider({
refreshUnread,
clearUnread,
lastChatActivity,
chatUnread,
refreshChatUnread,
}}
>
{children}