@burdenoff/microfe-conversations 2026.525.3 → 2026.527.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/conversations/ConversationsUserRoutes.js +4 -3
  2. package/dist/conversations/ConversationsUserRoutes.js.map +1 -1
  3. package/dist/conversations/components/EmptyState.js +27 -0
  4. package/dist/conversations/components/EmptyState.js.map +1 -0
  5. package/dist/conversations/components/LazyPageBoundary.js +26 -42
  6. package/dist/conversations/components/LazyPageBoundary.js.map +1 -1
  7. package/dist/conversations/components/MessageBubble.js +3 -1
  8. package/dist/conversations/components/MessageBubble.js.map +1 -1
  9. package/dist/conversations/components/PageLayout.js +3 -39
  10. package/dist/conversations/components/PageLayout.js.map +1 -1
  11. package/dist/conversations/components/PageSection.js +25 -0
  12. package/dist/conversations/components/PageSection.js.map +1 -0
  13. package/dist/conversations/components/PermissionGuard.js +1 -1
  14. package/dist/conversations/components/PermissionGuard.js.map +1 -1
  15. package/dist/conversations/components/lazyRetry.js +23 -0
  16. package/dist/conversations/components/lazyRetry.js.map +1 -0
  17. package/dist/conversations/constants/permissions.js +0 -1
  18. package/dist/conversations/constants/permissions.js.map +1 -1
  19. package/dist/conversations/hooks/useMessageQueries.js.map +1 -1
  20. package/dist/conversations/hooks/useMessageSubscriptions.js.map +1 -1
  21. package/dist/conversations/pages/AdminConversationsLandingPage.js +6 -6
  22. package/dist/conversations/pages/AdminConversationsLandingPage.js.map +1 -1
  23. package/dist/conversations/pages/ConversationAdminPages.js +56 -49
  24. package/dist/conversations/pages/ConversationAdminPages.js.map +1 -1
  25. package/dist/conversations/pages/ConversationDetailPage.js +3 -2
  26. package/dist/conversations/pages/ConversationDetailPage.js.map +1 -1
  27. package/dist/conversations/pages/ConversationListPage.js +133 -136
  28. package/dist/conversations/pages/ConversationListPage.js.map +1 -1
  29. package/dist/conversations/pages/ConversationsLandingPage.js +50 -57
  30. package/dist/conversations/pages/ConversationsLandingPage.js.map +1 -1
  31. package/dist/conversations/pages/ConversationsSettingsPage.js +8 -5
  32. package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
  33. package/dist/conversations/pages/NewConversationPage.js +87 -88
  34. package/dist/conversations/pages/NewConversationPage.js.map +1 -1
  35. package/dist/conversations/providers/ConversationsProvider.js +1 -1
  36. package/dist/conversations/providers/ConversationsProvider.js.map +1 -1
  37. package/dist/conversations/utils/mappers.js.map +1 -1
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/conversations/constants/permissions.ts"],"sourcesContent":["/**\n * RBAC permission constants for the Conversations microfrontend.\n *\n * Mirrors the @rbac directives in wspace-conversations-svc\n * (12 resources × 17+ actions, all scopeType: \"wspace\").\n *\n * Three records are exported:\n * - CONVERSATIONS_PERMISSIONS — master record (union of everything)\n * - CONVERSATIONS_USER_PERMISSIONS — curated subset safe for user-facing UI\n * - CONVERSATIONS_ADMIN_PERMISSIONS — curated subset for admin / governance UI\n *\n * Three derived union types are exported alongside:\n * - ConversationsPermission\n * - ConversationsUserPermission\n * - ConversationsAdminPermission\n *\n * The USER / ADMIN split is a UI-level convention — the backend gateway\n * enforces the real rule via @rbac directives and resolver-level ownership\n * checks. See `workspaces-specs/modules/conversations/ADMIN-USER-SPLIT.md`\n * (ownership rule + naming-convention note) and `RBAC.md` for the canonical\n * role-to-permission mapping.\n */\n\nexport const CONVERSATIONS_PERMISSIONS = {\n // ────────────── conversation resource ──────────────\n CONVERSATION_READ: \"conversation:read\",\n CONVERSATION_LIST: \"conversation:list\",\n CONVERSATION_CREATE: \"conversation:create\",\n CONVERSATION_UPDATE: \"conversation:update\",\n CONVERSATION_DELETE: \"conversation:delete\",\n CONVERSATION_ARCHIVE: \"conversation:archive\",\n CONVERSATION_UNARCHIVE: \"conversation:unarchive\",\n\n // ────────────── message resource ──────────────\n MESSAGE_READ: \"message:read\",\n MESSAGE_CREATE: \"message:create\",\n MESSAGE_UPDATE: \"message:update\",\n MESSAGE_DELETE: \"message:delete\",\n MESSAGE_REACT: \"message:react\",\n MESSAGE_PIN: \"message:pin\",\n MESSAGE_MODERATE: \"message:moderate\",\n MESSAGE_REPORT: \"message:report\",\n MESSAGE_VOTE: \"message:vote\",\n\n // ────────────── message-draft resource ──────────────\n // Note: no MESSAGE_DRAFT_UPDATE — drafts are create-and-replace\n // (the backend schema exposes read/create/delete only; saveDraft\n // replaces any existing draft for the conversation).\n MESSAGE_DRAFT_READ: \"message-draft:read\",\n MESSAGE_DRAFT_CREATE: \"message-draft:create\",\n MESSAGE_DRAFT_DELETE: \"message-draft:delete\",\n\n // ────────────── participant resource ──────────────\n PARTICIPANT_READ: \"participant:read\",\n PARTICIPANT_ADD: \"participant:add\",\n PARTICIPANT_REMOVE: \"participant:remove\",\n PARTICIPANT_UPDATE: \"participant:update\",\n PARTICIPANT_LEAVE: \"participant:leave\",\n\n // ────────────── conversation-invite resource ──────────────\n CONVERSATION_INVITE_READ: \"conversation-invite:read\",\n CONVERSATION_INVITE_CREATE: \"conversation-invite:create\",\n CONVERSATION_INVITE_JOIN: \"conversation-invite:join\",\n\n // ────────────── call resource ──────────────\n // CALL_UPDATE is participant-self-service: it backs `updateCallParticipant`\n // (muted / video / screen share state for the calling participant). Call\n // host/moderator-only actions — ending or recording — live in\n // CALL_END / CALL_RECORD and belong in the admin subset.\n CALL_READ: \"call:read\",\n CALL_CREATE: \"call:create\",\n CALL_JOIN: \"call:join\",\n CALL_LEAVE: \"call:leave\",\n CALL_END: \"call:end\",\n CALL_UPDATE: \"call:update\",\n CALL_RECORD: \"call:record\",\n\n // ────────────── app-installation resource ──────────────\n APP_INSTALLATION_READ: \"app-installation:read\",\n APP_INSTALLATION_CREATE: \"app-installation:create\",\n APP_INSTALLATION_UPDATE: \"app-installation:update\",\n APP_INSTALLATION_DELETE: \"app-installation:delete\",\n APP_INSTALLATION_EXECUTE: \"app-installation:execute\",\n\n // ────────────── presence resource ──────────────\n PRESENCE_READ: \"presence:read\",\n PRESENCE_UPDATE: \"presence:update\",\n\n // ────────────── entity-comment resource ──────────────\n ENTITY_COMMENT_READ: \"entity-comment:read\",\n ENTITY_COMMENT_CREATE: \"entity-comment:create\",\n ENTITY_COMMENT_UPDATE: \"entity-comment:update\",\n ENTITY_COMMENT_DELETE: \"entity-comment:delete\",\n ENTITY_COMMENT_ASSIGN: \"entity-comment:assign\",\n ENTITY_COMMENT_EXPORT: \"entity-comment:export\",\n\n // ────────────── comment-attachment resource ──────────────\n COMMENT_ATTACHMENT_READ: \"comment-attachment:read\",\n COMMENT_ATTACHMENT_CREATE: \"comment-attachment:create\",\n COMMENT_ATTACHMENT_UPDATE: \"comment-attachment:update\",\n COMMENT_ATTACHMENT_DELETE: \"comment-attachment:delete\",\n\n // ────────────── comment-tag resource ──────────────\n COMMENT_TAG_READ: \"comment-tag:read\",\n COMMENT_TAG_ASSIGN: \"comment-tag:assign\",\n COMMENT_TAG_UPDATE: \"comment-tag:update\",\n COMMENT_TAG_DELETE: \"comment-tag:delete\",\n\n // ────────────── scheduled-comment resource ──────────────\n SCHEDULED_COMMENT_READ: \"scheduled-comment:read\",\n SCHEDULED_COMMENT_CREATE: \"scheduled-comment:create\",\n SCHEDULED_COMMENT_UPDATE: \"scheduled-comment:update\",\n SCHEDULED_COMMENT_DELETE: \"scheduled-comment:delete\",\n SCHEDULED_COMMENT_EXECUTE: \"scheduled-comment:execute\",\n} as const;\n\n/**\n * USER — curated subset of permissions safe to reference from\n * user-facing UI (inbox, chat detail, personal settings, etc).\n *\n * Users retain full CRUDL on their own conversations / messages.\n * The backend additionally enforces ownership at runtime — see the\n * \"Ownership rule\" in `workspaces-specs/modules/conversations/ADMIN-USER-SPLIT.md`:\n * these constants authorize the action on actor-owned resources only;\n * the same action on another user's resource requires an ADMIN permission.\n *\n * Per `RBAC.md`: any member can `message:report` and — when the conversation\n * setting `allowInviteLinks=true` — any member can `conversation-invite:create`.\n * Both constants therefore live in the USER subset. The shell additionally\n * gates invite creation on the conversation setting at render time.\n */\nexport const CONVERSATIONS_USER_PERMISSIONS = {\n CONVERSATION_READ: CONVERSATIONS_PERMISSIONS.CONVERSATION_READ,\n CONVERSATION_LIST: CONVERSATIONS_PERMISSIONS.CONVERSATION_LIST,\n CONVERSATION_CREATE: CONVERSATIONS_PERMISSIONS.CONVERSATION_CREATE,\n CONVERSATION_UPDATE: CONVERSATIONS_PERMISSIONS.CONVERSATION_UPDATE,\n\n MESSAGE_READ: CONVERSATIONS_PERMISSIONS.MESSAGE_READ,\n MESSAGE_CREATE: CONVERSATIONS_PERMISSIONS.MESSAGE_CREATE,\n MESSAGE_UPDATE: CONVERSATIONS_PERMISSIONS.MESSAGE_UPDATE,\n MESSAGE_DELETE: CONVERSATIONS_PERMISSIONS.MESSAGE_DELETE,\n MESSAGE_REACT: CONVERSATIONS_PERMISSIONS.MESSAGE_REACT,\n MESSAGE_REPORT: CONVERSATIONS_PERMISSIONS.MESSAGE_REPORT,\n MESSAGE_VOTE: CONVERSATIONS_PERMISSIONS.MESSAGE_VOTE,\n\n MESSAGE_DRAFT_READ: CONVERSATIONS_PERMISSIONS.MESSAGE_DRAFT_READ,\n MESSAGE_DRAFT_CREATE: CONVERSATIONS_PERMISSIONS.MESSAGE_DRAFT_CREATE,\n MESSAGE_DRAFT_DELETE: CONVERSATIONS_PERMISSIONS.MESSAGE_DRAFT_DELETE,\n\n PARTICIPANT_READ: CONVERSATIONS_PERMISSIONS.PARTICIPANT_READ,\n PARTICIPANT_LEAVE: CONVERSATIONS_PERMISSIONS.PARTICIPANT_LEAVE,\n\n CONVERSATION_INVITE_READ: CONVERSATIONS_PERMISSIONS.CONVERSATION_INVITE_READ,\n CONVERSATION_INVITE_CREATE:\n CONVERSATIONS_PERMISSIONS.CONVERSATION_INVITE_CREATE,\n CONVERSATION_INVITE_JOIN: CONVERSATIONS_PERMISSIONS.CONVERSATION_INVITE_JOIN,\n\n CALL_READ: CONVERSATIONS_PERMISSIONS.CALL_READ,\n CALL_CREATE: CONVERSATIONS_PERMISSIONS.CALL_CREATE,\n CALL_JOIN: CONVERSATIONS_PERMISSIONS.CALL_JOIN,\n CALL_LEAVE: CONVERSATIONS_PERMISSIONS.CALL_LEAVE,\n // CALL_UPDATE backs `updateCallParticipant` (per-participant self-service\n // state like mute / video / screen share). Host-only call lifecycle actions\n // (end, record) are ADMIN and live in CONVERSATIONS_ADMIN_PERMISSIONS.\n CALL_UPDATE: CONVERSATIONS_PERMISSIONS.CALL_UPDATE,\n\n APP_INSTALLATION_READ: CONVERSATIONS_PERMISSIONS.APP_INSTALLATION_READ,\n\n PRESENCE_READ: CONVERSATIONS_PERMISSIONS.PRESENCE_READ,\n PRESENCE_UPDATE: CONVERSATIONS_PERMISSIONS.PRESENCE_UPDATE,\n\n ENTITY_COMMENT_READ: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_READ,\n ENTITY_COMMENT_CREATE: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_CREATE,\n ENTITY_COMMENT_UPDATE: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_UPDATE,\n ENTITY_COMMENT_DELETE: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_DELETE,\n\n COMMENT_ATTACHMENT_READ: CONVERSATIONS_PERMISSIONS.COMMENT_ATTACHMENT_READ,\n COMMENT_ATTACHMENT_CREATE:\n CONVERSATIONS_PERMISSIONS.COMMENT_ATTACHMENT_CREATE,\n COMMENT_ATTACHMENT_UPDATE:\n CONVERSATIONS_PERMISSIONS.COMMENT_ATTACHMENT_UPDATE,\n COMMENT_ATTACHMENT_DELETE:\n CONVERSATIONS_PERMISSIONS.COMMENT_ATTACHMENT_DELETE,\n\n COMMENT_TAG_READ: CONVERSATIONS_PERMISSIONS.COMMENT_TAG_READ,\n\n SCHEDULED_COMMENT_READ: CONVERSATIONS_PERMISSIONS.SCHEDULED_COMMENT_READ,\n SCHEDULED_COMMENT_CREATE: CONVERSATIONS_PERMISSIONS.SCHEDULED_COMMENT_CREATE,\n SCHEDULED_COMMENT_UPDATE: CONVERSATIONS_PERMISSIONS.SCHEDULED_COMMENT_UPDATE,\n SCHEDULED_COMMENT_DELETE: CONVERSATIONS_PERMISSIONS.SCHEDULED_COMMENT_DELETE,\n} as const;\n\n/**\n * ADMIN / MODERATOR governance — curated subset of permissions\n * that should only be referenced from admin surfaces\n * (admin-app ConversationsAdminRoot). Gates bulk operations,\n * moderation, participant administration, and workspace-wide\n * lifecycle actions.\n */\nexport const CONVERSATIONS_ADMIN_PERMISSIONS = {\n CONVERSATION_DELETE: CONVERSATIONS_PERMISSIONS.CONVERSATION_DELETE,\n CONVERSATION_ARCHIVE: CONVERSATIONS_PERMISSIONS.CONVERSATION_ARCHIVE,\n CONVERSATION_UNARCHIVE: CONVERSATIONS_PERMISSIONS.CONVERSATION_UNARCHIVE,\n\n MESSAGE_PIN: CONVERSATIONS_PERMISSIONS.MESSAGE_PIN,\n MESSAGE_MODERATE: CONVERSATIONS_PERMISSIONS.MESSAGE_MODERATE,\n\n PARTICIPANT_ADD: CONVERSATIONS_PERMISSIONS.PARTICIPANT_ADD,\n PARTICIPANT_REMOVE: CONVERSATIONS_PERMISSIONS.PARTICIPANT_REMOVE,\n PARTICIPANT_UPDATE: CONVERSATIONS_PERMISSIONS.PARTICIPANT_UPDATE,\n\n CALL_END: CONVERSATIONS_PERMISSIONS.CALL_END,\n CALL_RECORD: CONVERSATIONS_PERMISSIONS.CALL_RECORD,\n\n APP_INSTALLATION_CREATE: CONVERSATIONS_PERMISSIONS.APP_INSTALLATION_CREATE,\n APP_INSTALLATION_UPDATE: CONVERSATIONS_PERMISSIONS.APP_INSTALLATION_UPDATE,\n APP_INSTALLATION_DELETE: CONVERSATIONS_PERMISSIONS.APP_INSTALLATION_DELETE,\n APP_INSTALLATION_EXECUTE: CONVERSATIONS_PERMISSIONS.APP_INSTALLATION_EXECUTE,\n\n ENTITY_COMMENT_ASSIGN: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_ASSIGN,\n ENTITY_COMMENT_EXPORT: CONVERSATIONS_PERMISSIONS.ENTITY_COMMENT_EXPORT,\n\n COMMENT_TAG_ASSIGN: CONVERSATIONS_PERMISSIONS.COMMENT_TAG_ASSIGN,\n COMMENT_TAG_UPDATE: CONVERSATIONS_PERMISSIONS.COMMENT_TAG_UPDATE,\n COMMENT_TAG_DELETE: CONVERSATIONS_PERMISSIONS.COMMENT_TAG_DELETE,\n\n SCHEDULED_COMMENT_EXECUTE:\n CONVERSATIONS_PERMISSIONS.SCHEDULED_COMMENT_EXECUTE,\n} as const;\n\nexport type ConversationsPermission =\n (typeof CONVERSATIONS_PERMISSIONS)[keyof typeof CONVERSATIONS_PERMISSIONS];\n\nexport type ConversationsUserPermission =\n (typeof CONVERSATIONS_USER_PERMISSIONS)[keyof typeof CONVERSATIONS_USER_PERMISSIONS];\n\nexport type ConversationsAdminPermission =\n (typeof CONVERSATIONS_ADMIN_PERMISSIONS)[keyof typeof CONVERSATIONS_ADMIN_PERMISSIONS];\n"],"mappings":";AAuBA,IAAa,IAA4B;CAEvC,mBAAmB;CACnB,mBAAmB;CACnB,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACrB,sBAAsB;CACtB,wBAAwB;CAGxB,cAAc;CACd,gBAAgB;CAChB,gBAAgB;CAChB,gBAAgB;CAChB,eAAe;CACf,aAAa;CACb,kBAAkB;CAClB,gBAAgB;CAChB,cAAc;CAMd,oBAAoB;CACpB,sBAAsB;CACtB,sBAAsB;CAGtB,kBAAkB;CAClB,iBAAiB;CACjB,oBAAoB;CACpB,oBAAoB;CACpB,mBAAmB;CAGnB,0BAA0B;CAC1B,4BAA4B;CAC5B,0BAA0B;CAO1B,WAAW;CACX,aAAa;CACb,WAAW;CACX,YAAY;CACZ,UAAU;CACV,aAAa;CACb,aAAa;CAGb,uBAAuB;CACvB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,0BAA0B;CAG1B,eAAe;CACf,iBAAiB;CAGjB,qBAAqB;CACrB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CAGvB,yBAAyB;CACzB,2BAA2B;CAC3B,2BAA2B;CAC3B,2BAA2B;CAG3B,kBAAkB;CAClB,oBAAoB;CACpB,oBAAoB;CACpB,oBAAoB;CAGpB,wBAAwB;CACxB,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;CAC1B,2BAA2B;CAC5B;AAkBoB,EAA0B,mBAC1B,EAA0B,mBACxB,EAA0B,qBAC1B,EAA0B,qBAEjC,EAA0B,cACxB,EAA0B,gBAC1B,EAA0B,gBAC1B,EAA0B,gBAC3B,EAA0B,eACzB,EAA0B,gBAC5B,EAA0B,cAEpB,EAA0B,oBACxB,EAA0B,sBAC1B,EAA0B,sBAE9B,EAA0B,kBACzB,EAA0B,mBAEnB,EAA0B,0BAElD,EAA0B,4BACF,EAA0B,0BAEzC,EAA0B,WACxB,EAA0B,aAC5B,EAA0B,WACzB,EAA0B,YAIzB,EAA0B,aAEhB,EAA0B,uBAElC,EAA0B,eACxB,EAA0B,iBAEtB,EAA0B,qBACxB,EAA0B,uBAC1B,EAA0B,uBAC1B,EAA0B,uBAExB,EAA0B,yBAEjD,EAA0B,2BAE1B,EAA0B,2BAE1B,EAA0B,2BAEV,EAA0B,kBAEpB,EAA0B,wBACxB,EAA0B,0BAC1B,EAA0B,0BAC1B,EAA0B,0BAW/B,EAA0B,qBACzB,EAA0B,sBACxB,EAA0B,wBAErC,EAA0B,aACrB,EAA0B,kBAE3B,EAA0B,iBACvB,EAA0B,oBAC1B,EAA0B,oBAEpC,EAA0B,UACvB,EAA0B,aAEd,EAA0B,yBAC1B,EAA0B,yBAC1B,EAA0B,yBACzB,EAA0B,0BAE7B,EAA0B,uBAC1B,EAA0B,uBAE7B,EAA0B,oBAC1B,EAA0B,oBAC1B,EAA0B,oBAG5C,EAA0B"}
1
+ {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/conversations/constants/permissions.ts"],"sourcesContent":["/**\n * RBAC permission constants for the Conversations microfrontend.\n *\n * Mirrors the @rbac directives in wspace-conversations-svc\n * (12 resources × 17+ actions, all scopeType: \"wspace\").\n *\n * Exports the master `CONVERSATIONS_PERMISSIONS` record and the derived\n * `ConversationsPermission` union. The USER / ADMIN subsets previously\n * declared here were never imported by any consumer — the gateway enforces\n * the real rule via @rbac directives and resolver-level ownership checks.\n * See `workspaces-specs/modules/conversations/ADMIN-USER-SPLIT.md`\n * (ownership rule + naming-convention note) and `RBAC.md` for the canonical\n * role-to-permission mapping.\n */\n\nexport const CONVERSATIONS_PERMISSIONS = {\n // ────────────── conversation resource ──────────────\n CONVERSATION_READ: \"conversation:read\",\n CONVERSATION_LIST: \"conversation:list\",\n CONVERSATION_CREATE: \"conversation:create\",\n CONVERSATION_UPDATE: \"conversation:update\",\n CONVERSATION_DELETE: \"conversation:delete\",\n CONVERSATION_ARCHIVE: \"conversation:archive\",\n CONVERSATION_UNARCHIVE: \"conversation:unarchive\",\n\n // ────────────── message resource ──────────────\n MESSAGE_READ: \"message:read\",\n MESSAGE_CREATE: \"message:create\",\n MESSAGE_UPDATE: \"message:update\",\n MESSAGE_DELETE: \"message:delete\",\n MESSAGE_REACT: \"message:react\",\n MESSAGE_PIN: \"message:pin\",\n MESSAGE_MODERATE: \"message:moderate\",\n MESSAGE_REPORT: \"message:report\",\n MESSAGE_VOTE: \"message:vote\",\n\n // ────────────── message-draft resource ──────────────\n // Note: no MESSAGE_DRAFT_UPDATE — drafts are create-and-replace\n // (the backend schema exposes read/create/delete only; saveDraft\n // replaces any existing draft for the conversation).\n MESSAGE_DRAFT_READ: \"message-draft:read\",\n MESSAGE_DRAFT_CREATE: \"message-draft:create\",\n MESSAGE_DRAFT_DELETE: \"message-draft:delete\",\n\n // ────────────── participant resource ──────────────\n PARTICIPANT_READ: \"participant:read\",\n PARTICIPANT_ADD: \"participant:add\",\n PARTICIPANT_REMOVE: \"participant:remove\",\n PARTICIPANT_UPDATE: \"participant:update\",\n PARTICIPANT_LEAVE: \"participant:leave\",\n\n // ────────────── conversation-invite resource ──────────────\n CONVERSATION_INVITE_READ: \"conversation-invite:read\",\n CONVERSATION_INVITE_CREATE: \"conversation-invite:create\",\n CONVERSATION_INVITE_JOIN: \"conversation-invite:join\",\n\n // ────────────── call resource ──────────────\n // CALL_UPDATE is participant-self-service: it backs `updateCallParticipant`\n // (muted / video / screen share state for the calling participant). Call\n // host/moderator-only actions — ending or recording — live in\n // CALL_END / CALL_RECORD and belong in the admin subset.\n CALL_READ: \"call:read\",\n CALL_CREATE: \"call:create\",\n CALL_JOIN: \"call:join\",\n CALL_LEAVE: \"call:leave\",\n CALL_END: \"call:end\",\n CALL_UPDATE: \"call:update\",\n CALL_RECORD: \"call:record\",\n\n // ────────────── app-installation resource ──────────────\n APP_INSTALLATION_READ: \"app-installation:read\",\n APP_INSTALLATION_CREATE: \"app-installation:create\",\n APP_INSTALLATION_UPDATE: \"app-installation:update\",\n APP_INSTALLATION_DELETE: \"app-installation:delete\",\n APP_INSTALLATION_EXECUTE: \"app-installation:execute\",\n\n // ────────────── presence resource ──────────────\n PRESENCE_READ: \"presence:read\",\n PRESENCE_UPDATE: \"presence:update\",\n\n // ────────────── entity-comment resource ──────────────\n ENTITY_COMMENT_READ: \"entity-comment:read\",\n ENTITY_COMMENT_CREATE: \"entity-comment:create\",\n ENTITY_COMMENT_UPDATE: \"entity-comment:update\",\n ENTITY_COMMENT_DELETE: \"entity-comment:delete\",\n ENTITY_COMMENT_ASSIGN: \"entity-comment:assign\",\n ENTITY_COMMENT_EXPORT: \"entity-comment:export\",\n\n // ────────────── comment-attachment resource ──────────────\n COMMENT_ATTACHMENT_READ: \"comment-attachment:read\",\n COMMENT_ATTACHMENT_CREATE: \"comment-attachment:create\",\n COMMENT_ATTACHMENT_UPDATE: \"comment-attachment:update\",\n COMMENT_ATTACHMENT_DELETE: \"comment-attachment:delete\",\n\n // ────────────── comment-tag resource ──────────────\n COMMENT_TAG_READ: \"comment-tag:read\",\n COMMENT_TAG_ASSIGN: \"comment-tag:assign\",\n COMMENT_TAG_UPDATE: \"comment-tag:update\",\n COMMENT_TAG_DELETE: \"comment-tag:delete\",\n\n // ────────────── scheduled-comment resource ──────────────\n SCHEDULED_COMMENT_READ: \"scheduled-comment:read\",\n SCHEDULED_COMMENT_CREATE: \"scheduled-comment:create\",\n SCHEDULED_COMMENT_UPDATE: \"scheduled-comment:update\",\n SCHEDULED_COMMENT_DELETE: \"scheduled-comment:delete\",\n SCHEDULED_COMMENT_EXECUTE: \"scheduled-comment:execute\",\n} as const;\n\nexport type ConversationsPermission =\n (typeof CONVERSATIONS_PERMISSIONS)[keyof typeof CONVERSATIONS_PERMISSIONS];\n"],"mappings":";AAeA,IAAa,IAA4B;CAEvC,mBAAmB;CACnB,mBAAmB;CACnB,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACrB,sBAAsB;CACtB,wBAAwB;CAGxB,cAAc;CACd,gBAAgB;CAChB,gBAAgB;CAChB,gBAAgB;CAChB,eAAe;CACf,aAAa;CACb,kBAAkB;CAClB,gBAAgB;CAChB,cAAc;CAMd,oBAAoB;CACpB,sBAAsB;CACtB,sBAAsB;CAGtB,kBAAkB;CAClB,iBAAiB;CACjB,oBAAoB;CACpB,oBAAoB;CACpB,mBAAmB;CAGnB,0BAA0B;CAC1B,4BAA4B;CAC5B,0BAA0B;CAO1B,WAAW;CACX,aAAa;CACb,WAAW;CACX,YAAY;CACZ,UAAU;CACV,aAAa;CACb,aAAa;CAGb,uBAAuB;CACvB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,0BAA0B;CAG1B,eAAe;CACf,iBAAiB;CAGjB,qBAAqB;CACrB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CAGvB,yBAAyB;CACzB,2BAA2B;CAC3B,2BAA2B;CAC3B,2BAA2B;CAG3B,kBAAkB;CAClB,oBAAoB;CACpB,oBAAoB;CACpB,oBAAoB;CAGpB,wBAAwB;CACxB,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;CAC1B,2BAA2B;CAC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMessageQueries.js","names":[],"sources":["../../../src/conversations/hooks/useMessageQueries.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport {\n useAddReactionMutation,\n useDeleteMessageMutation,\n useEditMessageMutation,\n GetMessagesDocument,\n useGetMessagesQuery,\n useGetThreadMessagesQuery,\n usePinMessageByHookMutation,\n useRemoveReactionMutation,\n useSearchMessagesByHookQuery,\n useSendMessageMutation,\n useUnpinMessageByHookMutation,\n} from \"../../generated/wspace-operations\";\nimport type { Message, MessageType } from \"../types\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { mapMessage } from \"../utils/mappers\";\n\ninterface UseGetMessagesOptions {\n first?: number;\n after?: string;\n before?: string;\n}\n\ninterface UseGetMessagesResult {\n messages: Message[];\n loading: boolean;\n error: Error | null;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n fetchMore: () => void;\n fetchPrevious: () => void;\n refetch: () => void;\n}\n\n/**\n * Hook: Fetch paginated messages for a conversation.\n */\nexport function useGetMessages(\n conversationId: string,\n options?: UseGetMessagesOptions,\n): UseGetMessagesResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetMessagesQuery({\n variables: {\n conversationId,\n first: options?.first ?? 50,\n after: options?.after,\n before: options?.before,\n },\n skip: !workspaceId || !conversationId,\n });\n\n const messages = useMemo(\n () =>\n queryResult.data?.messages.edges.map(\n (edge: { node: Record<string, unknown> }) => mapMessage(edge.node),\n ) ?? [],\n [queryResult.data],\n );\n\n const pageInfo = queryResult.data?.messages.pageInfo;\n\n return {\n messages,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n hasNextPage: pageInfo?.hasNextPage ?? false,\n hasPreviousPage: pageInfo?.hasPreviousPage ?? false,\n fetchMore: () => {\n const endCursor = pageInfo?.endCursor;\n if (!endCursor || !pageInfo?.hasNextPage) return;\n void queryResult.fetchMore({\n variables: {\n conversationId,\n first: options?.first ?? 50,\n after: endCursor,\n },\n });\n },\n fetchPrevious: () => {\n const startCursor = pageInfo?.startCursor;\n if (!startCursor || !pageInfo?.hasPreviousPage) return;\n void queryResult.fetchMore({\n variables: {\n conversationId,\n before: startCursor,\n first: options?.first ?? 50,\n },\n });\n },\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface UseGetThreadResult {\n messages: Message[];\n loading: boolean;\n error: Error | null;\n fetchMore: () => void;\n}\n\n/**\n * Hook: Fetch thread replies for a message.\n */\nexport function useGetThread(\n messageId: string,\n options?: { first?: number; after?: string },\n): UseGetThreadResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetThreadMessagesQuery({\n variables: {\n messageId,\n first: options?.first ?? 25,\n after: options?.after,\n },\n skip: !workspaceId || !messageId,\n });\n\n return {\n messages:\n queryResult.data?.thread.edges.map(\n (edge: { node: Record<string, unknown> }) => mapMessage(edge.node),\n ) ?? [],\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n fetchMore: () => {\n const endCursor = queryResult.data?.thread.pageInfo.endCursor;\n if (!queryResult.data?.thread.pageInfo.hasNextPage || !endCursor) return;\n void queryResult.fetchMore({\n variables: {\n messageId,\n first: options?.first ?? 25,\n after: endCursor,\n },\n });\n },\n };\n}\n\ninterface SendMessageInput {\n conversationId: string;\n content: string;\n type?: MessageType;\n parentMessageId?: string;\n attachmentIds?: string[];\n mentionUserIds?: string[];\n refetchFirst?: number;\n}\n\ninterface UseSendMessageResult {\n sendMessage: (input: SendMessageInput) => Promise<Message | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Send a message to a conversation.\n */\nexport function useSendMessage(): UseSendMessageResult {\n const [sendMessageMutation, mutationState] = useSendMessageMutation();\n\n const sendMessage = useCallback(\n async (input: SendMessageInput): Promise<Message | null> => {\n try {\n const { data } = await sendMessageMutation({\n variables: {\n input: {\n conversationId: input.conversationId,\n type: input.type ?? \"TEXT\",\n content: { text: input.content },\n parentMessageId: input.parentMessageId,\n attachmentIds: input.attachmentIds,\n mentionUserIds: input.mentionUserIds,\n },\n },\n refetchQueries: [\n {\n query: GetMessagesDocument,\n variables: {\n conversationId: input.conversationId,\n first: input.refetchFirst ?? 50,\n },\n },\n ],\n });\n\n const message = data?.sendMessage.message;\n return message ? mapMessage(message) : null;\n } catch (err) {\n throw err instanceof Error ? err : new Error(\"Failed to send message\");\n }\n },\n [sendMessageMutation],\n );\n\n return {\n sendMessage,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseEditMessageResult {\n editMessage: (\n messageId: string,\n content: string,\n reason?: string,\n ) => Promise<Message | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Edit an existing message.\n */\nexport function useEditMessage(): UseEditMessageResult {\n const [editMessageMutation, mutationState] = useEditMessageMutation();\n\n const editMessage = useCallback(\n async (\n messageId: string,\n content: string,\n reason?: string,\n ): Promise<Message | null> => {\n try {\n const { data } = await editMessageMutation({\n variables: {\n input: {\n id: messageId,\n content: { text: content },\n reason,\n },\n },\n });\n const message = data?.editMessage;\n return message ? mapMessage(message as Record<string, unknown>) : null;\n } catch (err) {\n throw err instanceof Error ? err : new Error(\"Failed to edit message\");\n }\n },\n [editMessageMutation],\n );\n\n return {\n editMessage,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseMessageActionsResult {\n deleteMessage: (messageId: string) => Promise<boolean>;\n addReaction: (messageId: string, emoji: string) => Promise<boolean>;\n removeReaction: (messageId: string, emoji: string) => Promise<boolean>;\n pinMessage: (messageId: string) => Promise<boolean>;\n unpinMessage: (messageId: string) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Message action mutations (delete, react, pin).\n */\nexport function useMessageActions(): UseMessageActionsResult {\n const [deleteMessageMutation, deleteState] = useDeleteMessageMutation();\n const [addReactionMutation, addReactionState] = useAddReactionMutation();\n const [removeReactionMutation, removeReactionState] =\n useRemoveReactionMutation();\n const [pinMessageMutation, pinState] = usePinMessageByHookMutation();\n const [unpinMessageMutation, unpinState] = useUnpinMessageByHookMutation();\n\n const deleteMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await deleteMessageMutation({\n variables: { id: messageId },\n });\n return data?.deleteMessage.success ?? false;\n },\n [deleteMessageMutation],\n );\n\n const addReaction = useCallback(\n async (messageId: string, emoji: string): Promise<boolean> => {\n const { data } = await addReactionMutation({\n variables: { messageId, emoji },\n });\n return Boolean(data?.addReaction.id);\n },\n [addReactionMutation],\n );\n\n const removeReaction = useCallback(\n async (messageId: string, emoji: string): Promise<boolean> => {\n const { data } = await removeReactionMutation({\n variables: { messageId, emoji },\n });\n return data?.removeReaction.success ?? false;\n },\n [removeReactionMutation],\n );\n\n const pinMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await pinMessageMutation({ variables: { messageId } });\n return Boolean(data?.pinMessage?.id);\n },\n [pinMessageMutation],\n );\n\n const unpinMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await unpinMessageMutation({ variables: { messageId } });\n return Boolean(data?.unpinMessage?.id);\n },\n [unpinMessageMutation],\n );\n\n return {\n deleteMessage,\n addReaction,\n removeReaction,\n pinMessage,\n unpinMessage,\n loading:\n deleteState.loading ||\n addReactionState.loading ||\n removeReactionState.loading ||\n pinState.loading ||\n unpinState.loading,\n error:\n (deleteState.error as Error | undefined) ??\n (addReactionState.error as Error | undefined) ??\n (removeReactionState.error as Error | undefined) ??\n (pinState.error as Error | undefined) ??\n (unpinState.error as Error | undefined) ??\n null,\n };\n}\n\ninterface UseSearchMessagesResult {\n messages: Message[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Search messages across conversations or within a conversation.\n */\nexport function useSearchMessages(\n query: string,\n conversationId?: string,\n): UseSearchMessagesResult {\n const { workspaceId } = useConversations();\n const queryResult = useSearchMessagesByHookQuery({\n variables: { query: query.trim(), conversationId, first: 50 },\n skip: !workspaceId || !query.trim(),\n });\n\n return {\n messages:\n queryResult.data?.searchMessages.edges.map(\n (edge: { node: Record<string, unknown> }) => mapMessage(edge.node),\n ) ?? [],\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useMessageQueries.js","names":[],"sources":["../../../src/conversations/hooks/useMessageQueries.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport {\n useAddReactionMutation,\n useDeleteMessageMutation,\n useEditMessageMutation,\n GetMessagesDocument,\n useGetMessagesQuery,\n usePinMessageByHookMutation,\n useRemoveReactionMutation,\n useSendMessageMutation,\n useUnpinMessageByHookMutation,\n} from \"../../generated/wspace-operations\";\nimport type { Message, MessageType } from \"../types\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { mapMessage } from \"../utils/mappers\";\n\ninterface UseGetMessagesOptions {\n first?: number;\n after?: string;\n before?: string;\n}\n\ninterface UseGetMessagesResult {\n messages: Message[];\n loading: boolean;\n error: Error | null;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n fetchMore: () => void;\n fetchPrevious: () => void;\n refetch: () => void;\n}\n\n/**\n * Hook: Fetch paginated messages for a conversation.\n */\nexport function useGetMessages(\n conversationId: string,\n options?: UseGetMessagesOptions,\n): UseGetMessagesResult {\n const { workspaceId } = useConversations();\n const queryResult = useGetMessagesQuery({\n variables: {\n conversationId,\n first: options?.first ?? 50,\n after: options?.after,\n before: options?.before,\n },\n skip: !workspaceId || !conversationId,\n });\n\n const messages = useMemo(\n () =>\n queryResult.data?.messages.edges.map(\n (edge: { node: Record<string, unknown> }) => mapMessage(edge.node),\n ) ?? [],\n [queryResult.data],\n );\n\n const pageInfo = queryResult.data?.messages.pageInfo;\n\n return {\n messages,\n loading: queryResult.loading,\n error: queryResult.error ?? null,\n hasNextPage: pageInfo?.hasNextPage ?? false,\n hasPreviousPage: pageInfo?.hasPreviousPage ?? false,\n fetchMore: () => {\n const endCursor = pageInfo?.endCursor;\n if (!endCursor || !pageInfo?.hasNextPage) return;\n void queryResult.fetchMore({\n variables: {\n conversationId,\n first: options?.first ?? 50,\n after: endCursor,\n },\n });\n },\n fetchPrevious: () => {\n const startCursor = pageInfo?.startCursor;\n if (!startCursor || !pageInfo?.hasPreviousPage) return;\n void queryResult.fetchMore({\n variables: {\n conversationId,\n before: startCursor,\n first: options?.first ?? 50,\n },\n });\n },\n refetch: () => {\n void queryResult.refetch();\n },\n };\n}\n\ninterface SendMessageInput {\n conversationId: string;\n content: string;\n type?: MessageType;\n parentMessageId?: string;\n attachmentIds?: string[];\n mentionUserIds?: string[];\n refetchFirst?: number;\n}\n\ninterface UseSendMessageResult {\n sendMessage: (input: SendMessageInput) => Promise<Message | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Send a message to a conversation.\n */\nexport function useSendMessage(): UseSendMessageResult {\n const [sendMessageMutation, mutationState] = useSendMessageMutation();\n\n const sendMessage = useCallback(\n async (input: SendMessageInput): Promise<Message | null> => {\n try {\n const { data } = await sendMessageMutation({\n variables: {\n input: {\n conversationId: input.conversationId,\n type: input.type ?? \"TEXT\",\n content: { text: input.content },\n parentMessageId: input.parentMessageId,\n attachmentIds: input.attachmentIds,\n mentionUserIds: input.mentionUserIds,\n },\n },\n refetchQueries: [\n {\n query: GetMessagesDocument,\n variables: {\n conversationId: input.conversationId,\n first: input.refetchFirst ?? 50,\n },\n },\n ],\n });\n\n const message = data?.sendMessage.message;\n return message ? mapMessage(message) : null;\n } catch (err) {\n throw err instanceof Error ? err : new Error(\"Failed to send message\");\n }\n },\n [sendMessageMutation],\n );\n\n return {\n sendMessage,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseEditMessageResult {\n editMessage: (\n messageId: string,\n content: string,\n reason?: string,\n ) => Promise<Message | null>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Edit an existing message.\n */\nexport function useEditMessage(): UseEditMessageResult {\n const [editMessageMutation, mutationState] = useEditMessageMutation();\n\n const editMessage = useCallback(\n async (\n messageId: string,\n content: string,\n reason?: string,\n ): Promise<Message | null> => {\n try {\n const { data } = await editMessageMutation({\n variables: {\n input: {\n id: messageId,\n content: { text: content },\n reason,\n },\n },\n });\n const message = data?.editMessage;\n return message ? mapMessage(message as Record<string, unknown>) : null;\n } catch (err) {\n throw err instanceof Error ? err : new Error(\"Failed to edit message\");\n }\n },\n [editMessageMutation],\n );\n\n return {\n editMessage,\n loading: mutationState.loading,\n error: mutationState.error ?? null,\n };\n}\n\ninterface UseMessageActionsResult {\n deleteMessage: (messageId: string) => Promise<boolean>;\n addReaction: (messageId: string, emoji: string) => Promise<boolean>;\n removeReaction: (messageId: string, emoji: string) => Promise<boolean>;\n pinMessage: (messageId: string) => Promise<boolean>;\n unpinMessage: (messageId: string) => Promise<boolean>;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook: Message action mutations (delete, react, pin).\n */\nexport function useMessageActions(): UseMessageActionsResult {\n const [deleteMessageMutation, deleteState] = useDeleteMessageMutation();\n const [addReactionMutation, addReactionState] = useAddReactionMutation();\n const [removeReactionMutation, removeReactionState] =\n useRemoveReactionMutation();\n const [pinMessageMutation, pinState] = usePinMessageByHookMutation();\n const [unpinMessageMutation, unpinState] = useUnpinMessageByHookMutation();\n\n const deleteMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await deleteMessageMutation({\n variables: { id: messageId },\n });\n return data?.deleteMessage.success ?? false;\n },\n [deleteMessageMutation],\n );\n\n const addReaction = useCallback(\n async (messageId: string, emoji: string): Promise<boolean> => {\n const { data } = await addReactionMutation({\n variables: { messageId, emoji },\n });\n return Boolean(data?.addReaction.id);\n },\n [addReactionMutation],\n );\n\n const removeReaction = useCallback(\n async (messageId: string, emoji: string): Promise<boolean> => {\n const { data } = await removeReactionMutation({\n variables: { messageId, emoji },\n });\n return data?.removeReaction.success ?? false;\n },\n [removeReactionMutation],\n );\n\n const pinMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await pinMessageMutation({ variables: { messageId } });\n return Boolean(data?.pinMessage?.id);\n },\n [pinMessageMutation],\n );\n\n const unpinMessage = useCallback(\n async (messageId: string): Promise<boolean> => {\n const { data } = await unpinMessageMutation({ variables: { messageId } });\n return Boolean(data?.unpinMessage?.id);\n },\n [unpinMessageMutation],\n );\n\n return {\n deleteMessage,\n addReaction,\n removeReaction,\n pinMessage,\n unpinMessage,\n loading:\n deleteState.loading ||\n addReactionState.loading ||\n removeReactionState.loading ||\n pinState.loading ||\n unpinState.loading,\n error:\n (deleteState.error as Error | undefined) ??\n (addReactionState.error as Error | undefined) ??\n (removeReactionState.error as Error | undefined) ??\n (pinState.error as Error | undefined) ??\n (unpinState.error as Error | undefined) ??\n null,\n };\n}\n\n"],"mappings":";;;;;AAoCA,SAAgB,EACd,GACA,GACsB;CACtB,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAc,EAAoB;EACtC,WAAW;GACT;GACA,OAAO,GAAS,SAAS;GACzB,OAAO,GAAS;GAChB,QAAQ,GAAS;GAClB;EACD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EAEI,IAAW,QAEb,EAAY,MAAM,SAAS,MAAM,KAC9B,MAA4C,EAAW,EAAK,KAAK,CACnE,IAAI,EAAE,EACT,CAAC,EAAY,KAAK,CACnB,EAEK,IAAW,EAAY,MAAM,SAAS;AAE5C,QAAO;EACL;EACA,SAAS,EAAY;EACrB,OAAO,EAAY,SAAS;EAC5B,aAAa,GAAU,eAAe;EACtC,iBAAiB,GAAU,mBAAmB;EAC9C,iBAAiB;GACf,IAAM,IAAY,GAAU;AACxB,IAAC,KAAa,CAAC,GAAU,eACxB,EAAY,UAAU,EACzB,WAAW;IACT;IACA,OAAO,GAAS,SAAS;IACzB,OAAO;IACR,EACF,CAAC;;EAEJ,qBAAqB;GACnB,IAAM,IAAc,GAAU;AAC1B,IAAC,KAAe,CAAC,GAAU,mBAC1B,EAAY,UAAU,EACzB,WAAW;IACT;IACA,QAAQ;IACR,OAAO,GAAS,SAAS;IAC1B,EACF,CAAC;;EAEJ,eAAe;AACR,KAAY,SAAS;;EAE7B;;AAsBH,SAAgB,IAAuC;CACrD,IAAM,CAAC,GAAqB,KAAiB,GAAwB;AAoCrE,QAAO;EACL,aAnCkB,EAClB,OAAO,MAAqD;AAC1D,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB;KACzC,WAAW,EACT,OAAO;MACL,gBAAgB,EAAM;MACtB,MAAM,EAAM,QAAQ;MACpB,SAAS,EAAE,MAAM,EAAM,SAAS;MAChC,iBAAiB,EAAM;MACvB,eAAe,EAAM;MACrB,gBAAgB,EAAM;MACvB,EACF;KACD,gBAAgB,CACd;MACE,OAAO;MACP,WAAW;OACT,gBAAgB,EAAM;OACtB,OAAO,EAAM,gBAAgB;OAC9B;MACF,CACF;KACF,CAAC,EAEI,IAAU,GAAM,YAAY;AAClC,WAAO,IAAU,EAAW,EAAQ,GAAG;YAChC,GAAK;AACZ,UAAM,aAAe,QAAQ,IAAM,gBAAI,MAAM,yBAAyB;;KAG1E,CAAC,EAAoB,CACtB;EAIC,SAAS,EAAc;EACvB,OAAO,EAAc,SAAS;EAC/B;;AAgBH,SAAgB,IAAuC;CACrD,IAAM,CAAC,GAAqB,KAAiB,GAAwB;AA2BrE,QAAO;EACL,aA1BkB,EAClB,OACE,GACA,GACA,MAC4B;AAC5B,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW,EACT,OAAO;KACL,IAAI;KACJ,SAAS,EAAE,MAAM,GAAS;KAC1B;KACD,EACF,EACF,CAAC,EACI,IAAU,GAAM;AACtB,WAAO,IAAU,EAAW,EAAmC,GAAG;YAC3D,GAAK;AACZ,UAAM,aAAe,QAAQ,IAAM,gBAAI,MAAM,yBAAyB;;KAG1E,CAAC,EAAoB,CACtB;EAIC,SAAS,EAAc;EACvB,OAAO,EAAc,SAAS;EAC/B;;AAgBH,SAAgB,IAA6C;CAC3D,IAAM,CAAC,GAAuB,KAAe,GAA0B,EACjE,CAAC,GAAqB,KAAoB,GAAwB,EAClE,CAAC,GAAwB,KAC7B,GAA2B,EACvB,CAAC,GAAoB,KAAY,GAA6B,EAC9D,CAAC,GAAsB,KAAc,GAA+B;AAgD1E,QAAO;EACL,eA/CoB,EACpB,OAAO,MAAwC;GAC7C,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW,EAAE,IAAI,GAAW,EAC7B,CAAC;AACF,UAAO,GAAM,cAAc,WAAW;KAExC,CAAC,EAAsB,CACxB;EAwCC,aAtCkB,EAClB,OAAO,GAAmB,MAAoC;GAC5D,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW;IAAE;IAAW;IAAO,EAChC,CAAC;AACF,UAAO,EAAQ,GAAM,YAAY;KAEnC,CAAC,EAAoB,CACtB;EA+BC,gBA7BqB,EACrB,OAAO,GAAmB,MAAoC;GAC5D,IAAM,EAAE,YAAS,MAAM,EAAuB,EAC5C,WAAW;IAAE;IAAW;IAAO,EAChC,CAAC;AACF,UAAO,GAAM,eAAe,WAAW;KAEzC,CAAC,EAAuB,CACzB;EAsBC,YApBiB,EACjB,OAAO,MAAwC;GAC7C,IAAM,EAAE,YAAS,MAAM,EAAmB,EAAE,WAAW,EAAE,cAAW,EAAE,CAAC;AACvE,UAAO,EAAQ,GAAM,YAAY;KAEnC,CAAC,EAAmB,CACrB;EAeC,cAbmB,EACnB,OAAO,MAAwC;GAC7C,IAAM,EAAE,YAAS,MAAM,EAAqB,EAAE,WAAW,EAAE,cAAW,EAAE,CAAC;AACzE,UAAO,EAAQ,GAAM,cAAc;KAErC,CAAC,EAAqB,CACvB;EAQC,SACE,EAAY,WACZ,EAAiB,WACjB,EAAoB,WACpB,EAAS,WACT,EAAW;EACb,OACG,EAAY,SACZ,EAAiB,SACjB,EAAoB,SACpB,EAAS,SACT,EAAW,SACZ;EACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMessageSubscriptions.js","names":[],"sources":["../../../src/conversations/hooks/useMessageSubscriptions.ts"],"sourcesContent":["/**\n * Real-time message subscription hooks.\n *\n * Wires the GraphQL subscriptions defined in\n * `src/operations/wspace/conversations/subscriptions/messageSubscriptions.graphql`\n * into Apollo cache updates so pages do not have to poll.\n *\n * Usage in a page:\n * useMessageStreamForConversation(conversationId);\n *\n * The hook ignores subscription errors (logs only) — the page UI continues to\n * work with whatever is already in the cache; the next refresh / refetch\n * recovers any missed events.\n *\n * NOTE: This file is the canonical replacement for `pollInterval` usage in\n * the conversation detail and list pages. Pages should remove their\n * `pollInterval: …` arguments when they switch to this hook.\n */\nimport { useEffect } from \"react\";\nimport { type ApolloCache, type FetchResult } from \"@apollo/client\";\nimport { useApolloClient } from \"@apollo/client/react\";\nimport { useSubscription } from \"@apollo/client/react\";\n\nimport {\n MessageAddedDocument,\n MessageUpdatedDocument,\n MessageDeletedDocument,\n MessageReactionChangedDocument,\n MessageReadReceiptAddedDocument,\n type MessageAddedSubscription,\n type MessageUpdatedSubscription,\n type MessageDeletedSubscription,\n type MessageReactionChangedSubscription,\n type MessageReadReceiptAddedSubscription,\n GetMessagesDocument,\n type GetMessagesQuery,\n type GetMessagesQueryVariables,\n} from \"../../generated/wspace-operations\";\n\ninterface MessageEdge {\n __typename?: \"MessageEdge\";\n cursor?: string | null;\n node?: { id: string; [key: string]: unknown } | null;\n}\n\ninterface MessagesConnection {\n __typename?: \"MessageConnection\";\n edges?: MessageEdge[] | null;\n pageInfo?: unknown;\n totalCount?: number | null;\n}\n\n/**\n * Append a new message to the cached `messages(conversationId: …)` connection\n * if it isn't already present.\n */\nfunction appendToCache(\n cache: ApolloCache,\n conversationId: string,\n newMessage: { id: string; [key: string]: unknown },\n): void {\n cache.updateQuery<GetMessagesQuery, GetMessagesQueryVariables>(\n {\n query: GetMessagesDocument,\n variables: { conversationId, first: 50 },\n },\n (existing: GetMessagesQuery | null) => {\n if (!existing) return existing;\n const conn = existing.messages as MessagesConnection | null | undefined;\n const edges = conn?.edges ?? [];\n if (edges.some((e) => e?.node?.id === newMessage.id)) return existing;\n\n return {\n ...existing,\n messages: {\n ...(conn ?? {}),\n edges: [\n ...edges,\n {\n __typename: \"MessageEdge\",\n cursor: newMessage.id,\n node: newMessage,\n },\n ],\n totalCount: (conn?.totalCount ?? edges.length) + 1,\n },\n } as GetMessagesQuery;\n },\n );\n}\n\n/**\n * Evict a deleted message from the cache.\n */\nfunction evictFromCache(cache: ApolloCache, messageId: string): void {\n cache.evict({ id: cache.identify({ __typename: \"Message\", id: messageId }) });\n cache.gc();\n}\n\nexport function useMessageStreamForConversation(\n conversationId: string | undefined,\n): void {\n const client = useApolloClient();\n\n // Inbound new messages\n useSubscription<MessageAddedSubscription>(MessageAddedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({ data }: { data: FetchResult<MessageAddedSubscription> }) => {\n const msg = data.data?.messageAdded;\n if (!msg || !conversationId) return;\n appendToCache(client.cache, conversationId, msg as { id: string });\n },\n onError: (err) =>\n console.warn(\"[useMessageStream] messageAdded error:\", err),\n });\n\n // Edits\n useSubscription<MessageUpdatedSubscription>(MessageUpdatedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useMessageStream] messageUpdated error:\", err),\n // Apollo merges by `id` automatically (cache normalisation) — no manual write needed.\n });\n\n // Deletes\n useSubscription<MessageDeletedSubscription>(MessageDeletedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({ data }: { data: FetchResult<MessageDeletedSubscription> }) => {\n const messageId = data.data?.messageDeleted?.messageId;\n if (!messageId) return;\n evictFromCache(client.cache, messageId);\n },\n onError: (err) =>\n console.warn(\"[useMessageStream] messageDeleted error:\", err),\n });\n\n // Cleanup intentionally noop — Apollo unsubscribes on unmount via the hook lifecycle.\n useEffect(() => () => undefined, [conversationId]);\n}\n\n/**\n * Reactions stream: subscribe per-message. Useful for a focused message\n * preview (not the entire conversation).\n */\nexport function useReactionStreamForMessage(\n messageId: string | undefined,\n): void {\n useSubscription<MessageReactionChangedSubscription>(\n MessageReactionChangedDocument,\n {\n variables: { messageId: messageId ?? \"\" },\n skip: !messageId,\n onError: (err) => console.warn(\"[useReactionStream] error:\", err),\n },\n );\n}\n\n/**\n * Read-receipt stream for a single message — used by the read-by indicator.\n */\nexport function useReadReceiptStreamForMessage(\n messageId: string | undefined,\n): void {\n useSubscription<MessageReadReceiptAddedSubscription>(\n MessageReadReceiptAddedDocument,\n {\n variables: { messageId: messageId ?? \"\" },\n skip: !messageId,\n onError: (err) => console.warn(\"[useReadReceiptStream] error:\", err),\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Conversation-level streams (used by the list and landing pages).\n// These imports resolve once codegen surfaces them from the new\n// subscriptions/conversationSubscriptions.graphql file.\n// ---------------------------------------------------------------------------\n\nimport {\n ConversationAddedDocument,\n ConversationUpdatedDocument,\n ConversationDeletedDocument,\n ParticipantChangedDocument,\n type ConversationAddedSubscription,\n type ConversationUpdatedSubscription,\n type ConversationDeletedSubscription,\n type ParticipantChangedSubscription,\n} from \"../../generated/wspace-operations\";\n\n/**\n * Subscribe to all conversation-level lifecycle events for the current actor.\n *\n * - `conversationAdded` — refresh the conversation list when one is created.\n * - `conversationDeleted` — evict from cache when one is removed.\n * - `conversationUpdated` — Apollo normalisation handles automatically; no\n * manual write required.\n * - `participantChanged` — refresh members panel.\n *\n * Use at the top of the list page / landing page once. Safe to call multiple\n * times across components — Apollo deduplicates.\n */\nexport function useConversationStream(\n conversationId?: string | undefined,\n): void {\n const client = useApolloClient();\n\n useSubscription<ConversationAddedSubscription>(ConversationAddedDocument, {\n onData: ({\n data,\n }: {\n data: FetchResult<ConversationAddedSubscription>;\n }) => {\n const conv = data.data?.conversationAdded;\n if (!conv) return;\n // Invalidate the list so the next render picks up the new conversation.\n // Cheaper than maintaining a manual append into every list variant.\n client.cache.evict({ fieldName: \"conversations\" });\n client.cache.gc();\n },\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationAdded error:\", err),\n });\n\n useSubscription<ConversationDeletedSubscription>(\n ConversationDeletedDocument,\n {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({\n data,\n }: {\n data: FetchResult<ConversationDeletedSubscription>;\n }) => {\n const id = data.data?.conversationDeleted?.conversationId;\n if (!id) return;\n client.cache.evict({\n id: client.cache.identify({ __typename: \"Conversation\", id }),\n });\n client.cache.gc();\n },\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationDeleted error:\", err),\n },\n );\n\n useSubscription<ConversationUpdatedSubscription>(\n ConversationUpdatedDocument,\n {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationUpdated error:\", err),\n },\n );\n\n useSubscription<ParticipantChangedSubscription>(ParticipantChangedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useConversationStream] participantChanged error:\", err),\n });\n}\n"],"mappings":";;;;AAwDA,SAAS,EACP,GACA,GACA,GACM;AACN,GAAM,YACJ;EACE,OAAO;EACP,WAAW;GAAE;GAAgB,OAAO;GAAI;EACzC,GACA,MAAsC;AACrC,MAAI,CAAC,EAAU,QAAO;EACtB,IAAM,IAAO,EAAS,UAChB,IAAQ,GAAM,SAAS,EAAE;AAG/B,SAFI,EAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAW,GAAG,GAAS,IAEtD;GACL,GAAG;GACH,UAAU;IACR,GAAI,KAAQ,EAAE;IACd,OAAO,CACL,GAAG,GACH;KACE,YAAY;KACZ,QAAQ,EAAW;KACnB,MAAM;KACP,CACF;IACD,aAAa,GAAM,cAAc,EAAM,UAAU;IAClD;GACF;GAEJ;;AAMH,SAAS,EAAe,GAAoB,GAAyB;AAEnE,CADA,EAAM,MAAM,EAAE,IAAI,EAAM,SAAS;EAAE,YAAY;EAAW,IAAI;EAAW,CAAC,EAAE,CAAC,EAC7E,EAAM,IAAI;;AAGZ,SAAgB,EACd,GACM;CACN,IAAM,IAAS,GAAiB;AAsChC,CAnCA,EAA0C,GAAsB;EAC9D,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EAAE,cAA4D;GACrE,IAAM,IAAM,EAAK,MAAM;AACnB,IAAC,KAAO,CAAC,KACb,EAAc,EAAO,OAAO,GAAgB,EAAsB;;EAEpE,UAAU,MACR,QAAQ,KAAK,0CAA0C,EAAI;EAC9D,CAAC,EAGF,EAA4C,GAAwB;EAClE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,4CAA4C,EAAI;EAEhE,CAAC,EAGF,EAA4C,GAAwB;EAClE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EAAE,cAA8D;GACvE,IAAM,IAAY,EAAK,MAAM,gBAAgB;AACxC,QACL,EAAe,EAAO,OAAO,EAAU;;EAEzC,UAAU,MACR,QAAQ,KAAK,4CAA4C,EAAI;EAChE,CAAC,EAGF,cAAsB,KAAA,GAAW,CAAC,EAAe,CAAC;;AAiEpD,SAAgB,EACd,GACM;CACN,IAAM,IAAS,GAAiB;AAmDhC,CAjDA,EAA+C,GAA2B;EACxE,SAAS,EACP,cAGI;AACS,KAAK,MAAM,sBAIxB,EAAO,MAAM,MAAM,EAAE,WAAW,iBAAiB,CAAC,EAClD,EAAO,MAAM,IAAI;;EAEnB,UAAU,MACR,QAAQ,KAAK,oDAAoD,EAAI;EACxE,CAAC,EAEF,EACE,GACA;EACE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EACP,cAGI;GACJ,IAAM,IAAK,EAAK,MAAM,qBAAqB;AACtC,SACL,EAAO,MAAM,MAAM,EACjB,IAAI,EAAO,MAAM,SAAS;IAAE,YAAY;IAAgB;IAAI,CAAC,EAC9D,CAAC,EACF,EAAO,MAAM,IAAI;;EAEnB,UAAU,MACR,QAAQ,KAAK,sDAAsD,EAAI;EAC1E,CACF,EAED,EACE,GACA;EACE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,sDAAsD,EAAI;EAC1E,CACF,EAED,EAAgD,GAA4B;EAC1E,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,qDAAqD,EAAI;EACzE,CAAC"}
1
+ {"version":3,"file":"useMessageSubscriptions.js","names":[],"sources":["../../../src/conversations/hooks/useMessageSubscriptions.ts"],"sourcesContent":["/**\n * Real-time message subscription hooks.\n *\n * Wires the GraphQL subscriptions defined in\n * `src/operations/wspace/conversations/subscriptions/messageSubscriptions.graphql`\n * into Apollo cache updates so pages do not have to poll.\n *\n * Usage in a page:\n * useMessageStreamForConversation(conversationId);\n *\n * The hook ignores subscription errors (logs only) — the page UI continues to\n * work with whatever is already in the cache; the next refresh / refetch\n * recovers any missed events.\n *\n * NOTE: This file is the canonical replacement for `pollInterval` usage in\n * the conversation detail and list pages. Pages should remove their\n * `pollInterval: …` arguments when they switch to this hook.\n */\nimport { useEffect } from \"react\";\nimport { type ApolloCache, type FetchResult } from \"@apollo/client\";\nimport { useApolloClient } from \"@apollo/client/react\";\nimport { useSubscription } from \"@apollo/client/react\";\n\nimport {\n MessageAddedDocument,\n MessageUpdatedDocument,\n MessageDeletedDocument,\n type MessageAddedSubscription,\n type MessageUpdatedSubscription,\n type MessageDeletedSubscription,\n GetMessagesDocument,\n type GetMessagesQuery,\n type GetMessagesQueryVariables,\n} from \"../../generated/wspace-operations\";\n\ninterface MessageEdge {\n __typename?: \"MessageEdge\";\n cursor?: string | null;\n node?: { id: string; [key: string]: unknown } | null;\n}\n\ninterface MessagesConnection {\n __typename?: \"MessageConnection\";\n edges?: MessageEdge[] | null;\n pageInfo?: unknown;\n totalCount?: number | null;\n}\n\n/**\n * Append a new message to the cached `messages(conversationId: …)` connection\n * if it isn't already present.\n */\nfunction appendToCache(\n cache: ApolloCache,\n conversationId: string,\n newMessage: { id: string; [key: string]: unknown },\n): void {\n cache.updateQuery<GetMessagesQuery, GetMessagesQueryVariables>(\n {\n query: GetMessagesDocument,\n variables: { conversationId, first: 50 },\n },\n (existing: GetMessagesQuery | null) => {\n if (!existing) return existing;\n const conn = existing.messages as MessagesConnection | null | undefined;\n const edges = conn?.edges ?? [];\n if (edges.some((e) => e?.node?.id === newMessage.id)) return existing;\n\n return {\n ...existing,\n messages: {\n ...(conn ?? {}),\n edges: [\n ...edges,\n {\n __typename: \"MessageEdge\",\n cursor: newMessage.id,\n node: newMessage,\n },\n ],\n totalCount: (conn?.totalCount ?? edges.length) + 1,\n },\n } as GetMessagesQuery;\n },\n );\n}\n\n/**\n * Evict a deleted message from the cache.\n */\nfunction evictFromCache(cache: ApolloCache, messageId: string): void {\n cache.evict({ id: cache.identify({ __typename: \"Message\", id: messageId }) });\n cache.gc();\n}\n\nexport function useMessageStreamForConversation(\n conversationId: string | undefined,\n): void {\n const client = useApolloClient();\n\n // Inbound new messages\n useSubscription<MessageAddedSubscription>(MessageAddedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({ data }: { data: FetchResult<MessageAddedSubscription> }) => {\n const msg = data.data?.messageAdded;\n if (!msg || !conversationId) return;\n appendToCache(client.cache, conversationId, msg as { id: string });\n },\n onError: (err) =>\n console.warn(\"[useMessageStream] messageAdded error:\", err),\n });\n\n // Edits\n useSubscription<MessageUpdatedSubscription>(MessageUpdatedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useMessageStream] messageUpdated error:\", err),\n // Apollo merges by `id` automatically (cache normalisation) — no manual write needed.\n });\n\n // Deletes\n useSubscription<MessageDeletedSubscription>(MessageDeletedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({ data }: { data: FetchResult<MessageDeletedSubscription> }) => {\n const messageId = data.data?.messageDeleted?.messageId;\n if (!messageId) return;\n evictFromCache(client.cache, messageId);\n },\n onError: (err) =>\n console.warn(\"[useMessageStream] messageDeleted error:\", err),\n });\n\n // Cleanup intentionally noop — Apollo unsubscribes on unmount via the hook lifecycle.\n useEffect(() => () => undefined, [conversationId]);\n}\n\n// ---------------------------------------------------------------------------\n// Conversation-level streams (used by the list and landing pages).\n// These imports resolve once codegen surfaces them from the new\n// subscriptions/conversationSubscriptions.graphql file.\n// ---------------------------------------------------------------------------\n\nimport {\n ConversationAddedDocument,\n ConversationUpdatedDocument,\n ConversationDeletedDocument,\n ParticipantChangedDocument,\n type ConversationAddedSubscription,\n type ConversationUpdatedSubscription,\n type ConversationDeletedSubscription,\n type ParticipantChangedSubscription,\n} from \"../../generated/wspace-operations\";\n\n/**\n * Subscribe to all conversation-level lifecycle events for the current actor.\n *\n * - `conversationAdded` — refresh the conversation list when one is created.\n * - `conversationDeleted` — evict from cache when one is removed.\n * - `conversationUpdated` — Apollo normalisation handles automatically; no\n * manual write required.\n * - `participantChanged` — refresh members panel.\n *\n * Use at the top of the list page / landing page once. Safe to call multiple\n * times across components — Apollo deduplicates.\n */\nexport function useConversationStream(\n conversationId?: string | undefined,\n): void {\n const client = useApolloClient();\n\n useSubscription<ConversationAddedSubscription>(ConversationAddedDocument, {\n onData: ({\n data,\n }: {\n data: FetchResult<ConversationAddedSubscription>;\n }) => {\n const conv = data.data?.conversationAdded;\n if (!conv) return;\n // Invalidate the list so the next render picks up the new conversation.\n // Cheaper than maintaining a manual append into every list variant.\n client.cache.evict({ fieldName: \"conversations\" });\n client.cache.gc();\n },\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationAdded error:\", err),\n });\n\n useSubscription<ConversationDeletedSubscription>(\n ConversationDeletedDocument,\n {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onData: ({\n data,\n }: {\n data: FetchResult<ConversationDeletedSubscription>;\n }) => {\n const id = data.data?.conversationDeleted?.conversationId;\n if (!id) return;\n client.cache.evict({\n id: client.cache.identify({ __typename: \"Conversation\", id }),\n });\n client.cache.gc();\n },\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationDeleted error:\", err),\n },\n );\n\n useSubscription<ConversationUpdatedSubscription>(\n ConversationUpdatedDocument,\n {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useConversationStream] conversationUpdated error:\", err),\n },\n );\n\n useSubscription<ParticipantChangedSubscription>(ParticipantChangedDocument, {\n variables: { conversationId: conversationId ?? \"\" },\n skip: !conversationId,\n onError: (err) =>\n console.warn(\"[useConversationStream] participantChanged error:\", err),\n });\n}\n"],"mappings":";;;;AAoDA,SAAS,EACP,GACA,GACA,GACM;AACN,GAAM,YACJ;EACE,OAAO;EACP,WAAW;GAAE;GAAgB,OAAO;GAAI;EACzC,GACA,MAAsC;AACrC,MAAI,CAAC,EAAU,QAAO;EACtB,IAAM,IAAO,EAAS,UAChB,IAAQ,GAAM,SAAS,EAAE;AAG/B,SAFI,EAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAW,GAAG,GAAS,IAEtD;GACL,GAAG;GACH,UAAU;IACR,GAAI,KAAQ,EAAE;IACd,OAAO,CACL,GAAG,GACH;KACE,YAAY;KACZ,QAAQ,EAAW;KACnB,MAAM;KACP,CACF;IACD,aAAa,GAAM,cAAc,EAAM,UAAU;IAClD;GACF;GAEJ;;AAMH,SAAS,EAAe,GAAoB,GAAyB;AAEnE,CADA,EAAM,MAAM,EAAE,IAAI,EAAM,SAAS;EAAE,YAAY;EAAW,IAAI;EAAW,CAAC,EAAE,CAAC,EAC7E,EAAM,IAAI;;AAGZ,SAAgB,EACd,GACM;CACN,IAAM,IAAS,GAAiB;AAsChC,CAnCA,EAA0C,GAAsB;EAC9D,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EAAE,cAA4D;GACrE,IAAM,IAAM,EAAK,MAAM;AACnB,IAAC,KAAO,CAAC,KACb,EAAc,EAAO,OAAO,GAAgB,EAAsB;;EAEpE,UAAU,MACR,QAAQ,KAAK,0CAA0C,EAAI;EAC9D,CAAC,EAGF,EAA4C,GAAwB;EAClE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,4CAA4C,EAAI;EAEhE,CAAC,EAGF,EAA4C,GAAwB;EAClE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EAAE,cAA8D;GACvE,IAAM,IAAY,EAAK,MAAM,gBAAgB;AACxC,QACL,EAAe,EAAO,OAAO,EAAU;;EAEzC,UAAU,MACR,QAAQ,KAAK,4CAA4C,EAAI;EAChE,CAAC,EAGF,cAAsB,KAAA,GAAW,CAAC,EAAe,CAAC;;AAgCpD,SAAgB,EACd,GACM;CACN,IAAM,IAAS,GAAiB;AAmDhC,CAjDA,EAA+C,GAA2B;EACxE,SAAS,EACP,cAGI;AACS,KAAK,MAAM,sBAIxB,EAAO,MAAM,MAAM,EAAE,WAAW,iBAAiB,CAAC,EAClD,EAAO,MAAM,IAAI;;EAEnB,UAAU,MACR,QAAQ,KAAK,oDAAoD,EAAI;EACxE,CAAC,EAEF,EACE,GACA;EACE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,SAAS,EACP,cAGI;GACJ,IAAM,IAAK,EAAK,MAAM,qBAAqB;AACtC,SACL,EAAO,MAAM,MAAM,EACjB,IAAI,EAAO,MAAM,SAAS;IAAE,YAAY;IAAgB;IAAI,CAAC,EAC9D,CAAC,EACF,EAAO,MAAM,IAAI;;EAEnB,UAAU,MACR,QAAQ,KAAK,sDAAsD,EAAI;EAC1E,CACF,EAED,EACE,GACA;EACE,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,sDAAsD,EAAI;EAC1E,CACF,EAED,EAAgD,GAA4B;EAC1E,WAAW,EAAE,gBAAgB,KAAkB,IAAI;EACnD,MAAM,CAAC;EACP,UAAU,MACR,QAAQ,KAAK,qDAAqD,EAAI;EACzE,CAAC"}
@@ -13,7 +13,7 @@ var d = [
13
13
  descriptionFallback: "Reported and flagged messages, moderation actions, and reviewer assignments.",
14
14
  path: "moderation",
15
15
  icon: /* @__PURE__ */ l(s, {
16
- className: "h-5 w-5",
16
+ className: "size-5",
17
17
  "aria-hidden": "true"
18
18
  })
19
19
  },
@@ -25,7 +25,7 @@ var d = [
25
25
  descriptionFallback: "Bulk archive / unarchive, participant edits, and cross-conversation actions.",
26
26
  path: "bulk",
27
27
  icon: /* @__PURE__ */ l(i, {
28
- className: "h-5 w-5",
28
+ className: "size-5",
29
29
  "aria-hidden": "true"
30
30
  })
31
31
  },
@@ -37,7 +37,7 @@ var d = [
37
37
  descriptionFallback: "Activity stream for admin / moderator actions on workspace conversations.",
38
38
  path: "audit",
39
39
  icon: /* @__PURE__ */ l(n, {
40
- className: "h-5 w-5",
40
+ className: "size-5",
41
41
  "aria-hidden": "true"
42
42
  })
43
43
  },
@@ -49,7 +49,7 @@ var d = [
49
49
  descriptionFallback: "Install, configure, and remove conversation apps and integrations.",
50
50
  path: "apps",
51
51
  icon: /* @__PURE__ */ l(o, {
52
- className: "h-5 w-5",
52
+ className: "size-5",
53
53
  "aria-hidden": "true"
54
54
  })
55
55
  }
@@ -69,7 +69,7 @@ function f() {
69
69
  children: [/* @__PURE__ */ l("div", {
70
70
  className: "p-2 rounded-md bg-muted",
71
71
  children: /* @__PURE__ */ l(a, {
72
- className: "h-5 w-5 text-muted-foreground",
72
+ className: "size-5 text-muted-foreground",
73
73
  "aria-hidden": "true"
74
74
  })
75
75
  }), /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h1", {
@@ -98,7 +98,7 @@ function f() {
98
98
  className: "text-sm font-medium text-foreground",
99
99
  children: e.label
100
100
  }), /* @__PURE__ */ l(r, {
101
- className: "h-4 w-4 text-muted-foreground",
101
+ className: "size-4 text-muted-foreground",
102
102
  "aria-hidden": "true"
103
103
  })]
104
104
  }), /* @__PURE__ */ l("p", {
@@ -1 +1 @@
1
- {"version":3,"file":"AdminConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/AdminConversationsLandingPage.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport { Link } from \"react-router-dom\";\nimport {\n MessageCircle,\n ShieldCheck,\n ListChecks,\n Activity,\n Package,\n ChevronRight,\n} from \"lucide-react\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface AdminSectionDefinition {\n id: string;\n /** i18n key used for the card label */\n labelKey: string;\n /** English fallback rendered when the i18n key is not resolved */\n labelFallback: string;\n /** i18n key used for the card description */\n descriptionKey: string;\n /** English fallback for the description */\n descriptionFallback: string;\n /** Route path relative to the admin basePath */\n path: string;\n icon: ReactNode;\n}\n\n// Module-scoped section catalogue. The icon JSX is stable (purely presentational,\n// no context or dynamic props) so constructing once at parse time avoids rebuilding\n// four lucide nodes on every render. If icons ever need to respond to context\n// (theme, RBAC), move the icon field into the useMemo mapper below.\nconst SECTION_DEFINITIONS: AdminSectionDefinition[] = [\n {\n id: \"moderation\",\n labelKey: \"conversations.admin.moderation\",\n labelFallback: \"Moderation\",\n descriptionKey: \"conversations.admin.moderationDescription\",\n descriptionFallback:\n \"Reported and flagged messages, moderation actions, and reviewer assignments.\",\n path: \"moderation\",\n icon: <ShieldCheck className=\"h-5 w-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"bulk\",\n labelKey: \"conversations.admin.bulkOperations\",\n labelFallback: \"Bulk Operations\",\n descriptionKey: \"conversations.admin.bulkOperationsDescription\",\n descriptionFallback:\n \"Bulk archive / unarchive, participant edits, and cross-conversation actions.\",\n path: \"bulk\",\n icon: <ListChecks className=\"h-5 w-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"audit\",\n labelKey: \"conversations.admin.auditLog\",\n labelFallback: \"Audit Log\",\n descriptionKey: \"conversations.admin.auditLogDescription\",\n descriptionFallback:\n \"Activity stream for admin / moderator actions on workspace conversations.\",\n path: \"audit\",\n icon: <Activity className=\"h-5 w-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"apps\",\n labelKey: \"conversations.admin.appInstallations\",\n labelFallback: \"App Installations\",\n descriptionKey: \"conversations.admin.appInstallationsDescription\",\n descriptionFallback:\n \"Install, configure, and remove conversation apps and integrations.\",\n path: \"apps\",\n icon: <Package className=\"h-5 w-5\" aria-hidden=\"true\" />,\n },\n];\n\n/**\n * Admin-facing landing page for the Conversations microfrontend.\n *\n * Lightweight overview + section directory. Each card links to its canonical\n * sub-route (`/moderation`, `/bulk`, `/audit`, `/apps`) for the API-backed\n * admin surfaces.\n */\nexport function AdminConversationsLandingPage() {\n const tr = useTr();\n\n const sections = useMemo(\n () =>\n SECTION_DEFINITIONS.map((section) => ({\n ...section,\n label: tr(section.labelKey, section.labelFallback),\n description: tr(section.descriptionKey, section.descriptionFallback),\n })),\n [tr],\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"border-b border-border-subtle px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-md bg-muted\">\n <MessageCircle\n className=\"h-5 w-5 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n <div>\n <h1 className=\"text-lg font-semibold text-foreground\">\n {tr(\"conversations.admin.landingTitle\", \"Conversations admin\")}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n \"conversations.admin.landingDescription\",\n \"Governance tools for workspace conversations. Select a section below to continue.\",\n )}\n </p>\n </div>\n </div>\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto p-6\">\n <div className=\"grid gap-3 sm:grid-cols-2 max-w-3xl\">\n {sections.map((section) => (\n <Link\n key={section.id}\n to={section.path}\n className=\"group flex items-start gap-3 rounded-lg border border-border-subtle bg-card p-4 transition-colors hover:border-border hover:bg-accent\"\n >\n <div className=\"p-2 rounded-md bg-muted text-muted-foreground group-hover:text-foreground\">\n {section.icon}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-foreground\">\n {section.label}\n </span>\n <ChevronRight\n className=\"h-4 w-4 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {section.description}\n </p>\n </div>\n </Link>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAgCA,IAAM,IAAgD;CACpD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAa,WAAU;GAAU,eAAY;GAAS,CAAA;EAC7D;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAY,WAAU;GAAU,eAAY;GAAS,CAAA;EAC5D;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAU,WAAU;GAAU,eAAY;GAAS,CAAA;EAC1D;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAS,WAAU;GAAU,eAAY;GAAS,CAAA;EACzD;CACF;AASD,SAAgB,IAAgC;CAC9C,IAAM,IAAK,GAAO,EAEZ,IAAW,QAEb,EAAoB,KAAK,OAAa;EACpC,GAAG;EACH,OAAO,EAAG,EAAQ,UAAU,EAAQ,cAAc;EAClD,aAAa,EAAG,EAAQ,gBAAgB,EAAQ,oBAAoB;EACrE,EAAE,EACL,CAAC,EAAG,CACL;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,WAAU;MACV,eAAY;MACZ,CAAA;KACE,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,oCAAoC,sBAAsB;KAC3D,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0CACA,oFACD;KACC,CAAA,CACA,EAAA,CAAA,CACF;;GACF,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,GAAD;KAEE,IAAI,EAAQ;KACZ,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ;MACL,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAQ;QACJ,CAAA,EACP,kBAAC,GAAD;QACE,WAAU;QACV,eAAY;QACZ,CAAA,CACE;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ;OACP,CAAA,CACA;QACD;OArBA,EAAQ,GAqBR,CACP;IACE,CAAA;GACF,CAAA,CACF"}
1
+ {"version":3,"file":"AdminConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/AdminConversationsLandingPage.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport { Link } from \"react-router-dom\";\nimport {\n MessageCircle,\n ShieldCheck,\n ListChecks,\n Activity,\n Package,\n ChevronRight,\n} from \"lucide-react\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface AdminSectionDefinition {\n id: string;\n /** i18n key used for the card label */\n labelKey: string;\n /** English fallback rendered when the i18n key is not resolved */\n labelFallback: string;\n /** i18n key used for the card description */\n descriptionKey: string;\n /** English fallback for the description */\n descriptionFallback: string;\n /** Route path relative to the admin basePath */\n path: string;\n icon: ReactNode;\n}\n\n// Module-scoped section catalogue. The icon JSX is stable (purely presentational,\n// no context or dynamic props) so constructing once at parse time avoids rebuilding\n// four lucide nodes on every render. If icons ever need to respond to context\n// (theme, RBAC), move the icon field into the useMemo mapper below.\nconst SECTION_DEFINITIONS: AdminSectionDefinition[] = [\n {\n id: \"moderation\",\n labelKey: \"conversations.admin.moderation\",\n labelFallback: \"Moderation\",\n descriptionKey: \"conversations.admin.moderationDescription\",\n descriptionFallback:\n \"Reported and flagged messages, moderation actions, and reviewer assignments.\",\n path: \"moderation\",\n icon: <ShieldCheck className=\"size-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"bulk\",\n labelKey: \"conversations.admin.bulkOperations\",\n labelFallback: \"Bulk Operations\",\n descriptionKey: \"conversations.admin.bulkOperationsDescription\",\n descriptionFallback:\n \"Bulk archive / unarchive, participant edits, and cross-conversation actions.\",\n path: \"bulk\",\n icon: <ListChecks className=\"size-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"audit\",\n labelKey: \"conversations.admin.auditLog\",\n labelFallback: \"Audit Log\",\n descriptionKey: \"conversations.admin.auditLogDescription\",\n descriptionFallback:\n \"Activity stream for admin / moderator actions on workspace conversations.\",\n path: \"audit\",\n icon: <Activity className=\"size-5\" aria-hidden=\"true\" />,\n },\n {\n id: \"apps\",\n labelKey: \"conversations.admin.appInstallations\",\n labelFallback: \"App Installations\",\n descriptionKey: \"conversations.admin.appInstallationsDescription\",\n descriptionFallback:\n \"Install, configure, and remove conversation apps and integrations.\",\n path: \"apps\",\n icon: <Package className=\"size-5\" aria-hidden=\"true\" />,\n },\n];\n\n/**\n * Admin-facing landing page for the Conversations microfrontend.\n *\n * Lightweight overview + section directory. Each card links to its canonical\n * sub-route (`/moderation`, `/bulk`, `/audit`, `/apps`) for the API-backed\n * admin surfaces.\n */\nexport function AdminConversationsLandingPage() {\n const tr = useTr();\n\n const sections = useMemo(\n () =>\n SECTION_DEFINITIONS.map((section) => ({\n ...section,\n label: tr(section.labelKey, section.labelFallback),\n description: tr(section.descriptionKey, section.descriptionFallback),\n })),\n [tr],\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"border-b border-border-subtle px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-md bg-muted\">\n <MessageCircle\n className=\"size-5 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n <div>\n <h1 className=\"text-lg font-semibold text-foreground\">\n {tr(\"conversations.admin.landingTitle\", \"Conversations admin\")}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n \"conversations.admin.landingDescription\",\n \"Governance tools for workspace conversations. Select a section below to continue.\",\n )}\n </p>\n </div>\n </div>\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto p-6\">\n <div className=\"grid gap-3 sm:grid-cols-2 max-w-3xl\">\n {sections.map((section) => (\n <Link\n key={section.id}\n to={section.path}\n className=\"group flex items-start gap-3 rounded-lg border border-border-subtle bg-card p-4 transition-colors hover:border-border hover:bg-accent\"\n >\n <div className=\"p-2 rounded-md bg-muted text-muted-foreground group-hover:text-foreground\">\n {section.icon}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-foreground\">\n {section.label}\n </span>\n <ChevronRight\n className=\"size-4 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {section.description}\n </p>\n </div>\n </Link>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAgCA,IAAM,IAAgD;CACpD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAa,WAAU;GAAS,eAAY;GAAS,CAAA;EAC5D;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAY,WAAU;GAAS,eAAY;GAAS,CAAA;EAC3D;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAU,WAAU;GAAS,eAAY;GAAS,CAAA;EACzD;CACD;EACE,IAAI;EACJ,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,qBACE;EACF,MAAM;EACN,MAAM,kBAAC,GAAD;GAAS,WAAU;GAAS,eAAY;GAAS,CAAA;EACxD;CACF;AASD,SAAgB,IAAgC;CAC9C,IAAM,IAAK,GAAO,EAEZ,IAAW,QAEb,EAAoB,KAAK,OAAa;EACpC,GAAG;EACH,OAAO,EAAG,EAAQ,UAAU,EAAQ,cAAc;EAClD,aAAa,EAAG,EAAQ,gBAAgB,EAAQ,oBAAoB;EACrE,EAAE,EACL,CAAC,EAAG,CACL;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,WAAU;MACV,eAAY;MACZ,CAAA;KACE,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,oCAAoC,sBAAsB;KAC3D,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0CACA,oFACD;KACC,CAAA,CACA,EAAA,CAAA,CACF;;GACF,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,GAAD;KAEE,IAAI,EAAQ;KACZ,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ;MACL,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAQ;QACJ,CAAA,EACP,kBAAC,GAAD;QACE,WAAU;QACV,eAAY;QACZ,CAAA,CACE;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ;OACP,CAAA,CACA;QACD;OArBA,EAAQ,GAqBR,CACP;IACE,CAAA;GACF,CAAA,CACF"}
@@ -1,6 +1,8 @@
1
1
  import { useConversations as e } from "../providers/ConversationsProvider.js";
2
2
  import { useTr as t } from "../utils/i18n.js";
3
- import { EmptyState as n, PageLayout as r, PageSection as i } from "../components/PageLayout.js";
3
+ import { PageSection as n } from "../components/PageSection.js";
4
+ import { EmptyState as r } from "../components/EmptyState.js";
5
+ import { PageLayout as i } from "../components/PageLayout.js";
4
6
  import { useEffect as a, useMemo as o, useState as s } from "react";
5
7
  import { Activity as c, Archive as l, CheckCircle2 as u, Package as d, ShieldCheck as f, Trash2 as p } from "lucide-react";
6
8
  import { gql as m } from "@apollo/client";
@@ -147,45 +149,46 @@ function O(e) {
147
149
  return "Message content unavailable";
148
150
  }
149
151
  }
150
- function k(e) {
151
- return new Intl.DateTimeFormat(void 0, {
152
- dateStyle: "medium",
153
- timeStyle: "short"
154
- }).format(new Date(e));
152
+ var k = new Intl.DateTimeFormat(void 0, {
153
+ dateStyle: "medium",
154
+ timeStyle: "short"
155
+ });
156
+ function A(e) {
157
+ return k.format(new Date(e));
155
158
  }
156
- function A({ children: e }) {
159
+ function j({ children: e }) {
157
160
  return /* @__PURE__ */ _("span", {
158
161
  className: "rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground",
159
162
  children: e
160
163
  });
161
164
  }
162
- function j() {
163
- let e = t(), { data: i, loading: a, error: o, refetch: s } = g(y, { variables: {
165
+ function M() {
166
+ let e = t(), { data: n, loading: a, error: o, refetch: s } = g(y, { variables: {
164
167
  first: 50,
165
168
  filter: { statuses: ["PENDING", "REVIEWING"] }
166
- } }), [c, { loading: l }] = h(b, { onCompleted: () => void s() }), d = i?.messageReports.edges.map((e) => e.node) ?? [];
167
- return /* @__PURE__ */ v(r, {
169
+ } }), [c, { loading: l }] = h(b, { onCompleted: () => void s() }), d = n?.messageReports.edges.map((e) => e.node) ?? [];
170
+ return /* @__PURE__ */ v(i, {
168
171
  title: e("conversations.admin.moderationPageTitle", "Moderation queue"),
169
172
  subtitle: e("conversations.admin.moderationPageDescription", "Review reported messages and apply moderator decisions."),
170
173
  icon: /* @__PURE__ */ _(f, {
171
- className: "h-5 w-5",
174
+ className: "size-5",
172
175
  "aria-hidden": "true"
173
176
  }),
174
177
  maxWidth: "2xl",
175
178
  children: [
176
179
  a && /* @__PURE__ */ _("p", {
177
180
  className: "text-sm text-muted-foreground",
178
- children: "Loading reports..."
181
+ children: "Loading reports…"
179
182
  }),
180
183
  o && /* @__PURE__ */ _("p", {
181
184
  className: "text-sm text-destructive",
182
185
  children: o.message
183
186
  }),
184
- !a && !o && d.length === 0 && /* @__PURE__ */ _(n, {
187
+ !a && !o && d.length === 0 && /* @__PURE__ */ _(r, {
185
188
  title: "No reports waiting for review",
186
189
  description: "Reported messages will appear here as they are submitted.",
187
190
  icon: /* @__PURE__ */ _(u, {
188
- className: "h-6 w-6",
191
+ className: "size-6",
189
192
  "aria-hidden": "true"
190
193
  })
191
194
  }),
@@ -201,11 +204,11 @@ function j() {
201
204
  /* @__PURE__ */ v("div", {
202
205
  className: "flex flex-wrap items-center gap-2",
203
206
  children: [
204
- /* @__PURE__ */ _(A, { children: e.status }),
205
- e.message.isHidden && /* @__PURE__ */ _(A, { children: "Hidden" }),
207
+ /* @__PURE__ */ _(j, { children: e.status }),
208
+ e.message.isHidden && /* @__PURE__ */ _(j, { children: "Hidden" }),
206
209
  /* @__PURE__ */ v("span", {
207
210
  className: "text-xs text-muted-foreground",
208
- children: ["Reported ", k(e.createdAt)]
211
+ children: ["Reported ", A(e.createdAt)]
209
212
  })
210
213
  ]
211
214
  }),
@@ -260,20 +263,20 @@ function j() {
260
263
  ]
261
264
  });
262
265
  }
263
- function M() {
264
- let e = t(), [n, i] = s(() => /* @__PURE__ */ new Set()), [a, o] = s("ARCHIVE"), { data: c, loading: u, error: d, refetch: f } = g(x, { variables: { first: 100 } }), [p, { data: m, loading: y }] = h(S, { onCompleted: () => {
265
- i(/* @__PURE__ */ new Set()), f();
266
+ function N() {
267
+ let e = t(), [n, r] = s(() => /* @__PURE__ */ new Set()), [a, o] = s("ARCHIVE"), { data: c, loading: u, error: d, refetch: f } = g(x, { variables: { first: 100 } }), [p, { data: m, loading: y }] = h(S, { onCompleted: () => {
268
+ r(/* @__PURE__ */ new Set()), f();
266
269
  } }), b = c?.conversations.edges.map((e) => e.node) ?? [], C = (e) => {
267
- i((t) => {
270
+ r((t) => {
268
271
  let n = new Set(t);
269
272
  return n.has(e) ? n.delete(e) : n.add(e), n;
270
273
  });
271
274
  };
272
- return /* @__PURE__ */ v(r, {
275
+ return /* @__PURE__ */ v(i, {
273
276
  title: e("conversations.admin.bulkOperationsPageTitle", "Bulk operations"),
274
277
  subtitle: e("conversations.admin.bulkOperationsPageDescription", "Select workspace conversations and apply archive, unarchive, or delete operations."),
275
278
  icon: /* @__PURE__ */ _(l, {
276
- className: "h-5 w-5",
279
+ className: "size-5",
277
280
  "aria-hidden": "true"
278
281
  }),
279
282
  maxWidth: "2xl",
@@ -311,7 +314,7 @@ function M() {
311
314
  children: [
312
315
  u && /* @__PURE__ */ _("p", {
313
316
  className: "text-sm text-muted-foreground",
314
- children: "Loading conversations..."
317
+ children: "Loading conversations…"
315
318
  }),
316
319
  d && /* @__PURE__ */ _("p", {
317
320
  className: "text-sm text-destructive",
@@ -333,6 +336,7 @@ function M() {
333
336
  className: "flex cursor-pointer items-center gap-3 border-b border-border px-4 py-3 last:border-b-0 hover:bg-accent",
334
337
  children: [/* @__PURE__ */ _("input", {
335
338
  type: "checkbox",
339
+ "aria-label": `Select ${e.title || "untitled conversation"}`,
336
340
  checked: n.has(e.id),
337
341
  onChange: () => C(e.id)
338
342
  }), /* @__PURE__ */ v("div", {
@@ -348,7 +352,7 @@ function M() {
348
352
  e.state,
349
353
  " · Updated",
350
354
  " ",
351
- k(e.updatedAt)
355
+ A(e.updatedAt)
352
356
  ]
353
357
  })]
354
358
  })]
@@ -357,8 +361,8 @@ function M() {
357
361
  ]
358
362
  });
359
363
  }
360
- function N() {
361
- let i = t(), { workspaceId: a } = e(), { data: o, loading: s, error: l } = g(C, {
364
+ function P() {
365
+ let n = t(), { workspaceId: a } = e(), { data: o, loading: s, error: l } = g(C, {
362
366
  variables: { filter: {
363
367
  workspaceId: a,
364
368
  resourceType: "conversation",
@@ -366,24 +370,24 @@ function N() {
366
370
  } },
367
371
  skip: !a
368
372
  }), u = o?.getActivities ?? [];
369
- return /* @__PURE__ */ v(r, {
370
- title: i("conversations.admin.auditLogPageTitle", "Audit log"),
371
- subtitle: i("conversations.admin.auditLogPageDescription", "Activity stream for admin and moderator actions on workspace conversations."),
373
+ return /* @__PURE__ */ v(i, {
374
+ title: n("conversations.admin.auditLogPageTitle", "Audit log"),
375
+ subtitle: n("conversations.admin.auditLogPageDescription", "Activity stream for admin and moderator actions on workspace conversations."),
372
376
  icon: /* @__PURE__ */ _(c, {
373
- className: "h-5 w-5",
377
+ className: "size-5",
374
378
  "aria-hidden": "true"
375
379
  }),
376
380
  maxWidth: "2xl",
377
381
  children: [
378
382
  s && /* @__PURE__ */ _("p", {
379
383
  className: "text-sm text-muted-foreground",
380
- children: "Loading activity..."
384
+ children: "Loading activity…"
381
385
  }),
382
386
  l && /* @__PURE__ */ _("p", {
383
387
  className: "text-sm text-destructive",
384
388
  children: l.message
385
389
  }),
386
- !s && !l && u.length === 0 && /* @__PURE__ */ _(n, {
390
+ !s && !l && u.length === 0 && /* @__PURE__ */ _(r, {
387
391
  title: "No activity found",
388
392
  description: "Conversation admin actions will appear here."
389
393
  }),
@@ -399,7 +403,7 @@ function N() {
399
403
  children: e.action
400
404
  }), /* @__PURE__ */ _("span", {
401
405
  className: "text-xs text-muted-foreground",
402
- children: k(e.timestamp)
406
+ children: A(e.timestamp)
403
407
  })]
404
408
  }),
405
409
  /* @__PURE__ */ _("p", {
@@ -421,8 +425,8 @@ function N() {
421
425
  ]
422
426
  });
423
427
  }
424
- function P() {
425
- let n = t(), { workspaceId: c } = e(), [l, u] = s(""), [f, m] = s("{}"), [y, b] = s({}), [x, S] = s(null), { data: C, loading: O, error: j, refetch: M } = g(w, {
428
+ function F() {
429
+ let r = t(), { workspaceId: c } = e(), [l, u] = s(""), [f, m] = s("{}"), [y, b] = s({}), [x, S] = s(null), { data: C, loading: O, error: k, refetch: M } = g(w, {
426
430
  variables: { workspaceId: c },
427
431
  skip: !c
428
432
  }), [N, { loading: P }] = h(T, { onCompleted: () => void M() }), [F, { loading: I }] = h(E, { onCompleted: () => void M() }), [L, { loading: R }] = h(D, { onCompleted: () => void M() }), z = o(() => C?.appInstallations ?? [], [C?.appInstallations]);
@@ -442,16 +446,16 @@ function P() {
442
446
  return null;
443
447
  }
444
448
  };
445
- return /* @__PURE__ */ v(r, {
446
- title: n("conversations.admin.appInstallationsPageTitle", "App installations"),
447
- subtitle: n("conversations.admin.appInstallationsPageDescription", "Install, configure, and remove conversation apps and integrations."),
449
+ return /* @__PURE__ */ v(i, {
450
+ title: r("conversations.admin.appInstallationsPageTitle", "App installations"),
451
+ subtitle: r("conversations.admin.appInstallationsPageDescription", "Install, configure, and remove conversation apps and integrations."),
448
452
  icon: /* @__PURE__ */ _(d, {
449
- className: "h-5 w-5",
453
+ className: "size-5",
450
454
  "aria-hidden": "true"
451
455
  }),
452
456
  maxWidth: "2xl",
453
457
  children: [
454
- /* @__PURE__ */ v(i, {
458
+ /* @__PURE__ */ v(n, {
455
459
  title: "Install app",
456
460
  description: "Install a workspace-scoped conversation app by app ID.",
457
461
  children: [/* @__PURE__ */ v("div", {
@@ -461,12 +465,14 @@ function P() {
461
465
  value: l,
462
466
  onChange: (e) => u(e.target.value),
463
467
  placeholder: "app-id",
468
+ "aria-label": "App ID",
464
469
  className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground"
465
470
  }),
466
471
  /* @__PURE__ */ _("textarea", {
467
472
  value: f,
468
473
  onChange: (e) => m(e.target.value),
469
474
  rows: 3,
475
+ "aria-label": "App settings JSON",
470
476
  className: "rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground"
471
477
  }),
472
478
  /* @__PURE__ */ _("button", {
@@ -495,11 +501,11 @@ function P() {
495
501
  }),
496
502
  O && /* @__PURE__ */ _("p", {
497
503
  className: "text-sm text-muted-foreground",
498
- children: "Loading app installations..."
504
+ children: "Loading app installations…"
499
505
  }),
500
- j && /* @__PURE__ */ _("p", {
506
+ k && /* @__PURE__ */ _("p", {
501
507
  className: "text-sm text-destructive",
502
- children: j.message
508
+ children: k.message
503
509
  }),
504
510
  /* @__PURE__ */ _("div", {
505
511
  className: "grid gap-3",
@@ -512,7 +518,7 @@ function P() {
512
518
  children: [/* @__PURE__ */ _("h3", {
513
519
  className: "text-sm font-semibold text-foreground",
514
520
  children: e.appId
515
- }), /* @__PURE__ */ _(A, { children: e.state })]
521
+ }), /* @__PURE__ */ _(j, { children: e.state })]
516
522
  }), /* @__PURE__ */ v("p", {
517
523
  className: "text-xs text-muted-foreground",
518
524
  children: [
@@ -523,7 +529,7 @@ function P() {
523
529
  e.installedBy,
524
530
  " on",
525
531
  " ",
526
- k(e.installedAt)
532
+ A(e.installedAt)
527
533
  ]
528
534
  })] }), /* @__PURE__ */ v("button", {
529
535
  type: "button",
@@ -531,7 +537,7 @@ function P() {
531
537
  className: "inline-flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground hover:bg-accent disabled:opacity-50",
532
538
  onClick: () => void L({ variables: { id: e.id } }),
533
539
  children: [/* @__PURE__ */ _(p, {
534
- className: "h-4 w-4",
540
+ className: "size-4",
535
541
  "aria-hidden": "true"
536
542
  }), "Uninstall"]
537
543
  })]
@@ -539,6 +545,7 @@ function P() {
539
545
  className: "mt-3 grid gap-2",
540
546
  children: [/* @__PURE__ */ _("textarea", {
541
547
  rows: 5,
548
+ "aria-label": `Settings for ${e.appId}`,
542
549
  value: y[e.id] ?? "{}",
543
550
  onChange: (t) => b((n) => ({
544
551
  ...n,
@@ -569,6 +576,6 @@ function P() {
569
576
  });
570
577
  }
571
578
  //#endregion
572
- export { P as AppInstallationsPage, M as BulkOperationsPage, N as ConversationAuditLogPage, j as ModerationQueuePage };
579
+ export { F as AppInstallationsPage, N as BulkOperationsPage, P as ConversationAuditLogPage, M as ModerationQueuePage };
573
580
 
574
581
  //# sourceMappingURL=ConversationAdminPages.js.map