@rapidmx/web-client 0.30.0 → 0.32.0

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 (402) hide show
  1. package/apps/admin/audit-log/index.tsx +3 -3
  2. package/apps/admin/branding/index.tsx +3 -3
  3. package/apps/admin/data-requests/index.tsx +18 -7
  4. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  5. package/apps/admin/distribution-lists/index.tsx +3 -3
  6. package/apps/admin/distribution-lists/new/index.tsx +3 -3
  7. package/apps/admin/domains/[uid].tsx +6 -5
  8. package/apps/admin/domains/index.tsx +3 -3
  9. package/apps/admin/domains/new/index.tsx +3 -3
  10. package/apps/admin/escrow-scopes/[uid].tsx +11 -5
  11. package/apps/admin/escrow-scopes/index.tsx +3 -3
  12. package/apps/admin/escrow-scopes/new/index.tsx +9 -3
  13. package/apps/admin/index.tsx +4 -4
  14. package/apps/admin/ingest-queue/index.tsx +3 -3
  15. package/apps/admin/mailboxes/[uid].tsx +15 -7
  16. package/apps/admin/quarantine/index.tsx +4 -4
  17. package/apps/admin/retention-policy/index.tsx +3 -3
  18. package/apps/admin/signing-certificates/index.tsx +3 -3
  19. package/apps/admin/transport-rules/[uid].tsx +4 -4
  20. package/apps/admin/transport-rules/index.tsx +3 -3
  21. package/apps/admin/transport-rules/new/index.tsx +3 -3
  22. package/apps/escrow/matters/[uid].tsx +26 -2
  23. package/apps/escrow/matters/new/index.tsx +4 -0
  24. package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
  25. package/apps/shared/calendar/useCalendarReminders.ts +2 -0
  26. package/apps/shared/components/admin/ActionAlert.tsx +32 -0
  27. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +3 -3
  28. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
  29. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
  30. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  31. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
  32. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +2 -2
  33. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  34. package/apps/shared/components/admin/elevation.ts +19 -0
  35. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
  36. package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
  37. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
  38. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
  39. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
  40. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  41. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
  42. package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
  43. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
  44. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
  45. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
  46. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
  47. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
  48. package/apps/shared/components/admin/settings/PluginsManager.tsx +195 -46
  49. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
  50. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
  51. package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
  52. package/apps/shared/components/admin/signOut.ts +3 -0
  53. package/apps/shared/components/admin/usePagedList.tsx +2 -2
  54. package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
  55. package/apps/shared/components/calendar/EventDetails.tsx +34 -8
  56. package/apps/shared/components/calendar/EventEditor.tsx +90 -40
  57. package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
  58. package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
  59. package/apps/shared/components/calendar/EventModal.tsx +25 -1
  60. package/apps/shared/components/calendar/FindATime.tsx +10 -2
  61. package/apps/shared/components/calendar/ListView.tsx +99 -25
  62. package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
  63. package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
  64. package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
  65. package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
  66. package/apps/shared/components/calendar/gridLayout.ts +94 -0
  67. package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
  68. package/apps/shared/components/calendar/timePicker.ts +5 -2
  69. package/apps/shared/components/contacts/ContactCard.tsx +15 -4
  70. package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
  71. package/apps/shared/components/contacts/ContactForm.tsx +61 -17
  72. package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
  73. package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
  74. package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
  75. package/apps/shared/components/contacts/participantDetails.ts +18 -1
  76. package/apps/shared/components/layout/AppShell.tsx +9 -0
  77. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
  78. package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
  79. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
  80. package/apps/shared/components/mail/ConversationList.tsx +49 -6
  81. package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
  82. package/apps/shared/components/mail/MenuButton.tsx +3 -400
  83. package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
  84. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
  85. package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
  86. package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
  87. package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
  88. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
  89. package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
  90. package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
  91. package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
  92. package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
  93. package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
  94. package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
  95. package/apps/shared/components/mail/writableMailboxes.ts +2 -0
  96. package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
  97. package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
  99. package/apps/shared/components/tasks/dueDate.ts +29 -0
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
  101. package/apps/shared/mail/decryptedMessages.ts +51 -2
  102. package/apps/shared/mail/listSnapshots.ts +2 -0
  103. package/apps/shared/mail/outbox/sendDecision.ts +14 -3
  104. package/apps/shared/mail/outbox/sendJob.ts +5 -0
  105. package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
  106. package/apps/shared/mail/usePermanentDelete.tsx +3 -1
  107. package/apps/shared/notifications/apiErrors.ts +2 -0
  108. package/apps/shared/notifications/preferences.ts +1 -1
  109. package/apps/shared/notifications/store.ts +3 -1
  110. package/apps/shared/plugins/pluginNav.ts +4 -2
  111. package/apps/www/calendar/index.tsx +24 -12
  112. package/apps/www/contacts/[uid].tsx +6 -1
  113. package/apps/www/contacts/index.tsx +66 -12
  114. package/apps/www/index.tsx +91 -28
  115. package/apps/www/messages/[uid].tsx +8 -3
  116. package/apps/www/settings/auto-reply/index.tsx +13 -0
  117. package/apps/www/settings/blocked-senders/index.tsx +4 -0
  118. package/apps/www/settings/encryption/index.tsx +66 -4
  119. package/apps/www/settings/privacy/index.tsx +4 -1
  120. package/apps/www/settings/profile/index.tsx +12 -1
  121. package/apps/www/settings/sharing/index.tsx +11 -1
  122. package/apps/www/settings/signatures/[uid].tsx +6 -4
  123. package/apps/www/tasks/index.tsx +97 -29
  124. package/dist/apps/admin/audit-log/index.js +3 -3
  125. package/dist/apps/admin/branding/index.js +3 -3
  126. package/dist/apps/admin/data-requests/index.js +17 -7
  127. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  128. package/dist/apps/admin/distribution-lists/index.js +3 -3
  129. package/dist/apps/admin/distribution-lists/new/index.js +3 -3
  130. package/dist/apps/admin/domains/[uid].js +6 -5
  131. package/dist/apps/admin/domains/index.js +3 -3
  132. package/dist/apps/admin/domains/new/index.js +3 -3
  133. package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
  134. package/dist/apps/admin/escrow-scopes/index.js +3 -3
  135. package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
  136. package/dist/apps/admin/index.js +4 -4
  137. package/dist/apps/admin/ingest-queue/index.js +3 -3
  138. package/dist/apps/admin/mailboxes/[uid].js +15 -7
  139. package/dist/apps/admin/quarantine/index.js +4 -4
  140. package/dist/apps/admin/retention-policy/index.js +3 -3
  141. package/dist/apps/admin/signing-certificates/index.js +3 -3
  142. package/dist/apps/admin/transport-rules/[uid].js +4 -4
  143. package/dist/apps/admin/transport-rules/index.js +3 -3
  144. package/dist/apps/admin/transport-rules/new/index.js +3 -3
  145. package/dist/apps/escrow/matters/[uid].js +8 -3
  146. package/dist/apps/escrow/matters/new/index.js +4 -0
  147. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
  148. package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
  149. package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
  150. package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
  151. package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
  152. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +2 -2
  153. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
  154. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
  155. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  156. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
  157. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +2 -2
  158. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  159. package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
  160. package/dist/apps/shared/components/admin/elevation.js +16 -0
  161. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
  162. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
  163. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
  164. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
  165. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
  166. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  167. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  168. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
  169. package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
  170. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
  171. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
  172. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
  173. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
  174. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/PluginsManager.js +103 -18
  176. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
  177. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
  178. package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
  179. package/dist/apps/shared/components/admin/signOut.js +3 -0
  180. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  181. package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
  182. package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
  183. package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
  184. package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
  185. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
  186. package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
  187. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
  188. package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
  189. package/dist/apps/shared/components/calendar/EventModal.js +10 -2
  190. package/dist/apps/shared/components/calendar/FindATime.js +4 -3
  191. package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
  192. package/dist/apps/shared/components/calendar/ListView.js +92 -27
  193. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  194. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
  195. package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
  196. package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
  197. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
  198. package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
  199. package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
  201. package/dist/apps/shared/components/calendar/timePicker.js +5 -2
  202. package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
  203. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
  204. package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
  205. package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
  206. package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
  207. package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
  208. package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
  209. package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
  210. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
  211. package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
  212. package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
  213. package/dist/apps/shared/components/layout/AppShell.js +9 -0
  214. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
  215. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
  216. package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
  217. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
  218. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
  219. package/dist/apps/shared/components/mail/ConversationList.js +49 -6
  220. package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
  221. package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
  222. package/dist/apps/shared/components/mail/MenuButton.js +2 -211
  223. package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
  224. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
  225. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
  226. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
  227. package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
  228. package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
  229. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
  230. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
  231. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
  232. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
  233. package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
  234. package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
  235. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
  236. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
  237. package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
  238. package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
  239. package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
  240. package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
  241. package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
  242. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
  243. package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
  244. package/dist/apps/shared/components/tasks/dueDate.js +26 -0
  245. package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
  246. package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
  247. package/dist/apps/shared/mail/decryptedMessages.js +46 -2
  248. package/dist/apps/shared/mail/listSnapshots.js +2 -0
  249. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
  250. package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
  251. package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
  252. package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
  253. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
  254. package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
  255. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  256. package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
  257. package/dist/apps/shared/notifications/apiErrors.js +2 -0
  258. package/dist/apps/shared/notifications/preferences.d.ts +1 -1
  259. package/dist/apps/shared/notifications/preferences.js +1 -1
  260. package/dist/apps/shared/notifications/store.js +3 -1
  261. package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
  262. package/dist/apps/shared/plugins/pluginNav.js +4 -2
  263. package/dist/apps/www/calendar/index.js +24 -12
  264. package/dist/apps/www/contacts/[uid].js +6 -2
  265. package/dist/apps/www/contacts/index.js +62 -11
  266. package/dist/apps/www/index.js +85 -30
  267. package/dist/apps/www/messages/[uid].js +8 -3
  268. package/dist/apps/www/settings/auto-reply/index.js +13 -1
  269. package/dist/apps/www/settings/blocked-senders/index.js +4 -0
  270. package/dist/apps/www/settings/encryption/index.js +29 -5
  271. package/dist/apps/www/settings/privacy/index.js +3 -2
  272. package/dist/apps/www/settings/profile/index.js +4 -1
  273. package/dist/apps/www/settings/sharing/index.js +10 -1
  274. package/dist/apps/www/settings/signatures/[uid].js +6 -4
  275. package/dist/apps/www/tasks/index.js +94 -32
  276. package/dist/lib/admin/auditLogApi.js +2 -2
  277. package/dist/lib/admin/distributionListsApi.js +2 -2
  278. package/dist/lib/admin/domainsApi.js +2 -2
  279. package/dist/lib/admin/escrowAuditLogApi.js +2 -2
  280. package/dist/lib/admin/escrowScopesApi.js +2 -2
  281. package/dist/lib/admin/pluginsApi.d.ts +31 -2
  282. package/dist/lib/admin/pluginsApi.js +23 -1
  283. package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
  284. package/dist/lib/admin/retentionPolicyApi.js +3 -0
  285. package/dist/lib/admin/transportRulesApi.js +2 -2
  286. package/dist/lib/appearance/preferencesApi.d.ts +1 -1
  287. package/dist/lib/appearance/preferencesApi.js +3 -14
  288. package/dist/lib/auth/session.d.ts +51 -12
  289. package/dist/lib/auth/session.js +214 -38
  290. package/dist/lib/branding/brandingApi.d.ts +3 -3
  291. package/dist/lib/branding/brandingApi.js +9 -21
  292. package/dist/lib/calendar/calendarApi.js +3 -1
  293. package/dist/lib/calendar/eventSearch.js +43 -6
  294. package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
  295. package/dist/lib/calendar/freeBusyApi.js +13 -5
  296. package/dist/lib/calendar/recurrence.js +51 -3
  297. package/dist/lib/calendar/useDayKey.d.ts +7 -0
  298. package/dist/lib/calendar/useDayKey.js +43 -0
  299. package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
  300. package/dist/lib/components/avatar/ContactAvatar.js +37 -8
  301. package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
  302. package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
  303. package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
  304. package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
  305. package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
  306. package/dist/lib/components/forms/ImageEditBadge.js +62 -0
  307. package/dist/lib/components/menus/MenuButton.d.ts +78 -0
  308. package/dist/lib/components/menus/MenuButton.js +219 -0
  309. package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
  310. package/dist/lib/components/navigation/BottomTabBar.js +22 -3
  311. package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
  312. package/dist/lib/components/overlays/PopoverPortal.js +3 -2
  313. package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
  314. package/dist/lib/contacts/contactsApi.d.ts +5 -2
  315. package/dist/lib/contacts/contactsApi.js +16 -19
  316. package/dist/lib/contacts/gravatar.d.ts +14 -3
  317. package/dist/lib/contacts/gravatar.js +42 -5
  318. package/dist/lib/contacts/preparePhoto.d.ts +24 -0
  319. package/dist/lib/contacts/preparePhoto.js +250 -0
  320. package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
  321. package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
  322. package/dist/lib/contacts/vcard.d.ts +3 -0
  323. package/dist/lib/contacts/vcard.js +59 -7
  324. package/dist/lib/crypto/escrowKeys.d.ts +8 -0
  325. package/dist/lib/crypto/escrowKeys.js +20 -1
  326. package/dist/lib/crypto/messageSecurity.d.ts +7 -4
  327. package/dist/lib/crypto/messageSecurity.js +23 -5
  328. package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
  329. package/dist/lib/crypto/passwordUnlock.js +13 -2
  330. package/dist/lib/crypto/smime.js +12 -2
  331. package/dist/lib/mail/compose/composeQuoting.js +3 -2
  332. package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
  333. package/dist/lib/mail/compose/quotedImages.js +35 -9
  334. package/dist/lib/mail/labelsApi.js +2 -2
  335. package/dist/lib/mail/mailApi.d.ts +8 -5
  336. package/dist/lib/mail/mailApi.js +73 -39
  337. package/dist/lib/mail/mailDetailHooks.js +11 -3
  338. package/dist/lib/mail/mailFilterRulesApi.js +2 -2
  339. package/dist/lib/mail/mailSignaturesApi.js +2 -2
  340. package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
  341. package/dist/lib/mail/mailboxImportApi.js +3 -15
  342. package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
  343. package/dist/lib/mail/messageBodySanitizer.js +44 -1
  344. package/dist/lib/search/queryGrammar.js +12 -4
  345. package/dist/lib/search/searchTier3.d.ts +6 -1
  346. package/dist/lib/search/searchTier3.js +54 -25
  347. package/dist/lib/tasks/tasksApi.js +3 -3
  348. package/dist/lib/util/api.d.ts +26 -4
  349. package/dist/lib/util/api.js +73 -16
  350. package/dist/lib/util/apiQuery.d.ts +11 -0
  351. package/dist/lib/util/apiQuery.js +11 -0
  352. package/dist/lib/util/settleWithLimit.d.ts +5 -0
  353. package/dist/lib/util/settleWithLimit.js +25 -0
  354. package/lib/admin/auditLogApi.ts +2 -2
  355. package/lib/admin/distributionListsApi.ts +2 -2
  356. package/lib/admin/domainsApi.ts +2 -2
  357. package/lib/admin/escrowAuditLogApi.ts +2 -2
  358. package/lib/admin/escrowScopesApi.ts +2 -2
  359. package/lib/admin/pluginsApi.ts +45 -3
  360. package/lib/admin/retentionPolicyApi.ts +4 -0
  361. package/lib/admin/transportRulesApi.ts +2 -2
  362. package/lib/appearance/preferencesApi.ts +3 -14
  363. package/lib/auth/session.ts +239 -39
  364. package/lib/branding/brandingApi.ts +9 -21
  365. package/lib/calendar/calendarApi.ts +3 -1
  366. package/lib/calendar/eventSearch.ts +46 -6
  367. package/lib/calendar/freeBusyApi.ts +14 -6
  368. package/lib/calendar/recurrence.ts +59 -8
  369. package/lib/calendar/useDayKey.ts +45 -0
  370. package/lib/components/avatar/ContactAvatar.tsx +46 -8
  371. package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
  372. package/lib/components/forms/CameraCaptureModal.tsx +133 -0
  373. package/lib/components/forms/ImageEditBadge.tsx +121 -0
  374. package/lib/components/menus/MenuButton.tsx +408 -0
  375. package/lib/components/navigation/BottomTabBar.tsx +30 -8
  376. package/lib/components/overlays/PopoverPortal.tsx +3 -2
  377. package/lib/components/pickers/TimeZonePicker.tsx +17 -1
  378. package/lib/contacts/contactsApi.ts +18 -19
  379. package/lib/contacts/gravatar.ts +47 -5
  380. package/lib/contacts/preparePhoto.ts +273 -0
  381. package/lib/contacts/useContactPhotoSrc.ts +177 -0
  382. package/lib/contacts/vcard.ts +60 -7
  383. package/lib/crypto/escrowKeys.ts +21 -1
  384. package/lib/crypto/messageSecurity.ts +33 -9
  385. package/lib/crypto/passwordUnlock.ts +14 -2
  386. package/lib/crypto/smime.ts +13 -2
  387. package/lib/mail/compose/composeQuoting.ts +3 -2
  388. package/lib/mail/compose/quotedImages.ts +39 -9
  389. package/lib/mail/labelsApi.ts +2 -2
  390. package/lib/mail/mailApi.ts +96 -39
  391. package/lib/mail/mailDetailHooks.ts +11 -3
  392. package/lib/mail/mailFilterRulesApi.ts +2 -2
  393. package/lib/mail/mailSignaturesApi.ts +2 -2
  394. package/lib/mail/mailboxImportApi.ts +9 -15
  395. package/lib/mail/messageBodySanitizer.ts +47 -1
  396. package/lib/search/queryGrammar.ts +13 -4
  397. package/lib/search/searchTier3.ts +65 -32
  398. package/lib/tasks/tasksApi.ts +3 -3
  399. package/lib/util/api.ts +99 -16
  400. package/lib/util/apiQuery.ts +13 -0
  401. package/lib/util/settleWithLimit.ts +25 -0
  402. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { ChangeEvent, useEffect, useRef, useState } from "react";
6
- import { ApiRequestError } from "../../../lib/util/api.js";
6
+ import { actionErrorMessage } from "../../shared/components/admin/elevation.js";
7
7
  import {
8
8
  AdminSigningEnrollment,
9
9
  getSigningEnrollmentInfo,
@@ -15,7 +15,7 @@ import {
15
15
  uploadSigningEnrollmentCertificate,
16
16
  } from "../../../lib/crypto/signingProviderApi.js";
17
17
  import AdminShell, { AdminShellProps } from "../../shared/components/admin/layout/AdminShell.js";
18
- import Alert from "../../../lib/components/feedback/Alert.js";
18
+ import Alert from "../../shared/components/admin/ActionAlert.js";
19
19
  import Button from "../../../lib/components/buttons/Button.js";
20
20
  import Modal from "../../../lib/components/overlays/Modal.js";
21
21
  import { notify } from "../../shared/notifications/store.js";
@@ -24,7 +24,7 @@ const INPUT_CLASS = "text-sm border border-border rounded-sm py-1.5 px-2 bg-surf
24
24
  const TEXTAREA_CLASS = `${INPUT_CLASS} font-mono text-xs`;
25
25
 
26
26
  function errorMessage(err: unknown, fallback: string): string {
27
- return err instanceof ApiRequestError ? err.message : fallback;
27
+ return actionErrorMessage(err, fallback);
28
28
  }
29
29
 
30
30
  function statusBadgeClass(status: AdminSigningEnrollment["status"]): string {
@@ -3,7 +3,6 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useEffect, useState } from "react";
6
- import { ApiRequestError } from "../../../lib/util/api.js";
7
6
  import {
8
7
  getTransportRule,
9
8
  TransportRule,
@@ -20,9 +19,10 @@ import {
20
19
  TRANSPORT_RULE_ACTION_TYPES,
21
20
  TRANSPORT_RULE_CONDITION_FIELDS,
22
21
  } from "./_transportRuleConfig.js";
23
- import Alert from "../../../lib/components/feedback/Alert.js";
22
+ import Alert from "../../shared/components/admin/ActionAlert.js";
24
23
  import Button from "../../../lib/components/buttons/Button.js";
25
24
  import FormField from "../../../lib/components/forms/FormField.js";
25
+ import { actionErrorMessage } from "../../shared/components/admin/elevation.js";
26
26
 
27
27
  const INPUT_CLASS =
28
28
  "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
@@ -63,7 +63,7 @@ function TransportRuleDetailContent({ uid }: { uid: string }) {
63
63
  });
64
64
  }
65
65
  })
66
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this transport rule."))
66
+ .catch((err) => setError(actionErrorMessage(err, "Could not load this transport rule.")))
67
67
  .finally(() => setLoading(false));
68
68
  }, [uid]);
69
69
 
@@ -106,7 +106,7 @@ function TransportRuleDetailContent({ uid }: { uid: string }) {
106
106
  setOriginal(updated);
107
107
  setSaved(true);
108
108
  } catch (err) {
109
- setError(err instanceof ApiRequestError ? err.message : "Could not save this transport rule.");
109
+ setError(actionErrorMessage(err, "Could not save this transport rule."));
110
110
  } finally {
111
111
  setSaving(false);
112
112
  }
@@ -3,11 +3,11 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { useEffect, useState } from "react";
6
- import { ApiRequestError } from "../../../lib/util/api.js";
7
6
  import { listTransportRules, TransportRule } from "../../../lib/admin/transportRulesApi.js";
8
7
  import AdminShell, { AdminShellProps } from "../../shared/components/admin/layout/AdminShell.js";
9
- import Alert from "../../../lib/components/feedback/Alert.js";
8
+ import Alert from "../../shared/components/admin/ActionAlert.js";
10
9
  import Button from "../../../lib/components/buttons/Button.js";
10
+ import { actionErrorMessage } from "../../shared/components/admin/elevation.js";
11
11
 
12
12
  const PAGE_SIZE = 25;
13
13
 
@@ -48,7 +48,7 @@ function TransportRulesContent() {
48
48
  useEffect(() => {
49
49
  listAllTransportRules()
50
50
  .then(setRules)
51
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load transport rules."))
51
+ .catch((err) => setError(actionErrorMessage(err, "Could not load transport rules.")))
52
52
  .finally(() => setLoading(false));
53
53
  }, []);
54
54
 
@@ -4,7 +4,6 @@
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useState } from "react";
6
6
  import { useRouter } from "@rapidrest/react/client";
7
- import { ApiRequestError } from "../../../../lib/util/api.js";
8
7
  import {
9
8
  createTransportRule,
10
9
  TransportRuleAction,
@@ -19,9 +18,10 @@ import {
19
18
  TRANSPORT_RULE_ACTION_TYPES,
20
19
  TRANSPORT_RULE_CONDITION_FIELDS,
21
20
  } from "../_transportRuleConfig.js";
22
- import Alert from "../../../../lib/components/feedback/Alert.js";
21
+ import Alert from "../../../shared/components/admin/ActionAlert.js";
23
22
  import Button from "../../../../lib/components/buttons/Button.js";
24
23
  import FormField from "../../../../lib/components/forms/FormField.js";
24
+ import { actionErrorMessage } from "../../../shared/components/admin/elevation.js";
25
25
 
26
26
  const INPUT_CLASS =
27
27
  "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
@@ -77,7 +77,7 @@ function NewTransportRuleForm() {
77
77
  const created = await createTransportRule({ name: name.trim(), ...rule });
78
78
  void navigate(`/admin/transport-rules/${encodeURIComponent(created.uid)}`);
79
79
  } catch (err) {
80
- setError(err instanceof ApiRequestError ? err.message : "Could not create the transport rule.");
80
+ setError(actionErrorMessage(err, "Could not create the transport rule."));
81
81
  } finally {
82
82
  setSaving(false);
83
83
  }
@@ -77,6 +77,8 @@ function MatterDetailContent({ uid }: { uid: string }) {
77
77
  const [closing, setClosing] = useState(false);
78
78
  const [closeError, setCloseError] = useState<string | null>(null);
79
79
  const [approveTarget, setApproveTarget] = useState<EscrowAccessRequest | null>(null);
80
+ // Denying cannot be undone - a denied request is never approved afterwards, only asked for again - so it is confirmed first.
81
+ const [denyTarget, setDenyTarget] = useState<EscrowAccessRequest | null>(null);
80
82
 
81
83
  const [showNewRequest, setShowNewRequest] = useState(false);
82
84
  const [newRequestMailboxUid, setNewRequestMailboxUid] = useState("");
@@ -184,7 +186,10 @@ function MatterDetailContent({ uid }: { uid: string }) {
184
186
  }
185
187
  }
186
188
 
187
- async function handleDeny(request: EscrowAccessRequest) {
189
+ // Only ever invoked from the deny-confirmation modal below, which only renders once `denyTarget` is set.
190
+ async function handleDeny() {
191
+ const request = denyTarget!;
192
+ setDenyTarget(null);
188
193
  setActingOn((prev) => ({ ...prev, [request.uid]: true }));
189
194
  setActionErrors((prev) => ({ ...prev, [request.uid]: "" }));
190
195
  try {
@@ -381,7 +386,7 @@ function MatterDetailContent({ uid }: { uid: string }) {
381
386
  className="!w-auto"
382
387
  loading={actingOn[request.uid]}
383
388
  disabled={actingOn[request.uid]}
384
- onClick={() => handleDeny(request)}
389
+ onClick={() => setDenyTarget(request)}
385
390
  >
386
391
  Deny
387
392
  </Button>
@@ -562,6 +567,25 @@ function MatterDetailContent({ uid }: { uid: string }) {
562
567
  </div>
563
568
  </Modal>
564
569
 
570
+ <Modal open={denyTarget !== null} onClose={() => setDenyTarget(null)} title="Deny access request">
571
+ <p className="text-sm mb-3">
572
+ Denying refuses this request for good: it can&rsquo;t be approved afterwards, and holders can&rsquo;t read the key material for
573
+ mailbox <span className="break-all font-semibold">{denyTarget?.mailboxUid}</span> through it. A new request has to be made to ask again.
574
+ </p>
575
+ <div className="flex gap-3 justify-end">
576
+ <Button type="button" variant="secondary" className="!w-auto" onClick={() => setDenyTarget(null)}>
577
+ Cancel
578
+ </Button>
579
+ <Button
580
+ type="button"
581
+ className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger"
582
+ onClick={() => void handleDeny()}
583
+ >
584
+ Deny request
585
+ </Button>
586
+ </div>
587
+ </Modal>
588
+
565
589
  <Modal open={showNewRequest} onClose={() => setShowNewRequest(false)} title="New access request">
566
590
  <form onSubmit={handleCreateRequest} className="flex flex-col gap-4">
567
591
  {newRequestError && <Alert>{newRequestError}</Alert>}
@@ -59,6 +59,10 @@ function NewMatterForm() {
59
59
  setError("At least one custodian mailbox is required.");
60
60
  return;
61
61
  }
62
+ if (Number.isNaN(new Date(dateRangeStart).getTime()) || Number.isNaN(new Date(dateRangeEnd).getTime())) {
63
+ setError("The date range start and end are both required.");
64
+ return;
65
+ }
62
66
  if (new Date(dateRangeStart).getTime() >= new Date(dateRangeEnd).getTime()) {
63
67
  setError("The date range start must be before its end.");
64
68
  return;
@@ -2,7 +2,7 @@
2
2
  // Copyright (C) 2026 Jean-Philippe Steinmetz
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
- /** Pure helper behind the calendar page's live updates: which push events say "an event changed, and the payload is only a busy block". */
5
+ /** Pure helper behind the calendar page's live updates: which push events say "an event changed". */
6
6
 
7
7
  import type { PushEvent } from "../../../lib/mail/pushClient.js";
8
8
 
@@ -11,17 +11,18 @@ import type { PushEvent } from "../../../lib/mail/pushClient.js";
11
11
  const EVENT_MODEL = /^CalendarEvent(Mongo|SQL)$/;
12
12
 
13
13
  /**
14
- * The uid of the event a push event announces as a redacted busy block, or `undefined` for anything else the shared push connection delivers.
14
+ * The event a push event says was created, changed or deleted - its uid, and whether it is gone - or `undefined` for anything else the shared push connection
15
+ * delivers.
15
16
  *
16
- * When somebody creates or changes a private or confidential event, restapi publishes it on the calendar's channel once, for every subscriber - and that one
17
- * payload has to be safe for a read-only reader of a shared calendar, so it is the busy block (`redacted: true`, title "Busy", no details). It must not be
18
- * stored as the event by the calendar's owner (or anybody else who may see it in full): they fetch the event again by its uid, which answers with as
19
- * much as they may see.
17
+ * The payload is never stored as the event: when somebody creates or changes a private or confidential event, restapi publishes it on the calendar's channel
18
+ * once, for every subscriber, and that one payload has to be safe for a read-only reader of a shared calendar, so it is the busy block (`redacted: true`,
19
+ * title "Busy", no details). Whoever may see the event in full fetches it again by its uid, which answers with as much as they may see - so every created or
20
+ * changed event is handled that way, an ordinary one (an invitation arriving, an edit on another device) included.
20
21
  */
21
- export function redactedEventUidOf(event: PushEvent): string | undefined {
22
- if (!EVENT_MODEL.test(event.type) || (event.action !== "create" && event.action !== "update")) {
22
+ export function changedEventOf(event: PushEvent): { uid: string; deleted: boolean } | undefined {
23
+ if (!EVENT_MODEL.test(event.type) || (event.action !== "create" && event.action !== "update" && event.action !== "delete")) {
23
24
  return undefined;
24
25
  }
25
- const data = event.data as { uid?: unknown; redacted?: unknown } | null | undefined;
26
- return data?.redacted === true && typeof data.uid === "string" ? data.uid : undefined;
26
+ const data = event.data as { uid?: unknown } | null | undefined;
27
+ return typeof data?.uid === "string" ? { uid: data.uid, deleted: event.action === "delete" } : undefined;
27
28
  }
@@ -45,6 +45,8 @@ export function useCalendarReminders({ userUid, enabled }: UseCalendarRemindersO
45
45
  kind: "calendar",
46
46
  title: notice.title,
47
47
  message: reminderMessage(notice.startDate),
48
+ // Anyone who can invite you can put any address in an event's location: the button says where it goes.
49
+ hint: joinUrl ? `Join Meeting opens ${new URL(joinUrl).host}` : undefined,
48
50
  href: CALENDAR_HREF,
49
51
  sticky: true,
50
52
  actions: [
@@ -0,0 +1,32 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { ReactNode, createContext, useContext } from "react";
6
+ import Alert from "../../../../lib/components/feedback/Alert.js";
7
+ import { isElevationMessage } from "./elevation.js";
8
+
9
+ /**
10
+ * What `AdminShell` offers the page inside it: sends the browser to auth-server to confirm the user's identity again, then back to this page. `undefined`
11
+ * outside the shell, and in one with no auth-server to send the browser to.
12
+ */
13
+ export const ReconfirmIdentityContext = createContext<(() => void) | undefined>(undefined);
14
+
15
+ /**
16
+ * `Alert` for the failure of an administrator action. When the failure is the server's refusal for want of a recently confirmed identity (see
17
+ * `actionErrorMessage()`), it also offers to confirm it again right there rather than leaving the user to work out that reloading the page does it.
18
+ */
19
+ export default function ActionAlert({ children }: { children: ReactNode }) {
20
+ const reconfirm = useContext(ReconfirmIdentityContext);
21
+ if (!reconfirm || typeof children !== "string" || !isElevationMessage(children)) {
22
+ return <Alert>{children}</Alert>;
23
+ }
24
+ return (
25
+ <Alert>
26
+ <span className="flex-1">{children}</span>
27
+ <button type="button" className="shrink-0 font-semibold underline" onClick={reconfirm}>
28
+ Confirm identity again
29
+ </button>
30
+ </Alert>
31
+ );
32
+ }
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React from "react";
6
- import Alert from "../../../../../lib/components/feedback/Alert.js";
6
+ import Alert from "../ActionAlert.js";
7
7
  import type { DiagnosticsInformation, DiagnosticsVersions } from "./diagnosticsApi.js";
8
8
  import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
9
9
  import ComponentsTable from "./ComponentsTable.js";
@@ -41,13 +41,13 @@ export default function InformationPanel({ versions, plugins, information }: Inf
41
41
  <SettingsTable
42
42
  id="diagnostics-environment"
43
43
  title="Environment variables"
44
- description="The server process's environment. Only variables known to be harmless show their value; the value of a secret, or of anything unrecognized, is withheld by the server and never sent."
44
+ description="The server process's environment. Every variable shows its value except those on the server's list of hidden settings, whose value is withheld by the server and never sent."
45
45
  settings={information.data.environment ?? []}
46
46
  />
47
47
  <SettingsTable
48
48
  id="diagnostics-configuration"
49
49
  title="Configuration"
50
- description="The effective settings the server runs with: the environment, saved plugin settings and defaults combined. Passwords, tokens, keys, certificates and the credentials in URLs are withheld by the server and never sent."
50
+ description="The effective settings the server runs with: the environment, saved plugin settings and defaults combined. Settings on the server's list of hidden settings, such as passwords, tokens and keys, are withheld by the server and never sent, and so are the credentials in URLs."
51
51
  settings={information.data.configuration ?? []}
52
52
  />
53
53
  </>
@@ -5,7 +5,7 @@
5
5
  import React from "react";
6
6
  import { HiOutlineExclamationTriangle } from "react-icons/hi2";
7
7
  import { getPluginStatus, listPlugins, Plugin, PluginStatus } from "../../../../../lib/admin/pluginsApi.js";
8
- import Alert from "../../../../../lib/components/feedback/Alert.js";
8
+ import Alert from "../ActionAlert.js";
9
9
  import Badge from "./Badge.js";
10
10
  import { NO_VALUE } from "./format.js";
11
11
 
@@ -15,7 +15,7 @@ import {
15
15
  HiOutlineStop,
16
16
  HiOutlineTrash,
17
17
  } from "react-icons/hi2";
18
- import Alert from "../../../../../lib/components/feedback/Alert.js";
18
+ import Alert from "../ActionAlert.js";
19
19
  import Button from "../../../../../lib/components/buttons/Button.js";
20
20
  import Badge, { BadgeTone } from "./Badge.js";
21
21
  import { SaveFile, timestampedFilename } from "./download.js";
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React from "react";
6
- import Alert from "../../../../../lib/components/feedback/Alert.js";
6
+ import Alert from "../ActionAlert.js";
7
7
  import type { DiagnosticsRuntime } from "./diagnosticsApi.js";
8
8
  import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
9
9
  import Badge from "./Badge.js";
@@ -4,7 +4,7 @@
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React from "react";
6
6
  import { HiOutlinePause, HiOutlinePlay } from "react-icons/hi2";
7
- import Alert from "../../../../../lib/components/feedback/Alert.js";
7
+ import Alert from "../ActionAlert.js";
8
8
  import Button from "../../../../../lib/components/buttons/Button.js";
9
9
  import { formatDateTime } from "./format.js";
10
10
  import HostCard from "./HostCard.js";
@@ -76,8 +76,8 @@ export interface DiagnosticsVersions {
76
76
  }
77
77
 
78
78
  /**
79
- * One environment variable or configuration setting. A `redacted` one has no `value`: the server withholds every secret-like
80
- * or unrecognized value and never sends it, so there is nothing for the client to hide.
79
+ * One environment variable or configuration setting. A `redacted` one has no `value`: the server withholds the value of every setting on its
80
+ * hidden list and never sends it, so there is nothing for the client to hide.
81
81
  */
82
82
  export interface DiagnosticsSetting {
83
83
  name: string;
@@ -3,10 +3,10 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useState } from "react";
6
- import { ApiRequestError } from "../../../../../lib/util/api.js";
7
6
  import { DistributionList, updateDistributionList } from "../../../../../lib/admin/distributionListsApi.js";
8
- import Alert from "../../../../../lib/components/feedback/Alert.js";
7
+ import Alert from "../ActionAlert.js";
9
8
  import Button from "../../../../../lib/components/buttons/Button.js";
9
+ import { actionErrorMessage } from "../elevation.js";
10
10
 
11
11
  export interface MemberListCardProps {
12
12
  list: DistributionList;
@@ -45,7 +45,7 @@ export default function MemberListCard({ list, onUpdate }: MemberListCardProps)
45
45
  setNewAddress("");
46
46
  onUpdate(updated);
47
47
  } catch (err) {
48
- setError(err instanceof ApiRequestError ? err.message : "Could not add this member.");
48
+ setError(actionErrorMessage(err, "Could not add this member."));
49
49
  } finally {
50
50
  setSaving(false);
51
51
  }
@@ -62,7 +62,7 @@ export default function MemberListCard({ list, onUpdate }: MemberListCardProps)
62
62
  });
63
63
  onUpdate(updated);
64
64
  } catch (err) {
65
- setError(err instanceof ApiRequestError ? err.message : "Could not remove this member.");
65
+ setError(actionErrorMessage(err, "Could not remove this member."));
66
66
  } finally {
67
67
  setSaving(false);
68
68
  }
@@ -23,6 +23,25 @@ export function isElevationRequired(err: unknown): boolean {
23
23
  return err instanceof ApiRequestError && err.status === 403 && err.code === ELEVATION_REQUIRED_CODE;
24
24
  }
25
25
 
26
+ /** What an administrator action says when the server refuses it because the token isn't elevated (any longer). The console confirms the user's identity
27
+ * when a page opens (see `AdminShell`), so reloading is what sends them round auth-server's confirmation again. */
28
+ export const ELEVATION_ACTION_MESSAGE = "This needs you to have recently confirmed your identity. Reload this page to confirm it again, then try once more.";
29
+
30
+ /** Whether `text` is one of the messages that say an action needs the user's identity confirmed again (this one, a plugin's or the diagnostics'). */
31
+ export function isElevationMessage(text: string): boolean {
32
+ return text.includes("recently confirmed your identity");
33
+ }
34
+
35
+ /** The text for an administrator action that failed: the elevation message when it only needed the user to confirm their identity (an action the
36
+ * server newly gates behind an elevated token - truncating or deleting a mailbox, a retention policy, an export of another mailbox, approving an
37
+ * erasure), else the server's own message, else `fallback`. */
38
+ export function actionErrorMessage(err: unknown, fallback: string): string {
39
+ if (isElevationRequired(err)) {
40
+ return ELEVATION_ACTION_MESSAGE;
41
+ }
42
+ return err instanceof ApiRequestError ? err.message : fallback;
43
+ }
44
+
26
45
  /** auth-server's elevation page: it sends the browser back to `returnTo` once the user has confirmed their
27
46
  * identity, or to its own account page if they cancel. */
28
47
  export function elevationUrl(authServerUrl: string, returnTo: string): string {
@@ -2,7 +2,8 @@
2
2
  // Copyright (C) 2026 Jean-Philippe Steinmetz
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
- import React from "react";
5
+ import React, { useEffect, useState } from "react";
6
+ import { escrowPublicKeyFingerprint, normalizeFingerprint } from "../../../../../lib/crypto/escrowKeys.js";
6
7
  import { toDatetimeLocal } from "../../../../../lib/util/dateInput.js";
7
8
  import { resolveEscrowScopeHolder } from "../../../../../lib/admin/escrowScopesApi.js";
8
9
  import FormField from "../../../../../lib/components/forms/FormField.js";
@@ -52,6 +53,13 @@ export function emptyEscrowScopeKeyAndHoldersValue(): EscrowScopeKeyAndHoldersVa
52
53
  };
53
54
  }
54
55
 
56
+ /** The message for a key validity date left empty (or not a date), which would otherwise be sent as nothing; `null` when both dates are there. */
57
+ export function keyValidityError(value: Pick<EscrowScopeKeyAndHoldersValue, "notBefore" | "notAfter">): string | null {
58
+ return Number.isNaN(new Date(value.notBefore).getTime()) || Number.isNaN(new Date(value.notAfter).getTime())
59
+ ? "The key's validity dates, not before and not after, are both required."
60
+ : null;
61
+ }
62
+
55
63
  /** The message shown when an administrator tries to make themselves a holder: whoever configures a scope must not
56
64
  * also be able to approve access under it (separation of duties - the server refuses it too). Holders who were
57
65
  * already on the scope (`existingHolderUserUids`) aren't treated as newly added. */
@@ -71,6 +79,33 @@ export default function EscrowScopeKeyAndHoldersFields({ value, onChange, disabl
71
79
  onChange({ ...value, [key]: next });
72
80
  }
73
81
 
82
+ // What is typed in the required-holders box, which may be empty meanwhile (to be typed over): the value is then 0, which the pages refuse on submit
83
+ // ("between 1 and the number of holders"), rather than snapping back to 1 under the cursor.
84
+ const [requiredText, setRequiredText] = useState(String(value.requiredHolders));
85
+ useEffect(() => {
86
+ setRequiredText((text) => (Number(text) === value.requiredHolders ? text : String(value.requiredHolders)));
87
+ }, [value.requiredHolders]);
88
+
89
+ // What the pasted key's own SHA-256 fingerprint is (`null`: it is no certificate), so the typed one - which the holders compare out of band -
90
+ // can be checked against it. `undefined` until worked out.
91
+ const [actualFingerprint, setActualFingerprint] = useState<string | null | undefined>(undefined);
92
+ const checking = !keyReadOnly && value.publicKey.trim() !== "";
93
+ useEffect(() => {
94
+ if (!checking) {
95
+ return;
96
+ }
97
+ let cancelled = false;
98
+ setActualFingerprint(undefined);
99
+ void escrowPublicKeyFingerprint(value.publicKey).then((fingerprint) => {
100
+ if (!cancelled) {
101
+ setActualFingerprint(fingerprint);
102
+ }
103
+ });
104
+ return () => {
105
+ cancelled = true;
106
+ };
107
+ }, [checking, value.publicKey]);
108
+
74
109
  return (
75
110
  <>
76
111
  <div className="bg-surface border border-border rounded-md p-6">
@@ -115,6 +150,23 @@ export default function EscrowScopeKeyAndHoldersFields({ value, onChange, disabl
115
150
  readOnly={keyReadOnly}
116
151
  onChange={(e) => set("fingerprint", e.target.value)}
117
152
  />
153
+ {checking && actualFingerprint === null && (
154
+ <p role="alert" className="text-xs text-danger mt-1.5">
155
+ This is not a base64-encoded certificate, so no fingerprint can be checked against it.
156
+ </p>
157
+ )}
158
+ {checking && actualFingerprint && normalizeFingerprint(value.fingerprint) !== actualFingerprint && (
159
+ <p role="alert" className="text-xs text-danger mt-1.5">
160
+ This fingerprint is not the one of the public key above, whose SHA-256 fingerprint is{" "}
161
+ <code className="font-mono break-all">{actualFingerprint}</code>.{" "}
162
+ <button type="button" className="underline font-semibold" disabled={disabled} onClick={() => set("fingerprint", actualFingerprint)}>
163
+ Use it
164
+ </button>
165
+ </p>
166
+ )}
167
+ {checking && actualFingerprint && normalizeFingerprint(value.fingerprint) === actualFingerprint && (
168
+ <p className="text-xs text-text-muted mt-1.5">This is the fingerprint of the public key above.</p>
169
+ )}
118
170
  </FormField>
119
171
 
120
172
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4">
@@ -171,9 +223,12 @@ export default function EscrowScopeKeyAndHoldersFields({ value, onChange, disabl
171
223
  id="requiredHolders"
172
224
  type="number"
173
225
  className={INPUT_CLASS}
174
- value={value.requiredHolders}
226
+ value={requiredText}
175
227
  disabled={disabled}
176
- onChange={(e) => set("requiredHolders", Number(e.target.value) || 1)}
228
+ onChange={(e) => {
229
+ setRequiredText(e.target.value);
230
+ set("requiredHolders", Number(e.target.value) || 0);
231
+ }}
177
232
  />
178
233
  </FormField>
179
234
 
@@ -48,6 +48,7 @@ import {
48
48
  recordElevationAttempt,
49
49
  } from "../elevation.js";
50
50
  import { signOutOfConsole } from "../signOut.js";
51
+ import { ReconfirmIdentityContext } from "../ActionAlert.js";
51
52
  import { pluginIcon } from "../../../plugins/pluginIcons.js";
52
53
  import { mergePluginNavItems, PluginNav, PluginNavProps } from "../../../plugins/pluginNav.js";
53
54
 
@@ -402,7 +403,11 @@ export default function AdminShell({ active, userUid, authServerUrl, pluginNav,
402
403
  <UserMenu userUid={userUid} authServerUrl={authServerUrl} onSignOut={handleSignOut} showMailLink />
403
404
  </header>
404
405
  <div id="app-content" className="flex-1">
405
- <main className="max-w-6xl mx-auto px-6 py-8">{children}</main>
406
+ <main className="max-w-6xl mx-auto px-6 py-8">
407
+ <ReconfirmIdentityContext.Provider value={authServerUrl ? handleRetryElevation : undefined}>
408
+ {children}
409
+ </ReconfirmIdentityContext.Provider>
410
+ </main>
406
411
  </div>
407
412
  </div>
408
413
  </div>
@@ -3,13 +3,13 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useEffect, useRef, useState } from "react";
6
- import { ApiRequestError } from "../../../../../lib/util/api.js";
7
6
  import { eraseLeftoverMailbox } from "../../../../../lib/admin/leftoverMailboxApi.js";
8
7
  import { DataSubjectErasureStatus, getErasureRequest } from "../../../../../lib/mail/erasureRequestApi.js";
9
- import Alert from "../../../../../lib/components/feedback/Alert.js";
8
+ import Alert from "../ActionAlert.js";
10
9
  import Button from "../../../../../lib/components/buttons/Button.js";
11
10
  import Modal from "../../../../../lib/components/overlays/Modal.js";
12
11
  import { notify } from "../../../notifications/store.js";
12
+ import { actionErrorMessage } from "../elevation.js";
13
13
 
14
14
  const INPUT_CLASS =
15
15
  "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
@@ -86,7 +86,7 @@ export default function EraseLeftoverDataDialog({
86
86
  setPhase("watching");
87
87
  } catch (err) {
88
88
  // The server's own words: a legal hold names its matter, a mailbox that exists again says so.
89
- setError(err instanceof ApiRequestError ? err.message : "Could not start the erasure.");
89
+ setError(actionErrorMessage(err, "Could not start the erasure."));
90
90
  setPhase("confirm");
91
91
  }
92
92
  }
@@ -3,13 +3,13 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useEffect, useState } from "react";
6
- import { ApiRequestError } from "../../../../../lib/util/api.js";
7
6
  import { EscrowScope, listEscrowScopes } from "../../../../../lib/admin/escrowScopesApi.js";
8
7
  import { Mailbox, updateMailbox } from "../../../../../lib/mail/mailApi.js";
9
8
  import { useApiClient } from "../../../../../lib/util/apiClientContext.js";
10
- import Alert from "../../../../../lib/components/feedback/Alert.js";
9
+ import Alert from "../ActionAlert.js";
11
10
  import Button from "../../../../../lib/components/buttons/Button.js";
12
11
  import Modal from "../../../../../lib/components/overlays/Modal.js";
12
+ import { actionErrorMessage } from "../elevation.js";
13
13
 
14
14
  const SELECT_CLASS =
15
15
  "text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
@@ -40,7 +40,7 @@ export default function EscrowScopeCard({ mailbox, onUpdate }: EscrowScopeCardPr
40
40
  useEffect(() => {
41
41
  listEscrowScopes({ limit: SCOPE_LIST_LIMIT }, client)
42
42
  .then(setScopes)
43
- .catch((err) => setLoadError(err instanceof ApiRequestError ? err.message : "Could not load escrow scopes."));
43
+ .catch((err) => setLoadError(actionErrorMessage(err, "Could not load escrow scopes.")));
44
44
  }, []);
45
45
 
46
46
  // Changing the scope changes who can recover this mailbox's encrypted mail, so it's confirmed first.
@@ -59,7 +59,7 @@ export default function EscrowScopeCard({ mailbox, onUpdate }: EscrowScopeCardPr
59
59
  onUpdate(updated);
60
60
  setSaved(true);
61
61
  } catch (err) {
62
- setSaveError(err instanceof ApiRequestError ? err.message : "Could not save the escrow scope.");
62
+ setSaveError(actionErrorMessage(err, "Could not save the escrow scope."));
63
63
  } finally {
64
64
  setSaving(false);
65
65
  }