@open-mercato/core 0.6.6-develop.6256.1.9fc16aedc4 → 0.6.6-develop.6257.1.6d0af84d26

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 (30) hide show
  1. package/dist/modules/messages/api/[id]/conversation/archive/route.js +25 -7
  2. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
  3. package/dist/modules/messages/api/[id]/conversation/read/route.js +24 -6
  4. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
  5. package/dist/modules/messages/api/[id]/route.js +13 -1
  6. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  7. package/dist/modules/messages/api/openapi.js +3 -1
  8. package/dist/modules/messages/api/openapi.js.map +2 -2
  9. package/dist/modules/messages/commands/conversation.js +77 -0
  10. package/dist/modules/messages/commands/conversation.js.map +2 -2
  11. package/dist/modules/messages/components/MessageDetailPageClient.js +16 -4
  12. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  13. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +18 -0
  14. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  15. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +13 -11
  16. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +2 -2
  17. package/package.json +7 -7
  18. package/src/modules/messages/api/[id]/conversation/archive/route.ts +29 -6
  19. package/src/modules/messages/api/[id]/conversation/read/route.ts +29 -6
  20. package/src/modules/messages/api/[id]/route.ts +15 -0
  21. package/src/modules/messages/api/openapi.ts +2 -0
  22. package/src/modules/messages/commands/conversation.ts +87 -0
  23. package/src/modules/messages/components/MessageDetailPageClient.tsx +16 -4
  24. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +26 -1
  25. package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +29 -14
  26. package/src/modules/messages/components/message-detail/types.ts +2 -0
  27. package/src/modules/messages/i18n/de.json +4 -0
  28. package/src/modules/messages/i18n/en.json +4 -0
  29. package/src/modules/messages/i18n/es.json +4 -0
  30. package/src/modules/messages/i18n/pl.json +4 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/commands/conversation.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { Message, MessageRecipient } from '../data/entities'\nimport { emitMessagesEvent } from '../events'\nimport { assertOrganizationAccess, type MessageScopeInput } from './shared'\n\nconst conversationCommandSchema = z.object({\n anchorMessageId: z.string().uuid(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n userId: z.string().uuid(),\n})\n\ntype ConversationCommandInput = z.infer<typeof conversationCommandSchema>\n\ntype ConversationScope = {\n anchorMessageId: string\n messageIds: string[]\n recipientMessageIds: Set<string>\n senderMessageIds: Set<string>\n}\n\nasync function resolveConversationScope(\n em: EntityManager,\n input: ConversationCommandInput,\n): Promise<ConversationScope> {\n const anchorMessage = await em.findOne(Message, {\n id: input.anchorMessageId,\n tenantId: input.tenantId,\n deletedAt: null,\n })\n\n if (!anchorMessage) throw new Error('Message not found')\n assertOrganizationAccess(input as MessageScopeInput, anchorMessage)\n\n const anchorRecipient = await em.findOne(MessageRecipient, {\n messageId: input.anchorMessageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n\n const canAccessAnchor = anchorMessage.senderUserId === input.userId || Boolean(anchorRecipient)\n if (!canAccessAnchor) throw new Error('Access denied')\n\n const threadId = anchorMessage.threadId ?? anchorMessage.id\n const conversationMessages = await em.find(\n Message,\n {\n threadId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n },\n {\n orderBy: {\n sentAt: 'ASC',\n createdAt: 'ASC',\n id: 'ASC',\n },\n },\n )\n\n const messagesById = new Map<string, Message>(conversationMessages.map((message) => [message.id, message]))\n messagesById.set(anchorMessage.id, anchorMessage)\n\n if (anchorMessage.threadId && !messagesById.has(anchorMessage.threadId)) {\n const threadRoot = await em.findOne(Message, {\n id: anchorMessage.threadId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n if (threadRoot) {\n messagesById.set(threadRoot.id, threadRoot)\n }\n }\n\n let parentMessageId = anchorMessage.parentMessageId ?? null\n while (parentMessageId) {\n if (messagesById.has(parentMessageId)) break\n const parentMessage = await em.findOne(Message, {\n id: parentMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n if (!parentMessage) break\n messagesById.set(parentMessage.id, parentMessage)\n parentMessageId = parentMessage.parentMessageId ?? null\n }\n\n let frontier = Array.from(messagesById.keys())\n while (frontier.length > 0) {\n const children = await em.find(Message, {\n parentMessageId: { $in: frontier },\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n\n const nextFrontier: string[] = []\n for (const child of children) {\n if (messagesById.has(child.id)) continue\n messagesById.set(child.id, child)\n nextFrontier.push(child.id)\n }\n frontier = nextFrontier\n }\n\n const scopedMessages = Array.from(messagesById.values())\n const messageIds = scopedMessages.map((message) => message.id)\n const recipientRows = messageIds.length > 0\n ? await em.find(MessageRecipient, {\n messageId: { $in: messageIds },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n : []\n\n const recipientMessageIds = new Set(recipientRows.map((item) => item.messageId))\n const senderMessageIds = new Set(\n scopedMessages\n .filter((item) => item.senderUserId === input.userId)\n .map((item) => item.id),\n )\n\n const visibleMessageIds = scopedMessages\n .map((item) => item.id)\n .filter((messageId) => recipientMessageIds.has(messageId) || senderMessageIds.has(messageId))\n\n if (visibleMessageIds.length === 0) throw new Error('Access denied')\n\n return {\n anchorMessageId: anchorMessage.id,\n messageIds: visibleMessageIds,\n recipientMessageIds,\n senderMessageIds,\n }\n}\n\nconst archiveConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.archive_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToArchive = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToArchive.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const archivedAt = new Date()\n const archivedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToArchive },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status !== 'archived' || recipient.archivedAt === null) {\n recipient.archivedAt = archivedAt\n recipient.status = 'archived'\n archivedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of archivedIds) {\n await emitMessagesEvent('messages.message.archived', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: archivedIds.length }\n },\n}\n\nconst markConversationUnreadForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.mark_unread_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToMarkUnread = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToMarkUnread.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const markedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToMarkUnread },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status !== 'unread' || recipient.readAt !== null) {\n recipient.status = 'unread'\n recipient.readAt = null\n markedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of markedIds) {\n await emitMessagesEvent('messages.message.marked_unread', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: markedIds.length }\n },\n}\n\ntype DeletedTarget = { messageId: string; target: 'sender' | 'recipient' }\n\nconst deleteConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.delete_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToDelete = scope.messageIds.filter(\n (messageId) => scope.recipientMessageIds.has(messageId) || scope.senderMessageIds.has(messageId),\n )\n\n if (messageIdsToDelete.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const deletedNow = new Date()\n const deletedTargets: DeletedTarget[] = []\n await em.transactional(async (trx) => {\n for (const messageId of messageIdsToDelete) {\n const recipient = await trx.findOne(MessageRecipient, {\n messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (recipient) {\n recipient.status = 'deleted'\n recipient.deletedAt = deletedNow\n deletedTargets.push({ messageId, target: 'recipient' })\n continue\n }\n if (scope.senderMessageIds.has(messageId)) {\n const message = await trx.findOne(Message, {\n id: messageId,\n tenantId: input.tenantId,\n deletedAt: null,\n })\n if (message && message.senderUserId === input.userId) {\n message.deletedAt = deletedNow\n deletedTargets.push({ messageId, target: 'sender' })\n }\n }\n }\n })\n\n for (const { messageId, target } of deletedTargets) {\n await emitMessagesEvent('messages.message.deleted', {\n messageId,\n actorUserId: input.userId,\n recipientUserId: input.userId,\n target,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: deletedTargets.length }\n },\n}\n\nregisterCommand(archiveConversationForActorCommand)\nregisterCommand(markConversationUnreadForActorCommand)\nregisterCommand(deleteConversationForActorCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { Message, MessageRecipient } from '../data/entities'\nimport { emitMessagesEvent } from '../events'\nimport { assertOrganizationAccess, type MessageScopeInput } from './shared'\n\nconst conversationCommandSchema = z.object({\n anchorMessageId: z.string().uuid(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n userId: z.string().uuid(),\n})\n\ntype ConversationCommandInput = z.infer<typeof conversationCommandSchema>\n\ntype ConversationScope = {\n anchorMessageId: string\n messageIds: string[]\n recipientMessageIds: Set<string>\n senderMessageIds: Set<string>\n}\n\nasync function resolveConversationScope(\n em: EntityManager,\n input: ConversationCommandInput,\n): Promise<ConversationScope> {\n const anchorMessage = await em.findOne(Message, {\n id: input.anchorMessageId,\n tenantId: input.tenantId,\n deletedAt: null,\n })\n\n if (!anchorMessage) throw new Error('Message not found')\n assertOrganizationAccess(input as MessageScopeInput, anchorMessage)\n\n const anchorRecipient = await em.findOne(MessageRecipient, {\n messageId: input.anchorMessageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n\n const canAccessAnchor = anchorMessage.senderUserId === input.userId || Boolean(anchorRecipient)\n if (!canAccessAnchor) throw new Error('Access denied')\n\n const threadId = anchorMessage.threadId ?? anchorMessage.id\n const conversationMessages = await em.find(\n Message,\n {\n threadId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n },\n {\n orderBy: {\n sentAt: 'ASC',\n createdAt: 'ASC',\n id: 'ASC',\n },\n },\n )\n\n const messagesById = new Map<string, Message>(conversationMessages.map((message) => [message.id, message]))\n messagesById.set(anchorMessage.id, anchorMessage)\n\n if (anchorMessage.threadId && !messagesById.has(anchorMessage.threadId)) {\n const threadRoot = await em.findOne(Message, {\n id: anchorMessage.threadId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n if (threadRoot) {\n messagesById.set(threadRoot.id, threadRoot)\n }\n }\n\n let parentMessageId = anchorMessage.parentMessageId ?? null\n while (parentMessageId) {\n if (messagesById.has(parentMessageId)) break\n const parentMessage = await em.findOne(Message, {\n id: parentMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n if (!parentMessage) break\n messagesById.set(parentMessage.id, parentMessage)\n parentMessageId = parentMessage.parentMessageId ?? null\n }\n\n let frontier = Array.from(messagesById.keys())\n while (frontier.length > 0) {\n const children = await em.find(Message, {\n parentMessageId: { $in: frontier },\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n isDraft: false,\n })\n\n const nextFrontier: string[] = []\n for (const child of children) {\n if (messagesById.has(child.id)) continue\n messagesById.set(child.id, child)\n nextFrontier.push(child.id)\n }\n frontier = nextFrontier\n }\n\n const scopedMessages = Array.from(messagesById.values())\n const messageIds = scopedMessages.map((message) => message.id)\n const recipientRows = messageIds.length > 0\n ? await em.find(MessageRecipient, {\n messageId: { $in: messageIds },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n : []\n\n const recipientMessageIds = new Set(recipientRows.map((item) => item.messageId))\n const senderMessageIds = new Set(\n scopedMessages\n .filter((item) => item.senderUserId === input.userId)\n .map((item) => item.id),\n )\n\n const visibleMessageIds = scopedMessages\n .map((item) => item.id)\n .filter((messageId) => recipientMessageIds.has(messageId) || senderMessageIds.has(messageId))\n\n if (visibleMessageIds.length === 0) throw new Error('Access denied')\n\n return {\n anchorMessageId: anchorMessage.id,\n messageIds: visibleMessageIds,\n recipientMessageIds,\n senderMessageIds,\n }\n}\n\nconst archiveConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.archive_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToArchive = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToArchive.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const archivedAt = new Date()\n const archivedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToArchive },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status !== 'archived' || recipient.archivedAt === null) {\n recipient.archivedAt = archivedAt\n recipient.status = 'archived'\n archivedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of archivedIds) {\n await emitMessagesEvent('messages.message.archived', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: archivedIds.length }\n },\n}\n\nconst markConversationUnreadForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.mark_unread_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToMarkUnread = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToMarkUnread.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const markedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToMarkUnread },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status !== 'unread' || recipient.readAt !== null) {\n recipient.status = 'unread'\n recipient.readAt = null\n markedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of markedIds) {\n await emitMessagesEvent('messages.message.marked_unread', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: markedIds.length }\n },\n}\n\nconst unarchiveConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.unarchive_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToUnarchive = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToUnarchive.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const unarchivedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToUnarchive },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status === 'archived' || recipient.archivedAt !== null) {\n recipient.archivedAt = null\n recipient.status = recipient.readAt ? 'read' : 'unread'\n unarchivedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of unarchivedIds) {\n await emitMessagesEvent('messages.message.unarchived', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: unarchivedIds.length }\n },\n}\n\nconst markConversationReadForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.mark_read_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToMarkRead = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))\n\n if (messageIdsToMarkRead.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const readAt = new Date()\n const markedIds: string[] = []\n await em.transactional(async (trx) => {\n const recipients = await trx.find(MessageRecipient, {\n messageId: { $in: messageIdsToMarkRead },\n recipientUserId: input.userId,\n deletedAt: null,\n })\n for (const recipient of recipients) {\n if (recipient.status === 'unread') {\n recipient.status = 'read'\n recipient.readAt = readAt\n markedIds.push(recipient.messageId)\n }\n }\n })\n\n for (const messageId of markedIds) {\n await emitMessagesEvent('messages.message.read', {\n messageId,\n recipientUserId: input.userId,\n userId: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: markedIds.length }\n },\n}\n\ntype DeletedTarget = { messageId: string; target: 'sender' | 'recipient' }\n\nconst deleteConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {\n id: 'messages.conversation.delete_for_actor',\n async execute(rawInput, ctx) {\n const input = conversationCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = await resolveConversationScope(em, input)\n const messageIdsToDelete = scope.messageIds.filter(\n (messageId) => scope.recipientMessageIds.has(messageId) || scope.senderMessageIds.has(messageId),\n )\n\n if (messageIdsToDelete.length === 0) {\n return { ok: true, affectedCount: 0 }\n }\n\n const deletedNow = new Date()\n const deletedTargets: DeletedTarget[] = []\n await em.transactional(async (trx) => {\n for (const messageId of messageIdsToDelete) {\n const recipient = await trx.findOne(MessageRecipient, {\n messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (recipient) {\n recipient.status = 'deleted'\n recipient.deletedAt = deletedNow\n deletedTargets.push({ messageId, target: 'recipient' })\n continue\n }\n if (scope.senderMessageIds.has(messageId)) {\n const message = await trx.findOne(Message, {\n id: messageId,\n tenantId: input.tenantId,\n deletedAt: null,\n })\n if (message && message.senderUserId === input.userId) {\n message.deletedAt = deletedNow\n deletedTargets.push({ messageId, target: 'sender' })\n }\n }\n }\n })\n\n for (const { messageId, target } of deletedTargets) {\n await emitMessagesEvent('messages.message.deleted', {\n messageId,\n actorUserId: input.userId,\n recipientUserId: input.userId,\n target,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }, { persistent: true })\n }\n\n return { ok: true, affectedCount: deletedTargets.length }\n },\n}\n\nregisterCommand(archiveConversationForActorCommand)\nregisterCommand(unarchiveConversationForActorCommand)\nregisterCommand(markConversationUnreadForActorCommand)\nregisterCommand(markConversationReadForActorCommand)\nregisterCommand(deleteConversationForActorCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -189,6 +189,8 @@ function MessageDetailPageClient({ id }) {
189
189
  const firstConversationMessageId = state.conversationItems[0]?.id ?? null;
190
190
  const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null;
191
191
  const canRunConversationActions = Boolean(firstConversationMessageId);
192
+ const conversationArchived = Boolean(detail.conversationArchived);
193
+ const conversationAllUnread = Boolean(detail.conversationAllUnread);
192
194
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
193
195
  /* @__PURE__ */ jsx(
194
196
  MainMessageHeader,
@@ -197,6 +199,8 @@ function MessageDetailPageClient({ id }) {
197
199
  priority: detail.priority ?? "normal",
198
200
  canReply: !detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId),
199
201
  canForwardAll: !detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId),
202
+ conversationArchived,
203
+ conversationAllUnread,
200
204
  actionsDisabled: Boolean(state.activeConversationAction),
201
205
  activeActionId: state.activeConversationAction,
202
206
  onReply: () => {
@@ -213,13 +217,21 @@ function MessageDetailPageClient({ id }) {
213
217
  messageId: latestConversationMessageId
214
218
  });
215
219
  },
216
- onArchiveConversation: () => {
220
+ onToggleArchiveConversation: () => {
217
221
  if (!canRunConversationActions) return;
218
- void state.archiveConversation(firstConversationMessageId ?? void 0);
222
+ if (conversationArchived) {
223
+ void state.unarchiveConversation(firstConversationMessageId ?? void 0);
224
+ } else {
225
+ void state.archiveConversation(firstConversationMessageId ?? void 0);
226
+ }
219
227
  },
220
- onMarkAllUnread: () => {
228
+ onToggleReadConversation: () => {
221
229
  if (!canRunConversationActions) return;
222
- void state.markConversationUnread(firstConversationMessageId ?? void 0);
230
+ if (conversationAllUnread) {
231
+ void state.markConversationRead(firstConversationMessageId ?? void 0);
232
+ } else {
233
+ void state.markConversationUnread(firstConversationMessageId ?? void 0);
234
+ }
223
235
  },
224
236
  onDeleteConversation: () => {
225
237
  if (!canRunConversationActions || state.activeConversationAction) return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/components/MessageDetailPageClient.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\n// UMES extension surface \u2014 message detail injection spots (SPEC-045d \u00A79.3a)\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport {\n getMessageUiComponentRegistry,\n} from './utils/typeUiRegistry'\nimport {\n MessageDetailActionsSection,\n MessageDetailAttachmentsSection,\n MessageDetailBodySection,\n MessageDetailComposerDialogs,\n MessageDetailDialogs,\n MainMessageHeader,\n MessageListComponent,\n MessageHeader,\n MessageDetailMetaSection,\n MessageDetailObjectsSection,\n} from './message-detail/detail-panels'\nimport { useMessageDetails } from './message-detail/hooks/useMessageDetails'\n\nfunction MessageConversationDetailItem({\n messageId,\n isCollapsible,\n isExpanded,\n onToggle,\n onReply,\n onForward,\n userFeatures,\n}: {\n messageId: string\n isCollapsible: boolean\n isExpanded: boolean\n onToggle: () => void\n onReply: (messageId: string) => void\n onForward: (messageId: string) => void\n userFeatures: string[]\n}) {\n const state = useMessageDetails(messageId)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n if (state.isLoadingDetail) {\n return (\n <section className=\"py-3\">\n <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n </section>\n )\n }\n\n if (!state.detail) {\n return (\n <section className=\"py-3\">\n <ErrorMessage label={state.loadErrorMessage} />\n </section>\n )\n }\n\n const ContentComponent = state.contentComponentKey\n ? messageUiRegistry.contentComponents[state.contentComponentKey] ?? null\n : null\n const ActionsComponent = state.actionsComponentKey\n ? messageUiRegistry.actionsComponents[state.actionsComponentKey] ?? null\n : null\n\n return (\n <section className=\"py-3\">\n <div className=\"space-y-4\">\n <section className=\"space-y-2 py-2\">\n <MessageHeader\n detail={state.detail}\n updatingState={state.updatingState}\n isArchived={state.isArchived}\n showSubject={false}\n collapseToggle={isCollapsible ? { expanded: isExpanded, onToggle } : undefined}\n onReply={() => onReply(messageId)}\n onForward={() => onForward(messageId)}\n onEdit={() => state.setEditOpen(true)}\n onToggleRead={() => void state.toggleRead()}\n onToggleArchive={() => void state.toggleArchive()}\n onDelete={() => state.setDeleteConfirmationOpen(true)}\n />\n\n <MessageDetailBodySection\n detail={state.detail}\n contentProps={state.contentProps}\n ContentComponent={ContentComponent}\n />\n\n {/* UMES \u2014 channel payload renderer mounts here (channel-linked emails, Slack blocks, etc.). */}\n <InjectionSpot\n spotId=\"detail:messages:message:body:after\"\n context={{ messageId }}\n data={state.detail}\n />\n\n <MessageDetailMetaSection detail={state.detail} />\n\n {/* UMES \u2014 channel sidebar widgets (channel info panel, contact preview, delivery status).\n `userFeatures` lets feature-gated widgets (e.g. the channel reassignment editor,\n gated on `communication_channels.assign`) render their controls for permitted users. */}\n <InjectionSpot\n spotId=\"detail:messages:message:sidebar\"\n context={{ messageId, userFeatures }}\n data={state.detail}\n />\n </section>\n\n <MessageDetailActionsSection\n detail={state.detail}\n messageActions={state.messageActions}\n executingActionId={state.executingActionId}\n ActionsComponent={ActionsComponent}\n onExecuteActionById={state.handleExecuteActionById}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailObjectsSection\n detail={state.detail}\n objectActionsByObjectId={state.objectActionsByObjectId}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailAttachmentsSection\n attachmentsQuery={state.attachmentsQuery}\n attachments={state.attachments}\n />\n </div>\n\n <MessageDetailComposerDialogs\n id={messageId}\n detail={state.detail}\n attachments={state.attachments}\n editOpen={state.editOpen}\n setEditOpen={state.setEditOpen}\n onRefresh={() => state.detailQuery.refetch()}\n />\n\n <MessageDetailDialogs\n pendingActionConfirmation={state.pendingActionConfirmation}\n setPendingActionConfirmation={state.setPendingActionConfirmation}\n executingActionId={state.executingActionId}\n handleConfirmPendingAction={state.handleConfirmPendingAction}\n handleActionConfirmDialogKeyDown={state.handleActionConfirmDialogKeyDown}\n deleteConfirmationOpen={state.deleteConfirmationOpen}\n setDeleteConfirmationOpen={state.setDeleteConfirmationOpen}\n updatingState={state.updatingState}\n handleDelete={state.handleDelete}\n handleDeleteDialogKeyDown={state.handleDeleteDialogKeyDown}\n />\n </section>\n )\n}\n\nexport function MessageDetailPageClient({ id }: { id: string }) {\n const state = useMessageDetails(id)\n // Resolve the viewer's feature grants once and forward them into the sidebar\n // injection spot so feature-gated widgets (channel reassignment editor) can\n // render their controls. Without this, hub widgets that gate on\n // `context.userFeatures` (e.g. `communication_channels.assign`) stay read-only.\n const [userFeatures, setUserFeatures] = React.useState<string[]>([])\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n const res = await apiCall<{ ok: boolean; granted?: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['communication_channels.assign'] }),\n }).catch(() => null)\n if (!cancelled && res?.ok && Array.isArray(res.result?.granted)) {\n setUserFeatures(res.result.granted)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [])\n const [activeInlineComposer, setActiveInlineComposer] = React.useState<{\n variant: 'reply' | 'forward'\n messageId: string\n } | null>(null)\n const inlineComposerContainerRef = React.useRef<HTMLDivElement | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!activeInlineComposer) return\n\n const composerContainer = inlineComposerContainerRef.current\n if (!composerContainer) return\n\n const firstFocusableElement = composerContainer.querySelector<HTMLElement>(\n 'textarea, input, [contenteditable=\"true\"]',\n )\n firstFocusableElement?.focus()\n }, [activeInlineComposer])\n\n if (state.isLoadingDetail) {\n return <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n }\n\n if (!state.detail) {\n return (\n <ErrorMessage\n label={state.loadErrorMessage}\n action={(\n <Button type=\"button\" variant=\"outline\" onClick={state.backToList}>\n {state.t('messages.actions.backToList', 'Back to messages')}\n </Button>\n )}\n />\n )\n }\n\n const detail = state.detail\n const firstConversationMessageId = state.conversationItems[0]?.id ?? null\n const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null\n const canRunConversationActions = Boolean(firstConversationMessageId)\n\n return (\n <div className=\"space-y-3\">\n <MainMessageHeader\n subject={detail.subject}\n priority={(detail.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal'}\n canReply={!detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId)}\n canForwardAll={!detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId)}\n actionsDisabled={Boolean(state.activeConversationAction)}\n activeActionId={state.activeConversationAction}\n onReply={() => {\n if (!firstConversationMessageId) return\n setActiveInlineComposer({\n variant: 'reply',\n messageId: firstConversationMessageId,\n })\n }}\n onForwardAll={() => {\n if (!latestConversationMessageId) return\n setActiveInlineComposer({\n variant: 'forward',\n messageId: latestConversationMessageId,\n })\n }}\n onArchiveConversation={() => {\n if (!canRunConversationActions) return\n void state.archiveConversation(firstConversationMessageId ?? undefined)\n }}\n onMarkAllUnread={() => {\n if (!canRunConversationActions) return\n void state.markConversationUnread(firstConversationMessageId ?? undefined)\n }}\n onDeleteConversation={() => {\n if (!canRunConversationActions || state.activeConversationAction) return\n void (async () => {\n const confirmed = await confirm({\n title: state.t('messages.confirm.deleteConversationTitle', 'Delete conversation'),\n text: state.t('messages.confirm.deleteConversation', 'Delete this conversation from your view?'),\n confirmText: state.t('messages.actions.deleteConversation', 'Delete conversation'),\n cancelText: state.t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n await state.deleteConversation(firstConversationMessageId ?? undefined)\n })()\n }}\n />\n <div className=\"divide-y border-y\">\n {state.conversationItems.map((item) => {\n const isForcedExpanded = item.id === state.forcedExpandedItemId\n const isExpanded = state.isConversationItemExpanded(item.id)\n if (isExpanded) {\n return (\n <MessageConversationDetailItem\n key={item.id}\n messageId={item.id}\n userFeatures={userFeatures}\n isCollapsible={!isForcedExpanded}\n isExpanded\n onToggle={() => state.toggleConversationItem(item.id)}\n onReply={(messageId) => {\n setActiveInlineComposer({\n variant: 'reply',\n messageId,\n })\n }}\n onForward={(messageId) => {\n setActiveInlineComposer({\n variant: 'forward',\n messageId,\n })\n }}\n />\n )\n }\n\n return (\n <section key={item.id} className=\"px-1 py-1\">\n <MessageListComponent\n message={state.buildConversationListItemMessage(item)}\n onClick={() => state.toggleConversationItem(item.id)}\n />\n </section>\n )\n })}\n </div>\n\n {activeInlineComposer ? (\n <div ref={inlineComposerContainerRef}>\n <MessageComposer\n inline\n inlineBackHref={null}\n variant={activeInlineComposer.variant}\n messageId={activeInlineComposer.messageId}\n onCancel={() => {\n setActiveInlineComposer(null)\n }}\n onSuccess={() => {\n setActiveInlineComposer(null)\n void state.detailQuery.refetch()\n }}\n />\n </div>\n ) : null}\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\n// UMES extension surface \u2014 message detail injection spots (SPEC-045d \u00A79.3a)\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport {\n getMessageUiComponentRegistry,\n} from './utils/typeUiRegistry'\nimport {\n MessageDetailActionsSection,\n MessageDetailAttachmentsSection,\n MessageDetailBodySection,\n MessageDetailComposerDialogs,\n MessageDetailDialogs,\n MainMessageHeader,\n MessageListComponent,\n MessageHeader,\n MessageDetailMetaSection,\n MessageDetailObjectsSection,\n} from './message-detail/detail-panels'\nimport { useMessageDetails } from './message-detail/hooks/useMessageDetails'\n\nfunction MessageConversationDetailItem({\n messageId,\n isCollapsible,\n isExpanded,\n onToggle,\n onReply,\n onForward,\n userFeatures,\n}: {\n messageId: string\n isCollapsible: boolean\n isExpanded: boolean\n onToggle: () => void\n onReply: (messageId: string) => void\n onForward: (messageId: string) => void\n userFeatures: string[]\n}) {\n const state = useMessageDetails(messageId)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n if (state.isLoadingDetail) {\n return (\n <section className=\"py-3\">\n <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n </section>\n )\n }\n\n if (!state.detail) {\n return (\n <section className=\"py-3\">\n <ErrorMessage label={state.loadErrorMessage} />\n </section>\n )\n }\n\n const ContentComponent = state.contentComponentKey\n ? messageUiRegistry.contentComponents[state.contentComponentKey] ?? null\n : null\n const ActionsComponent = state.actionsComponentKey\n ? messageUiRegistry.actionsComponents[state.actionsComponentKey] ?? null\n : null\n\n return (\n <section className=\"py-3\">\n <div className=\"space-y-4\">\n <section className=\"space-y-2 py-2\">\n <MessageHeader\n detail={state.detail}\n updatingState={state.updatingState}\n isArchived={state.isArchived}\n showSubject={false}\n collapseToggle={isCollapsible ? { expanded: isExpanded, onToggle } : undefined}\n onReply={() => onReply(messageId)}\n onForward={() => onForward(messageId)}\n onEdit={() => state.setEditOpen(true)}\n onToggleRead={() => void state.toggleRead()}\n onToggleArchive={() => void state.toggleArchive()}\n onDelete={() => state.setDeleteConfirmationOpen(true)}\n />\n\n <MessageDetailBodySection\n detail={state.detail}\n contentProps={state.contentProps}\n ContentComponent={ContentComponent}\n />\n\n {/* UMES \u2014 channel payload renderer mounts here (channel-linked emails, Slack blocks, etc.). */}\n <InjectionSpot\n spotId=\"detail:messages:message:body:after\"\n context={{ messageId }}\n data={state.detail}\n />\n\n <MessageDetailMetaSection detail={state.detail} />\n\n {/* UMES \u2014 channel sidebar widgets (channel info panel, contact preview, delivery status).\n `userFeatures` lets feature-gated widgets (e.g. the channel reassignment editor,\n gated on `communication_channels.assign`) render their controls for permitted users. */}\n <InjectionSpot\n spotId=\"detail:messages:message:sidebar\"\n context={{ messageId, userFeatures }}\n data={state.detail}\n />\n </section>\n\n <MessageDetailActionsSection\n detail={state.detail}\n messageActions={state.messageActions}\n executingActionId={state.executingActionId}\n ActionsComponent={ActionsComponent}\n onExecuteActionById={state.handleExecuteActionById}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailObjectsSection\n detail={state.detail}\n objectActionsByObjectId={state.objectActionsByObjectId}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailAttachmentsSection\n attachmentsQuery={state.attachmentsQuery}\n attachments={state.attachments}\n />\n </div>\n\n <MessageDetailComposerDialogs\n id={messageId}\n detail={state.detail}\n attachments={state.attachments}\n editOpen={state.editOpen}\n setEditOpen={state.setEditOpen}\n onRefresh={() => state.detailQuery.refetch()}\n />\n\n <MessageDetailDialogs\n pendingActionConfirmation={state.pendingActionConfirmation}\n setPendingActionConfirmation={state.setPendingActionConfirmation}\n executingActionId={state.executingActionId}\n handleConfirmPendingAction={state.handleConfirmPendingAction}\n handleActionConfirmDialogKeyDown={state.handleActionConfirmDialogKeyDown}\n deleteConfirmationOpen={state.deleteConfirmationOpen}\n setDeleteConfirmationOpen={state.setDeleteConfirmationOpen}\n updatingState={state.updatingState}\n handleDelete={state.handleDelete}\n handleDeleteDialogKeyDown={state.handleDeleteDialogKeyDown}\n />\n </section>\n )\n}\n\nexport function MessageDetailPageClient({ id }: { id: string }) {\n const state = useMessageDetails(id)\n // Resolve the viewer's feature grants once and forward them into the sidebar\n // injection spot so feature-gated widgets (channel reassignment editor) can\n // render their controls. Without this, hub widgets that gate on\n // `context.userFeatures` (e.g. `communication_channels.assign`) stay read-only.\n const [userFeatures, setUserFeatures] = React.useState<string[]>([])\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n const res = await apiCall<{ ok: boolean; granted?: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['communication_channels.assign'] }),\n }).catch(() => null)\n if (!cancelled && res?.ok && Array.isArray(res.result?.granted)) {\n setUserFeatures(res.result.granted)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [])\n const [activeInlineComposer, setActiveInlineComposer] = React.useState<{\n variant: 'reply' | 'forward'\n messageId: string\n } | null>(null)\n const inlineComposerContainerRef = React.useRef<HTMLDivElement | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!activeInlineComposer) return\n\n const composerContainer = inlineComposerContainerRef.current\n if (!composerContainer) return\n\n const firstFocusableElement = composerContainer.querySelector<HTMLElement>(\n 'textarea, input, [contenteditable=\"true\"]',\n )\n firstFocusableElement?.focus()\n }, [activeInlineComposer])\n\n if (state.isLoadingDetail) {\n return <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n }\n\n if (!state.detail) {\n return (\n <ErrorMessage\n label={state.loadErrorMessage}\n action={(\n <Button type=\"button\" variant=\"outline\" onClick={state.backToList}>\n {state.t('messages.actions.backToList', 'Back to messages')}\n </Button>\n )}\n />\n )\n }\n\n const detail = state.detail\n const firstConversationMessageId = state.conversationItems[0]?.id ?? null\n const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null\n const canRunConversationActions = Boolean(firstConversationMessageId)\n const conversationArchived = Boolean(detail.conversationArchived)\n const conversationAllUnread = Boolean(detail.conversationAllUnread)\n\n return (\n <div className=\"space-y-3\">\n <MainMessageHeader\n subject={detail.subject}\n priority={(detail.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal'}\n canReply={!detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId)}\n canForwardAll={!detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId)}\n conversationArchived={conversationArchived}\n conversationAllUnread={conversationAllUnread}\n actionsDisabled={Boolean(state.activeConversationAction)}\n activeActionId={state.activeConversationAction}\n onReply={() => {\n if (!firstConversationMessageId) return\n setActiveInlineComposer({\n variant: 'reply',\n messageId: firstConversationMessageId,\n })\n }}\n onForwardAll={() => {\n if (!latestConversationMessageId) return\n setActiveInlineComposer({\n variant: 'forward',\n messageId: latestConversationMessageId,\n })\n }}\n onToggleArchiveConversation={() => {\n if (!canRunConversationActions) return\n if (conversationArchived) {\n void state.unarchiveConversation(firstConversationMessageId ?? undefined)\n } else {\n void state.archiveConversation(firstConversationMessageId ?? undefined)\n }\n }}\n onToggleReadConversation={() => {\n if (!canRunConversationActions) return\n if (conversationAllUnread) {\n void state.markConversationRead(firstConversationMessageId ?? undefined)\n } else {\n void state.markConversationUnread(firstConversationMessageId ?? undefined)\n }\n }}\n onDeleteConversation={() => {\n if (!canRunConversationActions || state.activeConversationAction) return\n void (async () => {\n const confirmed = await confirm({\n title: state.t('messages.confirm.deleteConversationTitle', 'Delete conversation'),\n text: state.t('messages.confirm.deleteConversation', 'Delete this conversation from your view?'),\n confirmText: state.t('messages.actions.deleteConversation', 'Delete conversation'),\n cancelText: state.t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n await state.deleteConversation(firstConversationMessageId ?? undefined)\n })()\n }}\n />\n <div className=\"divide-y border-y\">\n {state.conversationItems.map((item) => {\n const isForcedExpanded = item.id === state.forcedExpandedItemId\n const isExpanded = state.isConversationItemExpanded(item.id)\n if (isExpanded) {\n return (\n <MessageConversationDetailItem\n key={item.id}\n messageId={item.id}\n userFeatures={userFeatures}\n isCollapsible={!isForcedExpanded}\n isExpanded\n onToggle={() => state.toggleConversationItem(item.id)}\n onReply={(messageId) => {\n setActiveInlineComposer({\n variant: 'reply',\n messageId,\n })\n }}\n onForward={(messageId) => {\n setActiveInlineComposer({\n variant: 'forward',\n messageId,\n })\n }}\n />\n )\n }\n\n return (\n <section key={item.id} className=\"px-1 py-1\">\n <MessageListComponent\n message={state.buildConversationListItemMessage(item)}\n onClick={() => state.toggleConversationItem(item.id)}\n />\n </section>\n )\n })}\n </div>\n\n {activeInlineComposer ? (\n <div ref={inlineComposerContainerRef}>\n <MessageComposer\n inline\n inlineBackHref={null}\n variant={activeInlineComposer.variant}\n messageId={activeInlineComposer.messageId}\n onCancel={() => {\n setActiveInlineComposer(null)\n }}\n onSuccess={() => {\n setActiveInlineComposer(null)\n void state.detailQuery.refetch()\n }}\n />\n </div>\n ) : null}\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -122,6 +122,14 @@ function useMessageDetailsActions({
122
122
  successMessage: t("messages.flash.conversationArchived", "Conversation archived.")
123
123
  });
124
124
  }, [id, runConversationAction, t]);
125
+ const unarchiveConversation = React.useCallback(async (messageId) => {
126
+ const targetMessageId = messageId ?? id;
127
+ await runConversationAction("unarchiveConversation", {
128
+ url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/archive`,
129
+ method: "DELETE",
130
+ successMessage: t("messages.flash.conversationUnarchived", "Conversation restored from archive.")
131
+ });
132
+ }, [id, runConversationAction, t]);
125
133
  const markConversationUnread = React.useCallback(async (messageId) => {
126
134
  const targetMessageId = messageId ?? id;
127
135
  await runConversationAction("markAllUnread", {
@@ -131,6 +139,14 @@ function useMessageDetailsActions({
131
139
  skipDetailAutoMarkRead: true
132
140
  });
133
141
  }, [id, runConversationAction, t]);
142
+ const markConversationRead = React.useCallback(async (messageId) => {
143
+ const targetMessageId = messageId ?? id;
144
+ await runConversationAction("markAllRead", {
145
+ url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,
146
+ method: "PUT",
147
+ successMessage: t("messages.flash.conversationMarkedRead", "Conversation marked read.")
148
+ });
149
+ }, [id, runConversationAction, t]);
134
150
  const deleteConversation = React.useCallback(async (messageId) => {
135
151
  const targetMessageId = messageId ?? id;
136
152
  await runConversationAction("deleteConversation", {
@@ -298,8 +314,10 @@ function useMessageDetailsActions({
298
314
  toggleRead,
299
315
  toggleArchive,
300
316
  archiveConversation,
317
+ unarchiveConversation,
301
318
  deleteConversation,
302
319
  markConversationUnread,
320
+ markConversationRead,
303
321
  requestAndRefresh,
304
322
  handleDelete,
305
323
  handleDeleteDialogKeyDown,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { UseQueryResult } from '@tanstack/react-query'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { MessageActionsProps, MessageContentProps } from '@open-mercato/shared/modules/messages/types'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type {\n ActionResult,\n MessageAction,\n MessageAttachment,\n MessageDetail,\n PendingActionConfirmation,\n} from '../types'\nimport { isSafeNavigationHref, parseObjectActionId, toErrorMessage } from '../utils'\n\ntype RequestAndRefreshOptions = {\n skipDetailAutoMarkRead?: boolean\n}\n\ntype ConversationActionKind = 'archiveConversation' | 'deleteConversation' | 'markAllUnread'\n\ntype MessageDetailMutationContext = {\n resourceKind: 'message' | 'conversation'\n messageId: string\n action: string\n retryLastMutation: () => Promise<boolean>\n}\n\ntype UseMessageDetailsActionsInput = {\n id: string\n t: TranslateFn\n detail: MessageDetail | null\n detailQuery: UseQueryResult<MessageDetail | null, Error>\n attachments: MessageAttachment[] | undefined\n isArchived: boolean\n onDeleted: () => void\n refreshDetailWithoutAutoMarkRead: () => Promise<MessageDetail | null>\n}\n\nexport function useMessageDetailsActions({\n id,\n t,\n detail,\n detailQuery,\n attachments,\n isArchived,\n onDeleted,\n refreshDetailWithoutAutoMarkRead,\n}: UseMessageDetailsActionsInput) {\n const [replyOpen, setReplyOpen] = React.useState(false)\n const [forwardOpen, setForwardOpen] = React.useState(false)\n const [editOpen, setEditOpen] = React.useState(false)\n const [updatingState, setUpdatingState] = React.useState(false)\n const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)\n const [pendingActionConfirmation, setPendingActionConfirmation] = React.useState<PendingActionConfirmation | null>(null)\n const [deleteConfirmationOpen, setDeleteConfirmationOpen] = React.useState(false)\n const [activeConversationAction, setActiveConversationAction] = React.useState<ConversationActionKind | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MessageDetailMutationContext>({\n contextId: 'messages-detail-actions',\n })\n\n const requestAndRefresh = React.useCallback(async (\n url: string,\n method: 'PUT' | 'DELETE',\n options?: RequestAndRefreshOptions,\n ) => {\n setUpdatingState(true)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(url, { method })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.stateChangeFailed', 'Failed to update message state.'),\n )\n }\n if (options?.skipDetailAutoMarkRead) {\n await refreshDetailWithoutAutoMarkRead()\n } else {\n await detailQuery.refetch()\n }\n },\n context: { resourceKind: 'message', messageId: id, action: 'state-change', retryLastMutation },\n mutationPayload: { messageId: id, action: 'state-change', url, method },\n })\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.stateChangeFailed', 'Failed to update message state.'),\n 'error',\n )\n } finally {\n setUpdatingState(false)\n }\n }, [detailQuery, id, refreshDetailWithoutAutoMarkRead, retryLastMutation, runMutation, t])\n\n const handleDelete = React.useCallback(async () => {\n setUpdatingState(true)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(`/api/messages/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n throw new Error(toErrorMessage(call.result) ?? t('messages.errors.deleteFailed', 'Failed to delete message.'))\n }\n },\n context: { resourceKind: 'message', messageId: id, action: 'delete', retryLastMutation },\n mutationPayload: { messageId: id, action: 'delete' },\n })\n flash(t('messages.flash.deleted', 'Message deleted.'), 'success')\n onDeleted()\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.deleteFailed', 'Failed to delete message.'),\n 'error',\n )\n } finally {\n setUpdatingState(false)\n }\n }, [id, onDeleted, retryLastMutation, runMutation, t])\n\n const runConversationAction = React.useCallback(async (\n action: ConversationActionKind,\n options: {\n url: string\n method: 'PUT' | 'DELETE'\n successMessage: string\n skipDetailAutoMarkRead?: boolean\n onSuccess?: () => void\n },\n ) => {\n setActiveConversationAction(action)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean; affectedCount?: number }>(options.url, { method: options.method })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.conversationActionFailed', 'Failed to update conversation.'),\n )\n }\n },\n context: { resourceKind: 'conversation', messageId: id, action, retryLastMutation },\n mutationPayload: { messageId: id, action, url: options.url, method: options.method },\n })\n\n flash(options.successMessage, 'success')\n\n if (options.onSuccess) {\n options.onSuccess()\n return\n }\n\n if (options.skipDetailAutoMarkRead) {\n await refreshDetailWithoutAutoMarkRead()\n } else {\n await detailQuery.refetch()\n }\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.conversationActionFailed', 'Failed to update conversation.'),\n 'error',\n )\n } finally {\n setActiveConversationAction(null)\n }\n }, [detailQuery, id, refreshDetailWithoutAutoMarkRead, retryLastMutation, runMutation, t])\n\n const archiveConversation = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('archiveConversation', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/archive`,\n method: 'PUT',\n successMessage: t('messages.flash.conversationArchived', 'Conversation archived.'),\n })\n }, [id, runConversationAction, t])\n\n const markConversationUnread = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('markAllUnread', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,\n method: 'DELETE',\n successMessage: t('messages.flash.conversationMarkedUnread', 'Conversation marked unread.'),\n skipDetailAutoMarkRead: true,\n })\n }, [id, runConversationAction, t])\n\n const deleteConversation = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('deleteConversation', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation`,\n method: 'DELETE',\n successMessage: t('messages.flash.conversationDeleted', 'Conversation deleted.'),\n onSuccess: onDeleted,\n })\n }, [id, onDeleted, runConversationAction, t])\n\n const handleDeleteDialogKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleDelete()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n setDeleteConfirmationOpen(false)\n }\n }, [handleDelete])\n\n const executeAction = React.useCallback(async (action: MessageAction, payload?: Record<string, unknown>) => {\n setExecutingActionId(action.id)\n try {\n const call = await runMutation({\n operation: async () => {\n const result = await apiCall<ActionResult>(\n `/api/messages/${encodeURIComponent(id)}/actions/${encodeURIComponent(action.id)}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload ?? {}),\n },\n )\n\n if (!result.ok) {\n throw new Error(\n toErrorMessage(result.result)\n ?? t('messages.errors.actionFailed', 'Failed to execute action.'),\n )\n }\n\n return result\n },\n context: { resourceKind: 'message', messageId: id, action: 'execute-action', retryLastMutation },\n mutationPayload: { messageId: id, action: 'execute-action', actionId: action.id },\n })\n\n flash(t('messages.flash.actionSuccess', 'Action completed.'), 'success')\n\n const redirectTarget = call.result?.result?.redirect\n if (\n typeof redirectTarget === 'string'\n && redirectTarget.trim().length > 0\n && isSafeNavigationHref(redirectTarget)\n ) {\n window.location.href = redirectTarget\n return\n }\n\n await detailQuery.refetch()\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.actionFailed', 'Failed to execute action.'),\n 'error',\n )\n } finally {\n setExecutingActionId(null)\n }\n }, [detailQuery, id, retryLastMutation, runMutation, t])\n\n const handleExecuteAction = React.useCallback(async (action: MessageAction, payload?: Record<string, unknown>) => {\n if (executingActionId || detail?.actionTaken) return\n\n if (action.confirmRequired) {\n setPendingActionConfirmation({ action, payload })\n return\n }\n\n await executeAction(action, payload)\n }, [detail?.actionTaken, executeAction, executingActionId])\n\n const handleConfirmPendingAction = React.useCallback(async () => {\n if (!pendingActionConfirmation || executingActionId || detail?.actionTaken) return\n const { action, payload } = pendingActionConfirmation\n setPendingActionConfirmation(null)\n await executeAction(action, payload)\n }, [detail?.actionTaken, executeAction, executingActionId, pendingActionConfirmation])\n\n const handleActionConfirmDialogKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleConfirmPendingAction()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n setPendingActionConfirmation(null)\n }\n }, [handleConfirmPendingAction])\n\n const contentProps = React.useMemo<MessageContentProps>(() => ({\n message: {\n id: detail?.id ?? '',\n type: detail?.type ?? 'default',\n subject: detail?.subject ?? '',\n body: detail?.body ?? '',\n bodyFormat: detail?.bodyFormat ?? 'text',\n priority: (detail?.priority ?? 'normal') as 'low' | 'normal' | 'high' | 'urgent',\n sentAt: detail?.sentAt ? new Date(detail.sentAt) : null,\n senderName: detail?.senderName ?? undefined,\n senderUserId: detail?.senderUserId ?? '',\n actionData: detail?.actionData ?? null,\n actionTaken: detail?.actionTaken ?? null,\n actionTakenAt: detail?.actionTakenAt ? new Date(detail.actionTakenAt) : null,\n },\n objects: (detail?.objects ?? []).map((item) => ({\n id: item.id,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n snapshot: item.snapshot ?? undefined,\n })),\n attachments: (attachments ?? []).map((item) => ({\n id: item.id,\n fileName: item.fileName,\n fileSize: item.fileSize,\n mimeType: item.mimeType,\n url: item.url,\n })),\n }), [attachments, detail])\n\n const handleExecuteActionById = React.useCallback<MessageActionsProps['onExecuteAction']>(async (actionId, payload) => {\n const action = detail?.actionData?.actions?.find((entry) => {\n if (parseObjectActionId(entry.id)) return false\n return entry.id === actionId\n })\n if (!action) return\n await handleExecuteAction(action, payload)\n }, [detail?.actionData?.actions, handleExecuteAction])\n\n const messageActions = React.useMemo(() => {\n return (detail?.actionData?.actions ?? []).filter((action) => !parseObjectActionId(action.id))\n }, [detail?.actionData?.actions])\n\n const objectActionsByObjectId = React.useMemo(() => {\n const byObjectId = new Map<string, Map<string, MessageAction>>()\n for (const action of detail?.actionData?.actions ?? []) {\n const parsed = parseObjectActionId(action.id)\n if (!parsed) continue\n const current = byObjectId.get(parsed.objectId) ?? new Map<string, MessageAction>()\n current.set(parsed.actionId, action)\n byObjectId.set(parsed.objectId, current)\n }\n return byObjectId\n }, [detail?.actionData?.actions])\n\n const toggleRead = React.useCallback(async () => {\n await requestAndRefresh(\n `/api/messages/${encodeURIComponent(id)}/read`,\n detail?.isRead ? 'DELETE' : 'PUT',\n detail?.isRead ? { skipDetailAutoMarkRead: true } : undefined,\n )\n }, [detail?.isRead, id, requestAndRefresh])\n\n const toggleArchive = React.useCallback(async () => {\n await requestAndRefresh(\n `/api/messages/${encodeURIComponent(id)}/archive`,\n isArchived ? 'DELETE' : 'PUT',\n )\n }, [id, isArchived, requestAndRefresh])\n\n return {\n replyOpen,\n setReplyOpen,\n forwardOpen,\n setForwardOpen,\n editOpen,\n setEditOpen,\n updatingState,\n executingActionId,\n pendingActionConfirmation,\n setPendingActionConfirmation,\n deleteConfirmationOpen,\n setDeleteConfirmationOpen,\n activeConversationAction,\n toggleRead,\n toggleArchive,\n archiveConversation,\n deleteConversation,\n markConversationUnread,\n requestAndRefresh,\n handleDelete,\n handleDeleteDialogKeyDown,\n handleExecuteAction,\n handleConfirmPendingAction,\n handleActionConfirmDialogKeyDown,\n contentProps,\n handleExecuteActionById,\n messageActions,\n objectActionsByObjectId,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { UseQueryResult } from '@tanstack/react-query'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { MessageActionsProps, MessageContentProps } from '@open-mercato/shared/modules/messages/types'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type {\n ActionResult,\n MessageAction,\n MessageAttachment,\n MessageDetail,\n PendingActionConfirmation,\n} from '../types'\nimport { isSafeNavigationHref, parseObjectActionId, toErrorMessage } from '../utils'\n\ntype RequestAndRefreshOptions = {\n skipDetailAutoMarkRead?: boolean\n}\n\ntype ConversationActionKind =\n | 'archiveConversation'\n | 'unarchiveConversation'\n | 'deleteConversation'\n | 'markAllUnread'\n | 'markAllRead'\n\ntype MessageDetailMutationContext = {\n resourceKind: 'message' | 'conversation'\n messageId: string\n action: string\n retryLastMutation: () => Promise<boolean>\n}\n\ntype UseMessageDetailsActionsInput = {\n id: string\n t: TranslateFn\n detail: MessageDetail | null\n detailQuery: UseQueryResult<MessageDetail | null, Error>\n attachments: MessageAttachment[] | undefined\n isArchived: boolean\n onDeleted: () => void\n refreshDetailWithoutAutoMarkRead: () => Promise<MessageDetail | null>\n}\n\nexport function useMessageDetailsActions({\n id,\n t,\n detail,\n detailQuery,\n attachments,\n isArchived,\n onDeleted,\n refreshDetailWithoutAutoMarkRead,\n}: UseMessageDetailsActionsInput) {\n const [replyOpen, setReplyOpen] = React.useState(false)\n const [forwardOpen, setForwardOpen] = React.useState(false)\n const [editOpen, setEditOpen] = React.useState(false)\n const [updatingState, setUpdatingState] = React.useState(false)\n const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)\n const [pendingActionConfirmation, setPendingActionConfirmation] = React.useState<PendingActionConfirmation | null>(null)\n const [deleteConfirmationOpen, setDeleteConfirmationOpen] = React.useState(false)\n const [activeConversationAction, setActiveConversationAction] = React.useState<ConversationActionKind | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MessageDetailMutationContext>({\n contextId: 'messages-detail-actions',\n })\n\n const requestAndRefresh = React.useCallback(async (\n url: string,\n method: 'PUT' | 'DELETE',\n options?: RequestAndRefreshOptions,\n ) => {\n setUpdatingState(true)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(url, { method })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.stateChangeFailed', 'Failed to update message state.'),\n )\n }\n if (options?.skipDetailAutoMarkRead) {\n await refreshDetailWithoutAutoMarkRead()\n } else {\n await detailQuery.refetch()\n }\n },\n context: { resourceKind: 'message', messageId: id, action: 'state-change', retryLastMutation },\n mutationPayload: { messageId: id, action: 'state-change', url, method },\n })\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.stateChangeFailed', 'Failed to update message state.'),\n 'error',\n )\n } finally {\n setUpdatingState(false)\n }\n }, [detailQuery, id, refreshDetailWithoutAutoMarkRead, retryLastMutation, runMutation, t])\n\n const handleDelete = React.useCallback(async () => {\n setUpdatingState(true)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(`/api/messages/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n throw new Error(toErrorMessage(call.result) ?? t('messages.errors.deleteFailed', 'Failed to delete message.'))\n }\n },\n context: { resourceKind: 'message', messageId: id, action: 'delete', retryLastMutation },\n mutationPayload: { messageId: id, action: 'delete' },\n })\n flash(t('messages.flash.deleted', 'Message deleted.'), 'success')\n onDeleted()\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.deleteFailed', 'Failed to delete message.'),\n 'error',\n )\n } finally {\n setUpdatingState(false)\n }\n }, [id, onDeleted, retryLastMutation, runMutation, t])\n\n const runConversationAction = React.useCallback(async (\n action: ConversationActionKind,\n options: {\n url: string\n method: 'PUT' | 'DELETE'\n successMessage: string\n skipDetailAutoMarkRead?: boolean\n onSuccess?: () => void\n },\n ) => {\n setActiveConversationAction(action)\n try {\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean; affectedCount?: number }>(options.url, { method: options.method })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.conversationActionFailed', 'Failed to update conversation.'),\n )\n }\n },\n context: { resourceKind: 'conversation', messageId: id, action, retryLastMutation },\n mutationPayload: { messageId: id, action, url: options.url, method: options.method },\n })\n\n flash(options.successMessage, 'success')\n\n if (options.onSuccess) {\n options.onSuccess()\n return\n }\n\n if (options.skipDetailAutoMarkRead) {\n await refreshDetailWithoutAutoMarkRead()\n } else {\n await detailQuery.refetch()\n }\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.conversationActionFailed', 'Failed to update conversation.'),\n 'error',\n )\n } finally {\n setActiveConversationAction(null)\n }\n }, [detailQuery, id, refreshDetailWithoutAutoMarkRead, retryLastMutation, runMutation, t])\n\n const archiveConversation = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('archiveConversation', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/archive`,\n method: 'PUT',\n successMessage: t('messages.flash.conversationArchived', 'Conversation archived.'),\n })\n }, [id, runConversationAction, t])\n\n const unarchiveConversation = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('unarchiveConversation', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/archive`,\n method: 'DELETE',\n successMessage: t('messages.flash.conversationUnarchived', 'Conversation restored from archive.'),\n })\n }, [id, runConversationAction, t])\n\n const markConversationUnread = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('markAllUnread', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,\n method: 'DELETE',\n successMessage: t('messages.flash.conversationMarkedUnread', 'Conversation marked unread.'),\n skipDetailAutoMarkRead: true,\n })\n }, [id, runConversationAction, t])\n\n const markConversationRead = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('markAllRead', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,\n method: 'PUT',\n successMessage: t('messages.flash.conversationMarkedRead', 'Conversation marked read.'),\n })\n }, [id, runConversationAction, t])\n\n const deleteConversation = React.useCallback(async (messageId?: string) => {\n const targetMessageId = messageId ?? id\n await runConversationAction('deleteConversation', {\n url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation`,\n method: 'DELETE',\n successMessage: t('messages.flash.conversationDeleted', 'Conversation deleted.'),\n onSuccess: onDeleted,\n })\n }, [id, onDeleted, runConversationAction, t])\n\n const handleDeleteDialogKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleDelete()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n setDeleteConfirmationOpen(false)\n }\n }, [handleDelete])\n\n const executeAction = React.useCallback(async (action: MessageAction, payload?: Record<string, unknown>) => {\n setExecutingActionId(action.id)\n try {\n const call = await runMutation({\n operation: async () => {\n const result = await apiCall<ActionResult>(\n `/api/messages/${encodeURIComponent(id)}/actions/${encodeURIComponent(action.id)}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload ?? {}),\n },\n )\n\n if (!result.ok) {\n throw new Error(\n toErrorMessage(result.result)\n ?? t('messages.errors.actionFailed', 'Failed to execute action.'),\n )\n }\n\n return result\n },\n context: { resourceKind: 'message', messageId: id, action: 'execute-action', retryLastMutation },\n mutationPayload: { messageId: id, action: 'execute-action', actionId: action.id },\n })\n\n flash(t('messages.flash.actionSuccess', 'Action completed.'), 'success')\n\n const redirectTarget = call.result?.result?.redirect\n if (\n typeof redirectTarget === 'string'\n && redirectTarget.trim().length > 0\n && isSafeNavigationHref(redirectTarget)\n ) {\n window.location.href = redirectTarget\n return\n }\n\n await detailQuery.refetch()\n } catch (err) {\n flash(\n err instanceof Error\n ? err.message\n : t('messages.errors.actionFailed', 'Failed to execute action.'),\n 'error',\n )\n } finally {\n setExecutingActionId(null)\n }\n }, [detailQuery, id, retryLastMutation, runMutation, t])\n\n const handleExecuteAction = React.useCallback(async (action: MessageAction, payload?: Record<string, unknown>) => {\n if (executingActionId || detail?.actionTaken) return\n\n if (action.confirmRequired) {\n setPendingActionConfirmation({ action, payload })\n return\n }\n\n await executeAction(action, payload)\n }, [detail?.actionTaken, executeAction, executingActionId])\n\n const handleConfirmPendingAction = React.useCallback(async () => {\n if (!pendingActionConfirmation || executingActionId || detail?.actionTaken) return\n const { action, payload } = pendingActionConfirmation\n setPendingActionConfirmation(null)\n await executeAction(action, payload)\n }, [detail?.actionTaken, executeAction, executingActionId, pendingActionConfirmation])\n\n const handleActionConfirmDialogKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleConfirmPendingAction()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n setPendingActionConfirmation(null)\n }\n }, [handleConfirmPendingAction])\n\n const contentProps = React.useMemo<MessageContentProps>(() => ({\n message: {\n id: detail?.id ?? '',\n type: detail?.type ?? 'default',\n subject: detail?.subject ?? '',\n body: detail?.body ?? '',\n bodyFormat: detail?.bodyFormat ?? 'text',\n priority: (detail?.priority ?? 'normal') as 'low' | 'normal' | 'high' | 'urgent',\n sentAt: detail?.sentAt ? new Date(detail.sentAt) : null,\n senderName: detail?.senderName ?? undefined,\n senderUserId: detail?.senderUserId ?? '',\n actionData: detail?.actionData ?? null,\n actionTaken: detail?.actionTaken ?? null,\n actionTakenAt: detail?.actionTakenAt ? new Date(detail.actionTakenAt) : null,\n },\n objects: (detail?.objects ?? []).map((item) => ({\n id: item.id,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n snapshot: item.snapshot ?? undefined,\n })),\n attachments: (attachments ?? []).map((item) => ({\n id: item.id,\n fileName: item.fileName,\n fileSize: item.fileSize,\n mimeType: item.mimeType,\n url: item.url,\n })),\n }), [attachments, detail])\n\n const handleExecuteActionById = React.useCallback<MessageActionsProps['onExecuteAction']>(async (actionId, payload) => {\n const action = detail?.actionData?.actions?.find((entry) => {\n if (parseObjectActionId(entry.id)) return false\n return entry.id === actionId\n })\n if (!action) return\n await handleExecuteAction(action, payload)\n }, [detail?.actionData?.actions, handleExecuteAction])\n\n const messageActions = React.useMemo(() => {\n return (detail?.actionData?.actions ?? []).filter((action) => !parseObjectActionId(action.id))\n }, [detail?.actionData?.actions])\n\n const objectActionsByObjectId = React.useMemo(() => {\n const byObjectId = new Map<string, Map<string, MessageAction>>()\n for (const action of detail?.actionData?.actions ?? []) {\n const parsed = parseObjectActionId(action.id)\n if (!parsed) continue\n const current = byObjectId.get(parsed.objectId) ?? new Map<string, MessageAction>()\n current.set(parsed.actionId, action)\n byObjectId.set(parsed.objectId, current)\n }\n return byObjectId\n }, [detail?.actionData?.actions])\n\n const toggleRead = React.useCallback(async () => {\n await requestAndRefresh(\n `/api/messages/${encodeURIComponent(id)}/read`,\n detail?.isRead ? 'DELETE' : 'PUT',\n detail?.isRead ? { skipDetailAutoMarkRead: true } : undefined,\n )\n }, [detail?.isRead, id, requestAndRefresh])\n\n const toggleArchive = React.useCallback(async () => {\n await requestAndRefresh(\n `/api/messages/${encodeURIComponent(id)}/archive`,\n isArchived ? 'DELETE' : 'PUT',\n )\n }, [id, isArchived, requestAndRefresh])\n\n return {\n replyOpen,\n setReplyOpen,\n forwardOpen,\n setForwardOpen,\n editOpen,\n setEditOpen,\n updatingState,\n executingActionId,\n pendingActionConfirmation,\n setPendingActionConfirmation,\n deleteConfirmationOpen,\n setDeleteConfirmationOpen,\n activeConversationAction,\n toggleRead,\n toggleArchive,\n archiveConversation,\n unarchiveConversation,\n deleteConversation,\n markConversationUnread,\n markConversationRead,\n requestAndRefresh,\n handleDelete,\n handleDeleteDialogKeyDown,\n handleExecuteAction,\n handleConfirmPendingAction,\n handleActionConfirmDialogKeyDown,\n contentProps,\n handleExecuteActionById,\n messageActions,\n objectActionsByObjectId,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
5
  import { FormHeader } from "@open-mercato/ui/backend/forms";
6
6
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
7
- import { Archive, Forward, MailMinus, Reply, Trash2 } from "lucide-react";
7
+ import { Archive, ArchiveRestore, Forward, MailMinus, MailOpen, Reply, Trash2 } from "lucide-react";
8
8
  import { PriorityBadge } from "../../utils/PriorityBadge.js";
9
9
  function MainMessageHeader(props) {
10
10
  const t = useT();
@@ -26,19 +26,19 @@ function MainMessageHeader(props) {
26
26
  },
27
27
  {
28
28
  id: "archive-conversation",
29
- label: t("messages.actions.archiveConversation", "Archive conversation"),
30
- icon: Archive,
31
- onSelect: props.onArchiveConversation,
29
+ label: props.conversationArchived ? t("messages.actions.unarchiveConversation", "Unarchive conversation") : t("messages.actions.archiveConversation", "Archive conversation"),
30
+ icon: props.conversationArchived ? ArchiveRestore : Archive,
31
+ onSelect: props.onToggleArchiveConversation,
32
32
  disabled: props.actionsDisabled,
33
- loading: props.activeActionId === "archiveConversation"
33
+ loading: props.activeActionId === "archiveConversation" || props.activeActionId === "unarchiveConversation"
34
34
  },
35
35
  {
36
36
  id: "mark-all-unread",
37
- label: t("messages.actions.markAllUnread", "Mark all unread"),
38
- icon: MailMinus,
39
- onSelect: props.onMarkAllUnread,
37
+ label: props.conversationAllUnread ? t("messages.actions.markAllRead", "Mark all read") : t("messages.actions.markAllUnread", "Mark all unread"),
38
+ icon: props.conversationAllUnread ? MailOpen : MailMinus,
39
+ onSelect: props.onToggleReadConversation,
40
40
  disabled: props.actionsDisabled,
41
- loading: props.activeActionId === "markAllUnread"
41
+ loading: props.activeActionId === "markAllUnread" || props.activeActionId === "markAllRead"
42
42
  },
43
43
  {
44
44
  id: "delete-conversation",
@@ -52,10 +52,12 @@ function MainMessageHeader(props) {
52
52
  props.actionsDisabled,
53
53
  props.canForwardAll,
54
54
  props.canReply,
55
- props.onArchiveConversation,
55
+ props.conversationArchived,
56
+ props.conversationAllUnread,
57
+ props.onToggleArchiveConversation,
56
58
  props.onDeleteConversation,
57
59
  props.onForwardAll,
58
- props.onMarkAllUnread,
60
+ props.onToggleReadConversation,
59
61
  props.onReply,
60
62
  props.activeActionId,
61
63
  t
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Archive, Forward, MailMinus, Reply, Trash2 } from 'lucide-react'\nimport { PriorityBadge } from '../../utils/PriorityBadge'\n\ntype MainMessageHeaderProps = {\n subject: string\n priority: 'low' | 'normal' | 'high' | 'urgent'\n canReply: boolean\n canForwardAll: boolean\n actionsDisabled?: boolean\n activeActionId?: 'forwardAll' | 'archiveConversation' | 'markAllUnread' | 'deleteConversation' | null\n onReply: () => void\n onForwardAll: () => void\n onArchiveConversation: () => void\n onMarkAllUnread: () => void\n onDeleteConversation: () => void\n}\n\nexport function MainMessageHeader(props: MainMessageHeaderProps) {\n const t = useT()\n\n const menuActions = React.useMemo<ActionItem[]>(() => [\n {\n id: 'reply',\n label: t('messages.reply', 'Reply'),\n icon: Reply,\n onSelect: props.onReply,\n disabled: props.actionsDisabled || !props.canReply,\n },\n {\n id: 'forward-all',\n label: t('messages.actions.forwardAll', 'Forward all'),\n icon: Forward,\n onSelect: props.onForwardAll,\n disabled: props.actionsDisabled || !props.canForwardAll,\n loading: props.activeActionId === 'forwardAll',\n },\n {\n id: 'archive-conversation',\n label: t('messages.actions.archiveConversation', 'Archive conversation'),\n icon: Archive,\n onSelect: props.onArchiveConversation,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'archiveConversation',\n },\n {\n id: 'mark-all-unread',\n label: t('messages.actions.markAllUnread', 'Mark all unread'),\n icon: MailMinus,\n onSelect: props.onMarkAllUnread,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'markAllUnread',\n },\n {\n id: 'delete-conversation',\n label: t('messages.actions.deleteConversation', 'Delete conversation'),\n icon: Trash2,\n onSelect: props.onDeleteConversation,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'deleteConversation',\n },\n ], [\n props.actionsDisabled,\n props.canForwardAll,\n props.canReply,\n props.onArchiveConversation,\n props.onDeleteConversation,\n props.onForwardAll,\n props.onMarkAllUnread,\n props.onReply,\n props.activeActionId,\n t,\n ])\n\n return (\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/messages\"\n backLabel={t('messages.actions.backToList', 'Back to messages')}\n title={(\n <span className=\"inline-flex items-center gap-2\">\n <span className=\"truncate\">{props.subject}</span>\n <PriorityBadge priority={props.priority} />\n </span>\n )}\n utilityActions={props.canReply ? (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"default\"\n aria-label={t('messages.reply', 'Reply')}\n onClick={props.onReply}\n disabled={props.actionsDisabled || !props.canReply}\n >\n <Reply className=\"h-4 w-4\" aria-hidden />\n </IconButton>\n ) : undefined}\n menuTriggerMode=\"icon\"\n menuAriaLabel={t('messages.actions.conversationActions', 'Conversation actions')}\n menuActions={menuActions}\n />\n )\n}\n"],
5
- "mappings": ";AAqFQ,SACE,KADF;AAnFR,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,kBAAmC;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,SAAS,SAAS,WAAW,OAAO,cAAc;AAC3D,SAAS,qBAAqB;AAgBvB,SAAS,kBAAkB,OAA+B;AAC/D,QAAM,IAAI,KAAK;AAEf,QAAM,cAAc,MAAM,QAAsB,MAAM;AAAA,IACpD;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,kBAAkB,OAAO;AAAA,MAClC,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,+BAA+B,aAAa;AAAA,MACrD,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,MAC1C,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,wCAAwC,sBAAsB;AAAA,MACvE,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,kCAAkC,iBAAiB;AAAA,MAC5D,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,uCAAuC,qBAAqB;AAAA,MACrE,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,EACF,GAAG;AAAA,IACD,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAS;AAAA,MACT,WAAW,EAAE,+BAA+B,kBAAkB;AAAA,MAC9D,OACE,qBAAC,UAAK,WAAU,kCACd;AAAA,4BAAC,UAAK,WAAU,YAAY,gBAAM,SAAQ;AAAA,QAC1C,oBAAC,iBAAc,UAAU,MAAM,UAAU;AAAA,SAC3C;AAAA,MAEF,gBAAgB,MAAM,WACpB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,cAAY,EAAE,kBAAkB,OAAO;AAAA,UACvC,SAAS,MAAM;AAAA,UACf,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,UAE1C,8BAAC,SAAM,WAAU,WAAU,eAAW,MAAC;AAAA;AAAA,MACzC,IACE;AAAA,MACJ,iBAAgB;AAAA,MAChB,eAAe,EAAE,wCAAwC,sBAAsB;AAAA,MAC/E;AAAA;AAAA,EACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Archive, ArchiveRestore, Forward, MailMinus, MailOpen, Reply, Trash2 } from 'lucide-react'\nimport { PriorityBadge } from '../../utils/PriorityBadge'\n\ntype MainMessageHeaderProps = {\n subject: string\n priority: 'low' | 'normal' | 'high' | 'urgent'\n canReply: boolean\n canForwardAll: boolean\n conversationArchived: boolean\n conversationAllUnread: boolean\n actionsDisabled?: boolean\n activeActionId?:\n | 'forwardAll'\n | 'archiveConversation'\n | 'unarchiveConversation'\n | 'markAllUnread'\n | 'markAllRead'\n | 'deleteConversation'\n | null\n onReply: () => void\n onForwardAll: () => void\n onToggleArchiveConversation: () => void\n onToggleReadConversation: () => void\n onDeleteConversation: () => void\n}\n\nexport function MainMessageHeader(props: MainMessageHeaderProps) {\n const t = useT()\n\n const menuActions = React.useMemo<ActionItem[]>(() => [\n {\n id: 'reply',\n label: t('messages.reply', 'Reply'),\n icon: Reply,\n onSelect: props.onReply,\n disabled: props.actionsDisabled || !props.canReply,\n },\n {\n id: 'forward-all',\n label: t('messages.actions.forwardAll', 'Forward all'),\n icon: Forward,\n onSelect: props.onForwardAll,\n disabled: props.actionsDisabled || !props.canForwardAll,\n loading: props.activeActionId === 'forwardAll',\n },\n {\n id: 'archive-conversation',\n label: props.conversationArchived\n ? t('messages.actions.unarchiveConversation', 'Unarchive conversation')\n : t('messages.actions.archiveConversation', 'Archive conversation'),\n icon: props.conversationArchived ? ArchiveRestore : Archive,\n onSelect: props.onToggleArchiveConversation,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'archiveConversation' || props.activeActionId === 'unarchiveConversation',\n },\n {\n id: 'mark-all-unread',\n label: props.conversationAllUnread\n ? t('messages.actions.markAllRead', 'Mark all read')\n : t('messages.actions.markAllUnread', 'Mark all unread'),\n icon: props.conversationAllUnread ? MailOpen : MailMinus,\n onSelect: props.onToggleReadConversation,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'markAllUnread' || props.activeActionId === 'markAllRead',\n },\n {\n id: 'delete-conversation',\n label: t('messages.actions.deleteConversation', 'Delete conversation'),\n icon: Trash2,\n onSelect: props.onDeleteConversation,\n disabled: props.actionsDisabled,\n loading: props.activeActionId === 'deleteConversation',\n },\n ], [\n props.actionsDisabled,\n props.canForwardAll,\n props.canReply,\n props.conversationArchived,\n props.conversationAllUnread,\n props.onToggleArchiveConversation,\n props.onDeleteConversation,\n props.onForwardAll,\n props.onToggleReadConversation,\n props.onReply,\n props.activeActionId,\n t,\n ])\n\n return (\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/messages\"\n backLabel={t('messages.actions.backToList', 'Back to messages')}\n title={(\n <span className=\"inline-flex items-center gap-2\">\n <span className=\"truncate\">{props.subject}</span>\n <PriorityBadge priority={props.priority} />\n </span>\n )}\n utilityActions={props.canReply ? (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"default\"\n aria-label={t('messages.reply', 'Reply')}\n onClick={props.onReply}\n disabled={props.actionsDisabled || !props.canReply}\n >\n <Reply className=\"h-4 w-4\" aria-hidden />\n </IconButton>\n ) : undefined}\n menuTriggerMode=\"icon\"\n menuAriaLabel={t('messages.actions.conversationActions', 'Conversation actions')}\n menuActions={menuActions}\n />\n )\n}\n"],
5
+ "mappings": ";AAoGQ,SACE,KADF;AAlGR,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,kBAAmC;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,SAAS,gBAAgB,SAAS,WAAW,UAAU,OAAO,cAAc;AACrF,SAAS,qBAAqB;AAyBvB,SAAS,kBAAkB,OAA+B;AAC/D,QAAM,IAAI,KAAK;AAEf,QAAM,cAAc,MAAM,QAAsB,MAAM;AAAA,IACpD;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,kBAAkB,OAAO;AAAA,MAClC,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,+BAA+B,aAAa;AAAA,MACrD,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,MAC1C,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,MAAM,uBACT,EAAE,0CAA0C,wBAAwB,IACpE,EAAE,wCAAwC,sBAAsB;AAAA,MACpE,MAAM,MAAM,uBAAuB,iBAAiB;AAAA,MACpD,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB,yBAAyB,MAAM,mBAAmB;AAAA,IACtF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,MAAM,wBACT,EAAE,gCAAgC,eAAe,IACjD,EAAE,kCAAkC,iBAAiB;AAAA,MACzD,MAAM,MAAM,wBAAwB,WAAW;AAAA,MAC/C,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB,mBAAmB,MAAM,mBAAmB;AAAA,IAChF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,uCAAuC,qBAAqB;AAAA,MACrE,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,mBAAmB;AAAA,IACpC;AAAA,EACF,GAAG;AAAA,IACD,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAS;AAAA,MACT,WAAW,EAAE,+BAA+B,kBAAkB;AAAA,MAC9D,OACE,qBAAC,UAAK,WAAU,kCACd;AAAA,4BAAC,UAAK,WAAU,YAAY,gBAAM,SAAQ;AAAA,QAC1C,oBAAC,iBAAc,UAAU,MAAM,UAAU;AAAA,SAC3C;AAAA,MAEF,gBAAgB,MAAM,WACpB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,cAAY,EAAE,kBAAkB,OAAO;AAAA,UACvC,SAAS,MAAM;AAAA,UACf,UAAU,MAAM,mBAAmB,CAAC,MAAM;AAAA,UAE1C,8BAAC,SAAM,WAAU,WAAU,eAAW,MAAC;AAAA;AAAA,MACzC,IACE;AAAA,MACJ,iBAAgB;AAAA,MAChB,eAAe,EAAE,wCAAwC,sBAAsB;AAAA,MAC/E;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6256.1.9fc16aedc4",
3
+ "version": "0.6.6-develop.6257.1.6d0af84d26",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6256.1.9fc16aedc4",
250
- "@open-mercato/shared": "0.6.6-develop.6256.1.9fc16aedc4",
251
- "@open-mercato/ui": "0.6.6-develop.6256.1.9fc16aedc4",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6257.1.6d0af84d26",
250
+ "@open-mercato/shared": "0.6.6-develop.6257.1.6d0af84d26",
251
+ "@open-mercato/ui": "0.6.6-develop.6257.1.6d0af84d26",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6256.1.9fc16aedc4",
257
- "@open-mercato/shared": "0.6.6-develop.6256.1.9fc16aedc4",
258
- "@open-mercato/ui": "0.6.6-develop.6256.1.9fc16aedc4",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6257.1.6d0af84d26",
257
+ "@open-mercato/shared": "0.6.6-develop.6257.1.6d0af84d26",
258
+ "@open-mercato/ui": "0.6.6-develop.6257.1.6d0af84d26",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",
@@ -10,9 +10,14 @@ import {
10
10
 
11
11
  export const metadata = {
12
12
  PUT: { requireAuth: true, requireFeatures: ['messages.view'] },
13
+ DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },
13
14
  }
14
15
 
15
- export async function PUT(req: Request, { params }: { params: { id: string } }) {
16
+ async function runConversationArchiveMutation(
17
+ req: Request,
18
+ id: string,
19
+ commandId: 'messages.conversation.archive_for_actor' | 'messages.conversation.unarchive_for_actor',
20
+ ) {
16
21
  const { ctx, scope } = await resolveMessageContext(req)
17
22
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
18
23
 
@@ -23,7 +28,7 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
23
28
  organizationId: scope.organizationId,
24
29
  userId: scope.userId,
25
30
  resourceKind: 'messages.conversation',
26
- resourceId: params.id,
31
+ resourceId: id,
27
32
  operation: 'update',
28
33
  requestMethod: req.method,
29
34
  requestHeaders: req.headers,
@@ -39,9 +44,9 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
39
44
  }
40
45
 
41
46
  try {
42
- const { result, logEntry } = await commandBus.execute('messages.conversation.archive_for_actor', {
47
+ const { result, logEntry } = await commandBus.execute(commandId, {
43
48
  input: {
44
- anchorMessageId: params.id,
49
+ anchorMessageId: id,
45
50
  tenantId: scope.tenantId,
46
51
  organizationId: scope.organizationId,
47
52
  userId: scope.userId,
@@ -59,14 +64,14 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
59
64
  const response = Response.json(result)
60
65
  attachOperationMetadataHeader(response, logEntry, {
61
66
  resourceKind: 'messages.conversation',
62
- resourceId: params.id,
67
+ resourceId: id,
63
68
  })
64
69
  await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
65
70
  tenantId: scope.tenantId,
66
71
  organizationId: scope.organizationId,
67
72
  userId: scope.userId,
68
73
  resourceKind: 'messages.conversation',
69
- resourceId: params.id,
74
+ resourceId: id,
70
75
  operation: 'update',
71
76
  requestMethod: req.method,
72
77
  requestHeaders: req.headers,
@@ -85,6 +90,14 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
85
90
  }
86
91
  }
87
92
 
93
+ export async function PUT(req: Request, { params }: { params: { id: string } }) {
94
+ return runConversationArchiveMutation(req, params.id, 'messages.conversation.archive_for_actor')
95
+ }
96
+
97
+ export async function DELETE(req: Request, { params }: { params: { id: string } }) {
98
+ return runConversationArchiveMutation(req, params.id, 'messages.conversation.unarchive_for_actor')
99
+ }
100
+
88
101
  export const openApi: OpenApiRouteDoc = {
89
102
  tag: 'Messages',
90
103
  methods: {
@@ -98,5 +111,15 @@ export const openApi: OpenApiRouteDoc = {
98
111
  { status: 404, description: 'Message not found', schema: errorResponseSchema },
99
112
  ],
100
113
  },
114
+ DELETE: {
115
+ summary: 'Unarchive conversation for current actor',
116
+ responses: [
117
+ { status: 200, description: 'Conversation unarchived', schema: conversationMutationResponseSchema },
118
+ ],
119
+ errors: [
120
+ { status: 403, description: 'Access denied', schema: errorResponseSchema },
121
+ { status: 404, description: 'Message not found', schema: errorResponseSchema },
122
+ ],
123
+ },
101
124
  },
102
125
  }