Files
lynkeduppro-crm/src/lib/inbox-api.ts
T
maaz519 49b04e9710 feat: Messenger + Inbox on the AppShell data door + live IIOS socket
New "Communication" area in the dashboard, built on the existing appshell-sdk wiring
(useQuery/sdk.command) like Team, with the IIOS MessageSocket for live streaming.

- messenger-api.ts / inbox-api.ts: mock (demo) + live (crm.messenger.* / crm.inbox.*)
  behind one interface, switched by isShellConfigured().
- messenger-socket.tsx: MessengerSocketProvider — one IIOS MessageSocket per panel
  (openThread history + live on("message") + send). REST 4s poll is the automatic
  fallback when the socket isn't connected.
- messenger.tsx: conversation list ⇄ thread + composer + new-chat people picker
  (DM 1 person / group 2+); inbox.tsx: filterable feed with Done/Snooze/Archive.
- sidebar: Communication group (Messenger + Inbox, always-visible); dashboard: panel switch.
- .npmrc: add the @insignia Gitea registry for @insignia/iios-kernel-client.

Works on mock immediately; goes live once NEXT_PUBLIC_SUPABASE_URL + the BFF + be-crm are set.
tsc clean; next build passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:45:47 +05:30

58 lines
2.9 KiB
TypeScript

"use client";
// Inbox data layer. The inbox is a personalized work/awareness feed IIOS projects from events
// (NEEDS_REPLY, MENTION, …). The CRM lists it and transitions item state; items are never created
// here. Mock when the Shell isn't configured; live via the be-crm data door (crm.inbox.*) otherwise.
//
// Live contract (be-crm):
// query crm.inbox.list { state? } -> InboxItem[]
// cmd crm.inbox.transition { id, state, reason? } -> InboxItem
import { useCallback, useMemo, useState } from "react";
import { useAppShell, useQuery } from "@abe-kap/appshell-sdk/react";
import { isShellConfigured } from "./appshell";
export type InboxState = "OPEN" | "SNOOZED" | "DONE" | "ARCHIVED" | "CANCELLED" | "STALE";
export interface UiInboxItem {
id: string; kind: string; state: InboxState; title: string; summary?: string;
priority: string; threadId?: string; createdAt: string;
}
const SHELL = isShellConfigured();
export interface InboxData {
live: boolean; loading: boolean; error: string | null;
items: UiInboxItem[];
transition: (id: string, state: InboxState) => Promise<void>;
refetch: () => void;
}
export function useInboxData(state?: InboxState): InboxData {
return SHELL ? useLiveInbox(state) : useMockInbox(state);
}
function useLiveInbox(state?: InboxState): InboxData {
const { sdk } = useAppShell();
const q = useQuery<UiInboxItem[]>("crm.inbox.list", state ? { state } : {});
const transition = useCallback(async (id: string, next: InboxState) => {
await sdk.command("crm.inbox.transition", { id, state: next });
q.refetch();
}, [sdk, q]);
return { live: true, loading: q.loading, error: q.error?.message ?? null, items: q.data ?? [], transition, refetch: q.refetch };
}
const MOCK_ITEMS: UiInboxItem[] = [
{ id: "in_1", kind: "MENTION", state: "OPEN", title: "Sofia mentioned you", summary: "@you — can you confirm the Henderson scope?", priority: "HIGH", threadId: "th_mock_1", createdAt: new Date().toISOString() },
{ id: "in_2", kind: "NEEDS_REPLY", state: "OPEN", title: "Reply needed — Storm response", summary: "Dan: Crew is rolling out at 7.", priority: "MEDIUM", threadId: "th_mock_2", createdAt: new Date().toISOString() },
{ id: "in_3", kind: "SUPPORT_UPDATE", state: "OPEN", title: "Ticket TK-204 updated", summary: "Customer replied on the roof-leak case.", priority: "LOW", createdAt: new Date().toISOString() },
];
function useMockInbox(state?: InboxState): InboxData {
const [items, setItems] = useState<UiInboxItem[]>(MOCK_ITEMS);
const filtered = useMemo(() => (state ? items.filter((i) => i.state === state) : items), [items, state]);
const transition = useCallback(async (id: string, next: InboxState) => {
setItems((l) => l.map((i) => (i.id === id ? { ...i, state: next } : i)));
}, []);
return { live: false, loading: false, error: null, items: filtered, transition, refetch: () => {} };
}