@open-mercato/core 0.6.3-develop.3609.1.01983ef87a → 0.6.3-develop.3635.1.e4a05c16e8

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.
@@ -442,15 +442,16 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
442
442
  )
443
443
  }, [integrationId])
444
444
 
445
- const loadDetail = React.useCallback(async () => {
445
+ const loadDetail = React.useCallback(async (options?: { showLoading?: boolean }) => {
446
+ const showLoading = options?.showLoading ?? true
446
447
  const currentIntegrationId = resolveCurrentIntegrationId()
447
448
  if (!currentIntegrationId) {
448
- setIsLoading(false)
449
- setError(t('integrations.detail.loadError', 'Failed to load integration'))
449
+ if (showLoading) setIsLoading(false)
450
+ if (showLoading) setError(t('integrations.detail.loadError', 'Failed to load integration'))
450
451
  return
451
452
  }
452
- setError(null)
453
- setIsLoading(true)
453
+ if (showLoading) setError(null)
454
+ if (showLoading) setIsLoading(true)
454
455
  try {
455
456
  const call = await apiCall<IntegrationDetail>(
456
457
  `/api/integrations/${encodeURIComponent(currentIntegrationId)}`,
@@ -458,15 +459,16 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
458
459
  { fallback: null },
459
460
  )
460
461
  if (!call.ok || !call.result) {
461
- setError(t('integrations.detail.loadError', 'Failed to load integration'))
462
- setIsLoading(false)
462
+ if (showLoading) setError(t('integrations.detail.loadError', 'Failed to load integration'))
463
+ if (showLoading) setIsLoading(false)
463
464
  return
464
465
  }
465
466
  setDetail(call.result)
466
- setIsLoading(false)
467
+ setError(null)
468
+ if (showLoading) setIsLoading(false)
467
469
  } catch {
468
- setError(t('integrations.detail.loadError', 'Failed to load integration'))
469
- setIsLoading(false)
470
+ if (showLoading) setError(t('integrations.detail.loadError', 'Failed to load integration'))
471
+ if (showLoading) setIsLoading(false)
470
472
  }
471
473
  }, [resolveCurrentIntegrationId, t])
472
474
 
@@ -522,14 +524,14 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
522
524
  spotId: detailWidgetSpotId,
523
525
  })
524
526
  const refreshDetail = React.useCallback(async () => {
525
- await loadDetail()
527
+ await loadDetail({ showLoading: false })
526
528
  await loadCredentials()
527
529
  }, [loadCredentials, loadDetail])
528
530
  const refreshLogs = React.useCallback(async () => {
529
531
  await loadLogs()
530
532
  }, [loadLogs])
531
533
  const refreshHealthSnapshot = React.useCallback(async () => {
532
- await loadDetail()
534
+ await loadDetail({ showLoading: false })
533
535
  }, [loadDetail])
534
536
  const runIdFromUrl = searchParams?.get('runId') ?? null
535
537
  const refreshRunActivity = React.useCallback(async (options?: { showLoading?: boolean }) => {
@@ -546,7 +548,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
546
548
  { fallback: null },
547
549
  )
548
550
  await loadLogs()
549
- await loadDetail()
551
+ await loadDetail({ showLoading: false })
550
552
  if (call.ok && call.result) {
551
553
  setActiveRunDetail(call.result)
552
554
  setActiveRunRefreshedAt(new Date().toISOString())
@@ -58,6 +58,28 @@ async function emitMessageDeletedEvent(_container: ContainerWithResolve, payload
58
58
  )
59
59
  }
60
60
 
61
+ async function emitMessageGloballyDeletedEvent(_container: ContainerWithResolve, payload: {
62
+ messageId: string
63
+ actorUserId: string
64
+ recipientUserIds: string[]
65
+ tenantId: string
66
+ organizationId: string | null
67
+ }) {
68
+ const audience = Array.from(new Set([payload.actorUserId, ...payload.recipientUserIds]))
69
+ await emitMessagesEvent(
70
+ 'messages.message.deleted',
71
+ {
72
+ messageId: payload.messageId,
73
+ actorUserId: payload.actorUserId,
74
+ target: 'global',
75
+ recipientUserIds: audience,
76
+ tenantId: payload.tenantId,
77
+ organizationId: payload.organizationId,
78
+ },
79
+ { persistent: true },
80
+ )
81
+ }
82
+
61
83
  async function emitMessageIndexUpsert(
62
84
  container: ContainerWithResolve,
63
85
  payload: {
@@ -346,6 +368,15 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
346
368
  if (!message) return
347
369
  message.deletedAt = new Date()
348
370
  await em.flush()
371
+ if (!after.message.isDraft) {
372
+ await emitMessageGloballyDeletedEvent(ctx.container, {
373
+ messageId: after.message.id,
374
+ actorUserId: after.message.senderUserId,
375
+ recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),
376
+ tenantId: after.message.tenantId,
377
+ organizationId: after.message.organizationId,
378
+ })
379
+ }
349
380
  },
350
381
  }
351
382
 
@@ -704,6 +735,13 @@ const replyMessageCommand: CommandHandler<unknown, { id: string; externalEmail:
704
735
  if (!message) return
705
736
  message.deletedAt = new Date()
706
737
  await em.flush()
738
+ await emitMessageGloballyDeletedEvent(ctx.container, {
739
+ messageId: after.message.id,
740
+ actorUserId: after.message.senderUserId,
741
+ recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),
742
+ tenantId: after.message.tenantId,
743
+ organizationId: after.message.organizationId,
744
+ })
707
745
  },
708
746
  }
709
747
 
@@ -849,6 +887,13 @@ const forwardMessageCommand: CommandHandler<unknown, { id: string; externalEmail
849
887
  if (!message) return
850
888
  message.deletedAt = new Date()
851
889
  await em.flush()
890
+ await emitMessageGloballyDeletedEvent(ctx.container, {
891
+ messageId: after.message.id,
892
+ actorUserId: after.message.senderUserId,
893
+ recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),
894
+ tenantId: after.message.tenantId,
895
+ organizationId: after.message.organizationId,
896
+ })
852
897
  },
853
898
  }
854
899
 
@@ -896,12 +941,16 @@ const deleteForActorCommand: CommandHandler<unknown, { ok: true }> = {
896
941
  return { ok: true }
897
942
  }
898
943
  if (message.senderUserId === input.userId) {
944
+ const recipientRows = await em.find(MessageRecipient, { messageId: input.messageId })
899
945
  message.deletedAt = new Date()
900
946
  await em.flush()
901
- await emitMessageDeletedEvent(ctx.container, {
947
+ const recipientUserIds = recipientRows
948
+ .map((row) => row.recipientUserId)
949
+ .filter((value): value is string => typeof value === 'string' && value.length > 0)
950
+ await emitMessageGloballyDeletedEvent(ctx.container, {
902
951
  messageId: input.messageId,
903
952
  actorUserId: input.userId,
904
- target: 'sender',
953
+ recipientUserIds,
905
954
  tenantId: input.tenantId,
906
955
  organizationId: input.organizationId,
907
956
  })
@@ -25,11 +25,11 @@ type UseMessageDetailsActionsInput = {
25
25
  id: string
26
26
  t: TranslateFn
27
27
  detail: MessageDetail | null
28
- detailQuery: UseQueryResult<MessageDetail, Error>
28
+ detailQuery: UseQueryResult<MessageDetail | null, Error>
29
29
  attachments: MessageAttachment[] | undefined
30
30
  isArchived: boolean
31
31
  onDeleted: () => void
32
- refreshDetailWithoutAutoMarkRead: () => Promise<MessageDetail>
32
+ refreshDetailWithoutAutoMarkRead: () => Promise<MessageDetail | null>
33
33
  }
34
34
 
35
35
  export function useMessageDetailsActions({
@@ -26,10 +26,13 @@ export function useMessageDetailsQueries({
26
26
  [id, scopeVersion],
27
27
  )
28
28
 
29
- const detailQuery = useQuery({
29
+ const detailQuery = useQuery<MessageDetail | null>({
30
30
  queryKey: detailQueryKey,
31
31
  queryFn: async () => {
32
32
  const call = await apiCall<MessageDetail>(`/api/messages/${encodeURIComponent(id)}`)
33
+ if (call.status === 404) {
34
+ return null
35
+ }
33
36
  if (!call.ok || !call.result) {
34
37
  throw new Error(
35
38
  toErrorMessage(call.result)
@@ -42,17 +45,24 @@ export function useMessageDetailsQueries({
42
45
 
43
46
  const detail = detailQuery.data ?? null
44
47
  const isLoadingDetail = detailQuery.isLoading
45
- const loadErrorMessage = detailQuery.error instanceof Error
46
- ? detailQuery.error.message
47
- : t('messages.errors.loadDetailFailed', 'Failed to load message details.')
48
+ const isDetailMissing = detailQuery.isSuccess && detailQuery.data === null
49
+ const loadErrorMessage = isDetailMissing
50
+ ? t('messages.errors.messageDeleted', 'This message is no longer available.')
51
+ : detailQuery.error instanceof Error
52
+ ? detailQuery.error.message
53
+ : t('messages.errors.loadDetailFailed', 'Failed to load message details.')
48
54
 
49
- const attachmentsQuery = useQuery({
55
+ const attachmentsQuery = useQuery<MessageAttachment[]>({
50
56
  queryKey: ['messages', 'detail', id, 'attachments', scopeVersion],
51
57
  queryFn: async () => {
52
58
  const call = await apiCall<{ attachments?: MessageAttachment[] }>(
53
59
  `/api/messages/${encodeURIComponent(id)}/attachments`,
54
60
  )
55
61
 
62
+ if (call.status === 404) {
63
+ return []
64
+ }
65
+
56
66
  if (!call.ok) {
57
67
  throw new Error(
58
68
  toErrorMessage(call.result)
@@ -71,6 +81,11 @@ export function useMessageDetailsQueries({
71
81
  `/api/messages/${encodeURIComponent(id)}?skipMarkRead=1`,
72
82
  )
73
83
 
84
+ if (call.status === 404) {
85
+ queryClient.setQueryData(detailQueryKey, null)
86
+ return null
87
+ }
88
+
74
89
  if (!call.ok || !call.result) {
75
90
  throw new Error(
76
91
  toErrorMessage(call.result)
@@ -91,6 +106,7 @@ export function useMessageDetailsQueries({
91
106
  detailQuery,
92
107
  detail,
93
108
  isLoadingDetail,
109
+ isDetailMissing,
94
110
  loadErrorMessage,
95
111
  attachmentsQuery,
96
112
  attachments,
@@ -120,6 +120,7 @@
120
120
  "messages.errors.loadObjectOptionsFailed": "Objektoptionen konnten nicht geladen werden.",
121
121
  "messages.errors.loadObjectTypesFailed": "Objekttypen konnten nicht geladen werden.",
122
122
  "messages.errors.loadTypesFailed": "Nachrichtentypen konnten nicht geladen werden.",
123
+ "messages.errors.messageDeleted": "Diese Nachricht ist nicht mehr verfügbar.",
123
124
  "messages.errors.noBody": "Bitte Nachrichtentext eingeben.",
124
125
  "messages.errors.noExternalEmail": "Bitte gib eine gültige externe E-Mail ein.",
125
126
  "messages.errors.noRecipients": "Füge mindestens einen Empfänger hinzu.",
@@ -120,6 +120,7 @@
120
120
  "messages.errors.loadObjectOptionsFailed": "Failed to load object options.",
121
121
  "messages.errors.loadObjectTypesFailed": "Failed to load object types.",
122
122
  "messages.errors.loadTypesFailed": "Failed to load message types.",
123
+ "messages.errors.messageDeleted": "This message is no longer available.",
123
124
  "messages.errors.noBody": "Please enter a message.",
124
125
  "messages.errors.noExternalEmail": "Please enter a valid external email.",
125
126
  "messages.errors.noRecipients": "Please add at least one recipient.",
@@ -120,6 +120,7 @@
120
120
  "messages.errors.loadObjectOptionsFailed": "No se pudieron cargar las opciones de objeto.",
121
121
  "messages.errors.loadObjectTypesFailed": "No se pudieron cargar los tipos de objeto.",
122
122
  "messages.errors.loadTypesFailed": "No se pudieron cargar los tipos de mensaje.",
123
+ "messages.errors.messageDeleted": "Este mensaje ya no está disponible.",
123
124
  "messages.errors.noBody": "Introduce el contenido del mensaje.",
124
125
  "messages.errors.noExternalEmail": "Introduce un correo externo válido.",
125
126
  "messages.errors.noRecipients": "Añade al menos un destinatario.",
@@ -120,6 +120,7 @@
120
120
  "messages.errors.loadObjectOptionsFailed": "Nie udało się załadować opcji obiektów.",
121
121
  "messages.errors.loadObjectTypesFailed": "Nie udało się załadować typów obiektów.",
122
122
  "messages.errors.loadTypesFailed": "Nie udało się załadować typów wiadomości.",
123
+ "messages.errors.messageDeleted": "Ta wiadomość nie jest już dostępna.",
123
124
  "messages.errors.noBody": "Wpisz treść wiadomości.",
124
125
  "messages.errors.noExternalEmail": "Wpisz poprawny zewnętrzny adres e-mail.",
125
126
  "messages.errors.noRecipients": "Dodaj przynajmniej jednego odbiorcę.",