forked from Goutam/lynkeduppro-crm
feat(dashboard): live notifications phase 1 — presence, live unread, in-app toasts
Shared RealtimeProvider opens ONE dashboard-wide IIOS message socket reused by the messenger tab and the new NotificationCenter. CrmMessagingAdapter gains setFocus (drives presence/push suppression) and subscribeActivity (joins all of the caller's threads, fans out incoming messages). NotificationCenter shows a clickable toast on new messages when you're not on the messenger tab and deep-links to the conversation. Pulls @insignia/iios-messaging-ui@0.1.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
// App-wide in-app notifications. Uses the shared dashboard socket to watch activity across ALL of
|
||||
// the user's threads (via the adapter's subscribeActivity) and shows a clickable toast when a new
|
||||
// message arrives — unless you're already on the Messenger tab (you'd see it live there). Clicking
|
||||
// deep-links to the conversation. Renders nothing.
|
||||
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useAppShell, useAuth } from "@abe-kap/appshell-sdk/react";
|
||||
import type { MessagingAdapter } from "@insignia/iios-messaging-ui";
|
||||
import { isShellConfigured } from "@/lib/appshell";
|
||||
import { useRealtime } from "@/lib/realtime";
|
||||
import { CrmMessagingAdapter, type DataDoor } from "@/lib/crm-messaging-adapter";
|
||||
import { useToast } from "./ui";
|
||||
|
||||
const SHELL = isShellConfigured();
|
||||
|
||||
export function NotificationCenter({ active, onNavigate }: { active: string; onNavigate: (surface: "messenger" | "inbox", threadId: string) => void }) {
|
||||
const socket = useRealtime();
|
||||
const { sdk } = useAppShell();
|
||||
const { user } = useAuth();
|
||||
const toast = useToast();
|
||||
const me = user?.id;
|
||||
|
||||
// Keep the current tab readable inside the (stable) subscription callback.
|
||||
const activeRef = useRef(active);
|
||||
activeRef.current = active;
|
||||
|
||||
const adapter = useMemo<MessagingAdapter | null>(
|
||||
() => (me && socket ? new CrmMessagingAdapter(sdk as unknown as DataDoor, me, socket) : null),
|
||||
[sdk, me, socket],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!SHELL || !adapter?.subscribeActivity) return;
|
||||
return adapter.subscribeActivity(({ threadId, message }) => {
|
||||
if (message.actorId === me) return; // never notify me about my own message
|
||||
if (activeRef.current === "messenger") return; // already watching chat live
|
||||
toast.push({
|
||||
tone: "info",
|
||||
title: "New message",
|
||||
desc: message.text?.slice(0, 90) || "You have a new message",
|
||||
onClick: () => onNavigate("messenger", threadId),
|
||||
});
|
||||
});
|
||||
}, [adapter, me, toast, onNavigate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user