@burdenoff/microfe-conversations 2026.525.3 → 2026.527.2
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.
- package/dist/conversations/ConversationsUserRoutes.js +4 -3
- package/dist/conversations/ConversationsUserRoutes.js.map +1 -1
- package/dist/conversations/components/EmptyState.js +27 -0
- package/dist/conversations/components/EmptyState.js.map +1 -0
- package/dist/conversations/components/LazyPageBoundary.js +26 -42
- package/dist/conversations/components/LazyPageBoundary.js.map +1 -1
- package/dist/conversations/components/MessageBubble.js +3 -1
- package/dist/conversations/components/MessageBubble.js.map +1 -1
- package/dist/conversations/components/PageLayout.js +3 -39
- package/dist/conversations/components/PageLayout.js.map +1 -1
- package/dist/conversations/components/PageSection.js +25 -0
- package/dist/conversations/components/PageSection.js.map +1 -0
- package/dist/conversations/components/PermissionGuard.js +1 -1
- package/dist/conversations/components/PermissionGuard.js.map +1 -1
- package/dist/conversations/components/lazyRetry.js +23 -0
- package/dist/conversations/components/lazyRetry.js.map +1 -0
- package/dist/conversations/constants/permissions.js +0 -1
- package/dist/conversations/constants/permissions.js.map +1 -1
- package/dist/conversations/hooks/useMessageQueries.js.map +1 -1
- package/dist/conversations/hooks/useMessageSubscriptions.js.map +1 -1
- package/dist/conversations/pages/AdminConversationsLandingPage.js +6 -6
- package/dist/conversations/pages/AdminConversationsLandingPage.js.map +1 -1
- package/dist/conversations/pages/ConversationAdminPages.js +56 -49
- package/dist/conversations/pages/ConversationAdminPages.js.map +1 -1
- package/dist/conversations/pages/ConversationDetailPage.js +3 -2
- package/dist/conversations/pages/ConversationDetailPage.js.map +1 -1
- package/dist/conversations/pages/ConversationListPage.js +133 -136
- package/dist/conversations/pages/ConversationListPage.js.map +1 -1
- package/dist/conversations/pages/ConversationsLandingPage.js +50 -57
- package/dist/conversations/pages/ConversationsLandingPage.js.map +1 -1
- package/dist/conversations/pages/ConversationsSettingsPage.js +8 -5
- package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
- package/dist/conversations/pages/NewConversationPage.js +87 -88
- package/dist/conversations/pages/NewConversationPage.js.map +1 -1
- package/dist/conversations/providers/ConversationsProvider.js +1 -1
- package/dist/conversations/providers/ConversationsProvider.js.map +1 -1
- package/dist/conversations/utils/mappers.js.map +1 -1
- 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"],"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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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=\"
|
|
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 {
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
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
|
|
163
|
-
let e = t(), { data:
|
|
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 =
|
|
167
|
-
return /* @__PURE__ */ v(
|
|
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: "
|
|
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__ */ _(
|
|
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: "
|
|
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__ */ _(
|
|
205
|
-
e.message.isHidden && /* @__PURE__ */ _(
|
|
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 ",
|
|
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
|
|
264
|
-
let e = t(), [n,
|
|
265
|
-
|
|
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
|
-
|
|
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(
|
|
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: "
|
|
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
|
-
|
|
355
|
+
A(e.updatedAt)
|
|
352
356
|
]
|
|
353
357
|
})]
|
|
354
358
|
})]
|
|
@@ -357,8 +361,8 @@ function M() {
|
|
|
357
361
|
]
|
|
358
362
|
});
|
|
359
363
|
}
|
|
360
|
-
function
|
|
361
|
-
let
|
|
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(
|
|
370
|
-
title:
|
|
371
|
-
subtitle:
|
|
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: "
|
|
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__ */ _(
|
|
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:
|
|
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
|
|
425
|
-
let
|
|
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(
|
|
446
|
-
title:
|
|
447
|
-
subtitle:
|
|
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: "
|
|
453
|
+
className: "size-5",
|
|
450
454
|
"aria-hidden": "true"
|
|
451
455
|
}),
|
|
452
456
|
maxWidth: "2xl",
|
|
453
457
|
children: [
|
|
454
|
-
/* @__PURE__ */ v(
|
|
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
|
-
|
|
506
|
+
k && /* @__PURE__ */ _("p", {
|
|
501
507
|
className: "text-sm text-destructive",
|
|
502
|
-
children:
|
|
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__ */ _(
|
|
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
|
-
|
|
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: "
|
|
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 {
|
|
579
|
+
export { F as AppInstallationsPage, N as BulkOperationsPage, P as ConversationAuditLogPage, M as ModerationQueuePage };
|
|
573
580
|
|
|
574
581
|
//# sourceMappingURL=ConversationAdminPages.js.map
|