@rapidmx/web-client 0.9.0 → 0.11.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 (443) hide show
  1. package/README.md +373 -88
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/auth/accountUrl.ts +9 -0
  21. package/apps/shared/auth/adminAccess.ts +99 -0
  22. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  23. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  25. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +2 -1
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  27. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  28. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  29. package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
  30. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  31. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  32. package/apps/shared/components/contacts/ContactsToolbar.tsx +98 -115
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +477 -288
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
  38. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
  39. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  40. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  41. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  42. package/apps/shared/components/layout/UserMenu.tsx +372 -185
  43. package/apps/shared/components/mail/ConversationList.tsx +271 -262
  44. package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
  45. package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
  46. package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
  47. package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
  48. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  49. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  50. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
  51. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
  52. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
  53. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
  54. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
  55. package/apps/shared/components/mail/compose/composePerf.ts +46 -0
  56. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  57. package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
  58. package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
  59. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  60. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  61. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  62. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  63. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  64. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  65. package/apps/shared/components/mail/reading/color.ts +245 -0
  66. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  67. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  68. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  69. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  70. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  71. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  72. package/apps/shared/components/mail/unreadStyle.tsx +75 -0
  73. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  74. package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
  75. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  76. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  77. package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
  78. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
  79. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
  80. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
  81. package/apps/shared/keyboard/dispatch.ts +124 -0
  82. package/apps/shared/keyboard/format.ts +89 -0
  83. package/apps/shared/keyboard/keymap.ts +114 -0
  84. package/apps/shared/keyboard/match.ts +44 -0
  85. package/apps/shared/keyboard/parse.ts +136 -0
  86. package/apps/shared/keyboard/platform.ts +34 -0
  87. package/apps/shared/keyboard/registry.ts +65 -0
  88. package/apps/shared/keyboard/targets.ts +79 -0
  89. package/apps/shared/keyboard/useShortcut.ts +50 -0
  90. package/apps/shared/keyboard/useShortcutProps.ts +17 -0
  91. package/apps/shared/mail/folderCounts.ts +308 -0
  92. package/apps/shared/mail/folderTree.ts +143 -0
  93. package/apps/shared/mail/listSnapshots.ts +87 -0
  94. package/apps/shared/mail/messageReadState.ts +85 -0
  95. package/apps/shared/mail/newMailNotifications.ts +183 -0
  96. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  97. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  98. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  99. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  100. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  101. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  102. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  103. package/apps/shared/mail/outbox/sendState.ts +35 -0
  104. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  105. package/apps/shared/mail/useMailConnection.ts +203 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
  107. package/apps/shared/mail/useMarkMessageRead.ts +47 -0
  108. package/apps/shared/mail/useNewMailNotifications.ts +178 -0
  109. package/apps/shared/mail/useUnreadTitle.ts +42 -0
  110. package/apps/shared/navigation/AppRouter.tsx +300 -0
  111. package/apps/shared/navigation/appHrefs.ts +23 -0
  112. package/apps/shared/navigation/frameContext.tsx +35 -0
  113. package/apps/shared/navigation/idle.ts +45 -0
  114. package/apps/shared/navigation/routerContext.tsx +83 -0
  115. package/apps/shared/navigation/routes.ts +72 -0
  116. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  117. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  118. package/apps/shared/notifications/apiErrors.ts +98 -0
  119. package/apps/shared/notifications/headerOffset.ts +35 -0
  120. package/apps/shared/notifications/pushStatus.ts +56 -0
  121. package/apps/shared/notifications/store.ts +549 -0
  122. package/apps/shared/notifications/systemErrors.ts +63 -0
  123. package/apps/shared/notifications/useNotifications.ts +41 -0
  124. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
  125. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  126. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  127. package/apps/shared/signing/enrollmentView.ts +251 -0
  128. package/apps/shared/signing/signingInfo.ts +58 -0
  129. package/apps/shared/signing/useNow.ts +19 -0
  130. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  131. package/apps/shared/styles/app.css +108 -10
  132. package/apps/www/_layout.tsx +8 -2
  133. package/apps/www/_routedPage.tsx +24 -0
  134. package/apps/www/_routes.ts +36 -0
  135. package/apps/www/calendar/index.tsx +64 -33
  136. package/apps/www/contacts/[uid].tsx +109 -107
  137. package/apps/www/contacts/index.tsx +62 -42
  138. package/apps/www/index.tsx +2291 -1917
  139. package/apps/www/messages/[uid].tsx +108 -101
  140. package/apps/www/settings/appearance/index.tsx +21 -0
  141. package/apps/www/settings/auto-reply/index.tsx +7 -8
  142. package/apps/www/settings/encryption/index.tsx +1287 -1249
  143. package/apps/www/settings/filters/[uid].tsx +7 -2
  144. package/apps/www/settings/filters/index.tsx +102 -99
  145. package/apps/www/settings/filters/new/index.tsx +140 -133
  146. package/apps/www/settings/labels/index.tsx +204 -201
  147. package/apps/www/settings/privacy/index.tsx +7 -9
  148. package/apps/www/settings/read-receipts/index.tsx +7 -8
  149. package/apps/www/settings/sharing/index.tsx +256 -274
  150. package/apps/www/settings/signatures/[uid].tsx +172 -167
  151. package/apps/www/settings/signatures/index.tsx +88 -85
  152. package/apps/www/settings/signatures/new/index.tsx +135 -129
  153. package/apps/www/tasks/index.tsx +29 -18
  154. package/dist/apps/admin/_layout.d.ts +5 -1
  155. package/dist/apps/admin/_layout.js +3 -2
  156. package/dist/apps/admin/index.js +3 -2
  157. package/dist/apps/admin/ingest-queue/index.js +2 -1
  158. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  159. package/dist/apps/admin/quarantine/index.js +2 -1
  160. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  161. package/dist/apps/admin/signing-certificates/index.js +161 -0
  162. package/dist/apps/escrow/_layout.d.ts +5 -1
  163. package/dist/apps/escrow/_layout.js +3 -2
  164. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  165. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  166. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  167. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  168. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  169. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  170. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  171. package/dist/apps/shared/appearance/ColorField.js +40 -0
  172. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  173. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  174. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  175. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  176. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  177. package/dist/apps/shared/appearance/bootScript.js +33 -0
  178. package/dist/apps/shared/appearance/color.d.ts +65 -0
  179. package/dist/apps/shared/appearance/color.js +132 -0
  180. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  181. package/dist/apps/shared/appearance/photo.js +79 -0
  182. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  183. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  184. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  185. package/dist/apps/shared/appearance/theme.js +251 -0
  186. package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
  187. package/dist/apps/shared/auth/accountUrl.js +8 -0
  188. package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
  189. package/dist/apps/shared/auth/adminAccess.js +89 -0
  190. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  191. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  192. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  193. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  194. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  195. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  196. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
  197. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  198. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  199. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  201. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
  202. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  203. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  204. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
  205. package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
  206. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  207. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
  208. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  209. package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
  210. package/dist/apps/shared/components/layout/AppShell.js +122 -17
  211. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  212. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  213. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
  214. package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
  215. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  216. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  217. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  218. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  219. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  220. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  221. package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
  222. package/dist/apps/shared/components/layout/UserMenu.js +88 -7
  223. package/dist/apps/shared/components/mail/ConversationList.js +5 -11
  224. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
  225. package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
  226. package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
  227. package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
  228. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
  229. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
  230. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
  231. package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
  232. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  233. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  234. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  235. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  236. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
  237. package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
  238. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
  239. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
  240. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
  241. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
  242. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
  243. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
  244. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
  245. package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
  246. package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
  247. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  248. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  249. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
  250. package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
  251. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
  252. package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
  253. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  254. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  255. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  256. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  257. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  258. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  259. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  260. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  261. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  262. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  263. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  264. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  265. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  266. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  267. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  268. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  269. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  270. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  271. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  272. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  273. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  274. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  275. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  276. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  277. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  278. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  279. package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
  280. package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
  281. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  282. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  283. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  284. package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
  285. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  286. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  287. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  288. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  289. package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
  290. package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
  291. package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
  292. package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
  293. package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
  294. package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
  295. package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
  296. package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
  297. package/dist/apps/shared/keyboard/dispatch.js +109 -0
  298. package/dist/apps/shared/keyboard/format.d.ts +12 -0
  299. package/dist/apps/shared/keyboard/format.js +74 -0
  300. package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
  301. package/dist/apps/shared/keyboard/keymap.js +84 -0
  302. package/dist/apps/shared/keyboard/match.d.ts +20 -0
  303. package/dist/apps/shared/keyboard/match.js +31 -0
  304. package/dist/apps/shared/keyboard/parse.d.ts +31 -0
  305. package/dist/apps/shared/keyboard/parse.js +107 -0
  306. package/dist/apps/shared/keyboard/platform.d.ts +15 -0
  307. package/dist/apps/shared/keyboard/platform.js +22 -0
  308. package/dist/apps/shared/keyboard/registry.d.ts +39 -0
  309. package/dist/apps/shared/keyboard/registry.js +33 -0
  310. package/dist/apps/shared/keyboard/targets.d.ts +14 -0
  311. package/dist/apps/shared/keyboard/targets.js +67 -0
  312. package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
  313. package/dist/apps/shared/keyboard/useShortcut.js +35 -0
  314. package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
  315. package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
  316. package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
  317. package/dist/apps/shared/mail/folderCounts.js +218 -0
  318. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  319. package/dist/apps/shared/mail/folderTree.js +116 -0
  320. package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
  321. package/dist/apps/shared/mail/listSnapshots.js +43 -0
  322. package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
  323. package/dist/apps/shared/mail/messageReadState.js +63 -0
  324. package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
  325. package/dist/apps/shared/mail/newMailNotifications.js +138 -0
  326. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  327. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  328. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  329. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  330. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  331. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  332. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  333. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  334. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  335. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  336. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  337. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  338. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  339. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  340. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  341. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  342. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  343. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  344. package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
  345. package/dist/apps/shared/mail/useMailConnection.js +138 -0
  346. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
  347. package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
  348. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
  349. package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
  350. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
  351. package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
  352. package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
  353. package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
  354. package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
  355. package/dist/apps/shared/navigation/AppRouter.js +242 -0
  356. package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
  357. package/dist/apps/shared/navigation/appHrefs.js +20 -0
  358. package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
  359. package/dist/apps/shared/navigation/frameContext.js +24 -0
  360. package/dist/apps/shared/navigation/idle.d.ts +14 -0
  361. package/dist/apps/shared/navigation/idle.js +43 -0
  362. package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
  363. package/dist/apps/shared/navigation/routerContext.js +56 -0
  364. package/dist/apps/shared/navigation/routes.d.ts +32 -0
  365. package/dist/apps/shared/navigation/routes.js +37 -0
  366. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  367. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  368. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  369. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  370. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  371. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  372. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  373. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  374. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  375. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  376. package/dist/apps/shared/notifications/store.d.ts +136 -0
  377. package/dist/apps/shared/notifications/store.js +416 -0
  378. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  379. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  380. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  381. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  382. package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
  383. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  384. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  385. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  386. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  387. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  388. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  389. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  390. package/dist/apps/shared/signing/signingInfo.js +48 -0
  391. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  392. package/dist/apps/shared/signing/useNow.js +18 -0
  393. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  394. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  395. package/dist/apps/shared/styles/app.css +108 -10
  396. package/dist/apps/www/_layout.d.ts +5 -1
  397. package/dist/apps/www/_layout.js +3 -2
  398. package/dist/apps/www/_routedPage.d.ts +12 -0
  399. package/dist/apps/www/_routedPage.js +19 -0
  400. package/dist/apps/www/_routes.d.ts +11 -0
  401. package/dist/apps/www/_routes.js +30 -0
  402. package/dist/apps/www/calendar/index.d.ts +2 -2
  403. package/dist/apps/www/calendar/index.js +41 -11
  404. package/dist/apps/www/contacts/[uid].d.ts +3 -9
  405. package/dist/apps/www/contacts/[uid].js +11 -9
  406. package/dist/apps/www/contacts/index.d.ts +2 -2
  407. package/dist/apps/www/contacts/index.js +30 -24
  408. package/dist/apps/www/index.d.ts +2 -2
  409. package/dist/apps/www/index.js +332 -48
  410. package/dist/apps/www/messages/[uid].d.ts +3 -7
  411. package/dist/apps/www/messages/[uid].js +10 -5
  412. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  413. package/dist/apps/www/settings/appearance/index.js +13 -0
  414. package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
  415. package/dist/apps/www/settings/auto-reply/index.js +6 -7
  416. package/dist/apps/www/settings/encryption/index.d.ts +2 -2
  417. package/dist/apps/www/settings/encryption/index.js +106 -96
  418. package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
  419. package/dist/apps/www/settings/filters/[uid].js +6 -2
  420. package/dist/apps/www/settings/filters/index.d.ts +2 -2
  421. package/dist/apps/www/settings/filters/index.js +3 -1
  422. package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
  423. package/dist/apps/www/settings/filters/new/index.js +9 -3
  424. package/dist/apps/www/settings/labels/index.d.ts +2 -2
  425. package/dist/apps/www/settings/labels/index.js +3 -1
  426. package/dist/apps/www/settings/privacy/index.d.ts +2 -2
  427. package/dist/apps/www/settings/privacy/index.js +8 -9
  428. package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
  429. package/dist/apps/www/settings/read-receipts/index.js +6 -7
  430. package/dist/apps/www/settings/sharing/index.d.ts +2 -2
  431. package/dist/apps/www/settings/sharing/index.js +22 -34
  432. package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
  433. package/dist/apps/www/settings/signatures/[uid].js +6 -2
  434. package/dist/apps/www/settings/signatures/index.d.ts +2 -2
  435. package/dist/apps/www/settings/signatures/index.js +3 -1
  436. package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
  437. package/dist/apps/www/settings/signatures/new/index.js +9 -4
  438. package/dist/apps/www/tasks/index.d.ts +2 -2
  439. package/dist/apps/www/tasks/index.js +22 -16
  440. package/package.json +2 -2
  441. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  442. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  443. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../../_routedPage.js";
7
+ import { useNavigate } from "../../../../shared/navigation/AppRouter.js";
6
8
  import { useEffect, useState } from "react";
7
9
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
10
  import { listFolders } from "@rapidmx/react-shared/mail/mailApi.js";
@@ -13,11 +15,13 @@ import { MAIL_FILTER_CONDITION_FIELDS, buildMailFilterActionTypes } from "../_ma
13
15
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
16
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
15
17
  import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
18
+ import { notifyApiError } from "../../../../shared/notifications/apiErrors.js";
16
19
  const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
17
- export default function NewMailFilterPage(props) {
20
+ function NewMailFilterPage(props) {
18
21
  return (_jsx(SettingsShell, { ...props, active: "filters", children: _jsx(NewMailFilterForm, {}) }));
19
22
  }
20
23
  function NewMailFilterForm() {
24
+ const navigate = useNavigate();
21
25
  const { mailboxUid } = useSettingsShell();
22
26
  const [folders, setFolders] = useState(null);
23
27
  const [folderError, setFolderError] = useState(null);
@@ -54,10 +58,11 @@ function NewMailFilterForm() {
54
58
  setSaving(true);
55
59
  try {
56
60
  const created = await createMailFilterRule({ mailboxUid: mailboxUid, name: name.trim(), ...rule });
57
- window.location.href = `/settings/filters/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`;
61
+ navigate(`/settings/filters/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`);
58
62
  }
59
63
  catch (err) {
60
- setError(err instanceof ApiRequestError ? err.message : "Could not create the mail filter.");
64
+ // A pop-up: `error` above is only the form's own validation.
65
+ notifyApiError(err, "Couldn't create the mail filter");
61
66
  }
62
67
  finally {
63
68
  setSaving(false);
@@ -65,3 +70,4 @@ function NewMailFilterForm() {
65
70
  }
66
71
  return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-3xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "New mail filter" }), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "Applied in order once a message is verdicted safe to deliver, before it reaches your Inbox." }), error && _jsx(Alert, { children: error }), folderError && _jsx(Alert, { children: folderError }), folders === null ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : (_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsx("div", { className: "bg-surface border border-border rounded-md p-6", children: _jsx(FormField, { label: "Name", htmlFor: "name", children: _jsx("input", { id: "name", type: "text", className: INPUT_CLASS, value: name, onChange: (e) => setName(e.target.value), placeholder: "File newsletters" }) }) }), _jsx(RuleBuilder, { value: rule, onChange: setRule, conditionFields: MAIL_FILTER_CONDITION_FIELDS, actionTypes: buildMailFilterActionTypes(folders), showValidation: showValidation }), _jsxs("div", { className: "flex gap-3", children: [_jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Create filter" }), _jsx("a", { href: `/settings/filters?mailboxUid=${encodeURIComponent(mailboxUid)}`, children: _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", children: "Cancel" }) })] })] }))] }) }));
67
72
  }
73
+ export default routedPage("/settings/filters/new", NewMailFilterPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SettingsLabelsPageProps = Omit<SettingsShellProps, "active">;
4
- export default function SettingsLabelsPage(props: SettingsLabelsPageProps): React.JSX.Element;
3
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsLabelsPageProps>;
4
+ export default _default;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useEffect, useState } from "react";
7
8
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
9
  import { createLabel, deleteLabel, listLabels, updateLabel } from "@rapidmx/react-shared/mail/labelsApi.js";
@@ -11,7 +12,7 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
12
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
12
13
  import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
13
14
  import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
14
- export default function SettingsLabelsPage(props) {
15
+ function SettingsLabelsPage(props) {
15
16
  return (_jsx(SettingsShell, { ...props, active: "labels", children: _jsx(LabelsContent, {}) }));
16
17
  }
17
18
  const DEFAULT_COLOR = "#6366f1";
@@ -93,3 +94,4 @@ function LabelsContent() {
93
94
  setDeleteError(null);
94
95
  }, children: "Delete" })] })] }, label.uid))) }))] }), _jsxs(Modal, { open: formOpen, onClose: () => setFormOpen(false), title: editing ? "Edit label" : "New label", children: [formError && _jsx(Alert, { children: formError }), _jsx(FormField, { label: "Name", htmlFor: "label-name", children: _jsx("input", { id: "label-name", type: "text", value: formName, onChange: (e) => setFormName(e.target.value), className: "w-full text-sm px-3 py-1.5 rounded-md border border-border bg-surface" }) }), _jsx(FormField, { label: "Color", htmlFor: "label-color", children: _jsx("input", { id: "label-color", type: "color", value: formColor, onChange: (e) => setFormColor(e.target.value), className: "h-9 w-16 rounded-md border border-border bg-surface" }) }), _jsxs("div", { className: "flex justify-end gap-2 mt-2", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setFormOpen(false), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto", loading: saving, disabled: saving || !formName.trim(), onClick: handleSave, children: "Save" })] })] }), _jsxs(Modal, { open: !!deleting, onClose: () => setDeleting(null), title: "Delete this label?", children: [deleteError && _jsx(Alert, { children: deleteError }), _jsxs("p", { className: "text-sm text-text-muted mb-4", children: ["This removes \u201C", deleting?.name, "\u201D from every message it's applied to. This can't be undone."] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setDeleting(null), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto", loading: deleteBusy, disabled: deleteBusy, onClick: handleDelete, children: "Delete label" })] })] })] }));
95
96
  }
97
+ export default routedPage("/settings/labels", SettingsLabelsPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SettingsPrivacyPageProps = Omit<SettingsShellProps, "active">;
4
- export default function SettingsPrivacyPage(props: SettingsPrivacyPageProps): React.JSX.Element;
3
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsPrivacyPageProps>;
4
+ export default _default;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useEffect, useRef, useState } from "react";
7
8
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
9
  import { createExportRequest, exportRequestDownloadUrl, listExportRequests, } from "@rapidmx/react-shared/mail/dataExportApi.js";
@@ -13,6 +14,7 @@ import SettingsShell, { useSettingsShell } from "../../../shared/components/sett
13
14
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
15
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
15
16
  import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
17
+ import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
16
18
  const NON_MAIL_FOLDER_TYPES = new Set(["calendar", "contacts", "tasks", "notes"]);
17
19
  function importFormatFromFilename(filename) {
18
20
  return filename.toLowerCase().endsWith(".pst") ? "pst" : "mbox";
@@ -31,7 +33,7 @@ function statusBadgeClass(status) {
31
33
  function statusLabel(status) {
32
34
  return status.replace(/_/g, " ");
33
35
  }
34
- export default function SettingsPrivacyPage(props) {
36
+ function SettingsPrivacyPage(props) {
35
37
  return (_jsx(SettingsShell, { ...props, active: "privacy", children: _jsx(PrivacyContent, { userUid: props.userUid }) }));
36
38
  }
37
39
  function PrivacyContent({ userUid }) {
@@ -68,7 +70,6 @@ function ExportSection({ mailboxUid, mailboxLabel }) {
68
70
  const [loadError, setLoadError] = useState(null);
69
71
  const [format, setFormat] = useState("json");
70
72
  const [creating, setCreating] = useState(false);
71
- const [createError, setCreateError] = useState(null);
72
73
  // The create form below isn't gated behind `loading`, so a fast create-then-reload can resolve before
73
74
  // the initial mount fetch does; without this guard the mount fetch's now-stale response would land
74
75
  // last and silently revert the list. Only the most-recently-issued load's response is ever applied.
@@ -92,20 +93,19 @@ function ExportSection({ mailboxUid, mailboxLabel }) {
92
93
  }, [mailboxUid]);
93
94
  async function handleCreate(e) {
94
95
  e.preventDefault();
95
- setCreateError(null);
96
96
  setCreating(true);
97
97
  try {
98
98
  await createExportRequest({ format });
99
99
  await loadRequests();
100
100
  }
101
101
  catch (err) {
102
- setCreateError(err instanceof ApiRequestError ? err.message : "Could not start this export.");
102
+ notifyApiError(err, "Couldn't start the export");
103
103
  }
104
104
  finally {
105
105
  setCreating(false);
106
106
  }
107
107
  }
108
- return (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Export my data" }), _jsxs("p", { className: "text-xs text-text-muted mb-3", children: ["Requests a copy of ", _jsx("strong", { children: mailboxLabel }), "\u2019s messages, contacts, calendar, tasks, and notes. This runs in the background and can take a few minutes \u2014 check back here for a download link once it\u2019s ready."] }), _jsxs("form", { onSubmit: handleCreate, className: "flex items-end gap-3 mb-5", children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Format" }), _jsxs("select", { "aria-label": "Export format", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: format, onChange: (e) => setFormat(e.target.value), children: [_jsx("option", { value: "json", children: "JSON" }), _jsx("option", { value: "mbox", children: "Mbox" })] })] }), _jsx(Button, { type: "submit", variant: "secondary", className: "!w-auto", loading: creating, disabled: creating, children: "Request export" })] }), createError && _jsx(Alert, { children: createError }), loadError && _jsx(Alert, { children: loadError }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : requests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No export requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: requests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [request.format.toUpperCase(), " \u00B7 ", new Date(request.dateCreated).toLocaleString(), request.status === "failed" && request.errorMessage && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.errorMessage] }))] }), _jsxs("span", { className: "flex items-center gap-3", children: [_jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) }), request.status === "ready" && (_jsx("a", { href: exportRequestDownloadUrl(request.uid), className: "text-primary-dark hover:underline font-medium", children: "Download" }))] })] }, request.uid))) }))] }));
108
+ return (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Export my data" }), _jsxs("p", { className: "text-xs text-text-muted mb-3", children: ["Requests a copy of ", _jsx("strong", { children: mailboxLabel }), "\u2019s messages, contacts, calendar, tasks, and notes. This runs in the background and can take a few minutes \u2014 check back here for a download link once it\u2019s ready."] }), _jsxs("form", { onSubmit: handleCreate, className: "flex items-end gap-3 mb-5", children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Format" }), _jsxs("select", { "aria-label": "Export format", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: format, onChange: (e) => setFormat(e.target.value), children: [_jsx("option", { value: "json", children: "JSON" }), _jsx("option", { value: "mbox", children: "Mbox" })] })] }), _jsx(Button, { type: "submit", variant: "secondary", className: "!w-auto", loading: creating, disabled: creating, children: "Request export" })] }), loadError && _jsx(Alert, { children: loadError }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : requests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No export requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: requests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [request.format.toUpperCase(), " \u00B7 ", new Date(request.dateCreated).toLocaleString(), request.status === "failed" && request.errorMessage && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.errorMessage] }))] }), _jsxs("span", { className: "flex items-center gap-3", children: [_jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) }), request.status === "ready" && (_jsx("a", { href: exportRequestDownloadUrl(request.uid), className: "text-primary-dark hover:underline font-medium", children: "Download" }))] })] }, request.uid))) }))] }));
109
109
  }
110
110
  function ImportSection({ mailboxUid }) {
111
111
  const [folders, setFolders] = useState([]);
@@ -115,7 +115,6 @@ function ImportSection({ mailboxUid }) {
115
115
  const [loading, setLoading] = useState(true);
116
116
  const [loadError, setLoadError] = useState(null);
117
117
  const [uploading, setUploading] = useState(false);
118
- const [uploadError, setUploadError] = useState(null);
119
118
  const fileInputRef = useRef(null);
120
119
  // Same stale-response guard as ExportSection's loadRequests() - the Upload button is enabled
121
120
  // independently of this section's own `loading`, so a fast upload-then-reload can resolve before the
@@ -155,14 +154,13 @@ function ImportSection({ mailboxUid }) {
155
154
  if (!file || !targetFolderUid) {
156
155
  return;
157
156
  }
158
- setUploadError(null);
159
157
  setUploading(true);
160
158
  uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid })
161
159
  .then(() => loadRequests())
162
- .catch((err) => setUploadError(err instanceof ApiRequestError ? err.message : "Could not upload this file."))
160
+ .catch((err) => notifyApiError(err, "Couldn't upload the file"))
163
161
  .finally(() => setUploading(false));
164
162
  }
165
- return (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Import mail" }), _jsx("p", { className: "text-xs text-text-muted mb-3", children: "Uploads an Mbox or PST archive and imports every message it contains into the folder you choose below. This runs in the background \u2014 check back here for its progress." }), folders.length > 0 && (_jsxs("label", { className: "flex flex-col gap-1.5 text-sm mb-3 max-w-xs", children: [_jsx("span", { className: "font-semibold", children: "Destination folder" }), _jsx("select", { "aria-label": "Destination folder", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: targetFolderUid, onChange: (e) => setTargetFolderUid(e.target.value), children: folders.map((folder) => (_jsx("option", { value: folder.uid, children: folder.name }, folder.uid))) })] })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto mb-3", loading: uploading, disabled: uploading || !targetFolderUid, onClick: () => fileInputRef.current?.click(), children: "Upload Mbox or PST file" }), _jsx("input", { ref: fileInputRef, type: "file", accept: ".mbox,.pst", "aria-label": "Upload mail archive", className: "hidden", onChange: handleFileChange }), foldersError && _jsx(Alert, { children: foldersError }), uploadError && _jsx(Alert, { children: uploadError }), loadError && _jsx(Alert, { children: loadError }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : requests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No import requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: requests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [request.format.toUpperCase(), " \u00B7 ", new Date(request.dateCreated).toLocaleString(), request.status === "completed" && (_jsxs("span", { className: "text-text-muted", children: [" ", "\u2014 ", request.importedCount ?? 0, " imported", request.failedCount ? `, ${request.failedCount} failed` : ""] })), request.status === "failed" && request.errorMessage && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.errorMessage] }))] }), _jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) })] }, request.uid))) }))] }));
163
+ return (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Import mail" }), _jsx("p", { className: "text-xs text-text-muted mb-3", children: "Uploads an Mbox or PST archive and imports every message it contains into the folder you choose below. This runs in the background \u2014 check back here for its progress." }), folders.length > 0 && (_jsxs("label", { className: "flex flex-col gap-1.5 text-sm mb-3 max-w-xs", children: [_jsx("span", { className: "font-semibold", children: "Destination folder" }), _jsx("select", { "aria-label": "Destination folder", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: targetFolderUid, onChange: (e) => setTargetFolderUid(e.target.value), children: folders.map((folder) => (_jsx("option", { value: folder.uid, children: folder.name }, folder.uid))) })] })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto mb-3", loading: uploading, disabled: uploading || !targetFolderUid, onClick: () => fileInputRef.current?.click(), children: "Upload Mbox or PST file" }), _jsx("input", { ref: fileInputRef, type: "file", accept: ".mbox,.pst", "aria-label": "Upload mail archive", className: "hidden", onChange: handleFileChange }), foldersError && _jsx(Alert, { children: foldersError }), loadError && _jsx(Alert, { children: loadError }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : requests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No import requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: requests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [request.format.toUpperCase(), " \u00B7 ", new Date(request.dateCreated).toLocaleString(), request.status === "completed" && (_jsxs("span", { className: "text-text-muted", children: [" ", "\u2014 ", request.importedCount ?? 0, " imported", request.failedCount ? `, ${request.failedCount} failed` : ""] })), request.status === "failed" && request.errorMessage && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.errorMessage] }))] }), _jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) })] }, request.uid))) }))] }));
166
164
  }
167
165
  function ErasureSection({ mailboxLabel }) {
168
166
  const [requests, setRequests] = useState([]);
@@ -200,3 +198,4 @@ function ErasureSection({ mailboxLabel }) {
200
198
  }
201
199
  return (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Delete my account" }), _jsxs("p", { className: "text-xs text-text-muted mb-3", children: ["Permanently and irreversibly deletes ", _jsx("strong", { children: mailboxLabel }), " and everything in it, once a compliance administrator reviews and approves the request. There is no way to cancel a request or undo the deletion once it runs."] }), loadError && _jsx(Alert, { children: loadError }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [requests.length > 0 && (_jsx("ul", { className: "flex flex-col gap-2 mb-3", children: requests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [new Date(request.dateCreated).toLocaleString(), request.status === "denied" && request.reason && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.reason] }))] }), _jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) })] }, request.uid))) })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto !border-danger !text-danger hover:!border-danger hover:!text-danger", disabled: hasPending, onClick: () => setConfirming(true), children: "Request account erasure" })] })), _jsxs(Modal, { open: confirming, onClose: closeConfirm, title: "Delete my account", children: [_jsxs("p", { className: "text-sm mb-5", children: ["This permanently deletes ", mailboxLabel, " and everything in it \u2014 messages, contacts, calendar, tasks, and notes \u2014 once approved. This cannot be undone and cannot be cancelled once submitted."] }), createError && _jsx(Alert, { children: createError }), _jsxs("div", { className: "flex gap-3 justify-end mt-5", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: creating, onClick: closeConfirm, children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", loading: creating, disabled: creating, onClick: handleConfirm, children: "Request erasure" })] })] })] }));
202
200
  }
201
+ export default routedPage("/settings/privacy", SettingsPrivacyPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SettingsReadReceiptsPageProps = Omit<SettingsShellProps, "active">;
4
- export default function SettingsReadReceiptsPage(props: SettingsReadReceiptsPageProps): React.JSX.Element;
3
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsReadReceiptsPageProps>;
4
+ export default _default;
@@ -3,13 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useState } from "react";
7
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
8
  import { updateMailbox } from "@rapidmx/react-shared/mail/mailApi.js";
9
9
  import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
10
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
10
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
12
- export default function SettingsReadReceiptsPage(props) {
11
+ import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
12
+ function SettingsReadReceiptsPage(props) {
13
13
  return (_jsx(SettingsShell, { ...props, active: "read-receipts", children: _jsx(ReadReceiptsContent, {}) }));
14
14
  }
15
15
  function ReadReceiptsContent() {
@@ -25,12 +25,10 @@ function ReadReceiptsContent() {
25
25
  const [autoSendReceiptsExternal, setAutoSendReceiptsExternal] = useState(mailbox.autoSendReceiptsExternal ?? false);
26
26
  // See auto-reply/index.tsx's identical note - later saves must carry the version the previous save returned.
27
27
  const [version, setVersion] = useState(mailbox.version);
28
- const [error, setError] = useState(null);
29
28
  const [saving, setSaving] = useState(false);
30
29
  const [saved, setSaved] = useState(false);
31
30
  async function handleSubmit(e) {
32
31
  e.preventDefault();
33
- setError(null);
34
32
  setSaved(false);
35
33
  setSaving(true);
36
34
  try {
@@ -48,11 +46,12 @@ function ReadReceiptsContent() {
48
46
  setSaved(true);
49
47
  }
50
48
  catch (err) {
51
- setError(err instanceof ApiRequestError ? err.message : "Could not save read receipt settings.");
49
+ notifyApiError(err, "Couldn't save the read receipt settings");
52
50
  }
53
51
  finally {
54
52
  setSaving(false);
55
53
  }
56
54
  }
57
- return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Read Receipts" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "Controls when this mailbox requests a delivery/read receipt on mail it sends, and how it responds to a receipt requested of it. A per-message \u201CRequest a read receipt\u201D checkbox in Compose always overrides the request-side settings below for that one message." }), error && _jsx(Alert, { children: error }), saved && !error && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsxs("fieldset", { className: "flex flex-col gap-2", children: [_jsx("legend", { className: "text-sm font-semibold mb-1", children: "Request receipts when sending" }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptInternal, onChange: (e) => setAlwaysRequestReceiptInternal(e.target.checked) }), "From internal recipients (mail to this organization)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptFederated, onChange: (e) => setAlwaysRequestReceiptFederated(e.target.checked) }), "From federated recipients (partner organizations)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptExternal, onChange: (e) => setAlwaysRequestReceiptExternal(e.target.checked) }), "From external recipients"] })] }), _jsxs("fieldset", { className: "flex flex-col gap-2", children: [_jsx("legend", { className: "text-sm font-semibold mb-1", children: "Respond automatically to requests" }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsInternal, onChange: (e) => setAutoSendReceiptsInternal(e.target.checked) }), "From internal senders"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsFederated, onChange: (e) => setAutoSendReceiptsFederated(e.target.checked) }), "From federated senders (partner organizations)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsExternal, onChange: (e) => setAutoSendReceiptsExternal(e.target.checked) }), "From external senders"] }), _jsx("p", { className: "text-xs text-text-muted", children: "Unchecked means a request from that sender waits in your Inbox for you to send or decline it explicitly." })] }), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }) }));
55
+ return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Read Receipts" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "Controls when this mailbox requests a delivery/read receipt on mail it sends, and how it responds to a receipt requested of it. A per-message \u201CRequest a read receipt\u201D checkbox in Compose always overrides the request-side settings below for that one message." }), saved && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsxs("fieldset", { className: "flex flex-col gap-2", children: [_jsx("legend", { className: "text-sm font-semibold mb-1", children: "Request receipts when sending" }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptInternal, onChange: (e) => setAlwaysRequestReceiptInternal(e.target.checked) }), "From internal recipients (mail to this organization)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptFederated, onChange: (e) => setAlwaysRequestReceiptFederated(e.target.checked) }), "From federated recipients (partner organizations)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: alwaysRequestReceiptExternal, onChange: (e) => setAlwaysRequestReceiptExternal(e.target.checked) }), "From external recipients"] })] }), _jsxs("fieldset", { className: "flex flex-col gap-2", children: [_jsx("legend", { className: "text-sm font-semibold mb-1", children: "Respond automatically to requests" }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsInternal, onChange: (e) => setAutoSendReceiptsInternal(e.target.checked) }), "From internal senders"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsFederated, onChange: (e) => setAutoSendReceiptsFederated(e.target.checked) }), "From federated senders (partner organizations)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: autoSendReceiptsExternal, onChange: (e) => setAutoSendReceiptsExternal(e.target.checked) }), "From external senders"] }), _jsx("p", { className: "text-xs text-text-muted", children: "Unchecked means a request from that sender waits in your Inbox for you to send or decline it explicitly." })] }), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }) }));
58
56
  }
57
+ export default routedPage("/settings/read-receipts", SettingsReadReceiptsPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SettingsSharingPageProps = Omit<SettingsShellProps, "active">;
4
- export default function SettingsSharingPage(props: SettingsSharingPageProps): React.JSX.Element;
3
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsSharingPageProps>;
4
+ export default _default;
@@ -3,16 +3,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useEffect, useState } from "react";
7
8
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
- import { listMailboxAccess, lookupMailboxOwnerByEmail, removeMailboxAccess, setMailboxAccess, } from "@rapidmx/react-shared/mail/mailboxAccessApi.js";
9
+ import { listMailboxAccess, removeMailboxAccess, setMailboxAccess, } from "@rapidmx/react-shared/mail/mailboxAccessApi.js";
9
10
  import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
11
+ import PrincipalPicker from "../../../shared/components/sharing/PrincipalPicker.js";
10
12
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
13
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
12
14
  import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
13
15
  import { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
16
+ import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
14
17
  const ROLE_LABELS = { viewer: "Can view", manager: "Can manage" };
15
- export default function SettingsSharingPage(props) {
18
+ function SettingsSharingPage(props) {
16
19
  return (_jsx(SettingsShell, { ...props, active: "sharing", children: _jsx(SharingContent, {}) }));
17
20
  }
18
21
  /**
@@ -29,10 +32,8 @@ function SharingContent() {
29
32
  const [members, setMembers] = useState([]);
30
33
  const [status, setStatus] = useState("loading");
31
34
  const [error, setError] = useState(null);
32
- const [emailInput, setEmailInput] = useState("");
33
- const [newRole, setNewRole] = useState("viewer");
34
- const [adding, setAdding] = useState(false);
35
- const [addError, setAddError] = useState(null);
35
+ // An entry that is not a user uid, being replaced with the user it was meant for.
36
+ const [replacing, setReplacing] = useState(null);
36
37
  const [pendingRoleChange, setPendingRoleChange] = useState(null);
37
38
  const [removeTarget, setRemoveTarget] = useState(null);
38
39
  const [removing, setRemoving] = useState(false);
@@ -60,41 +61,28 @@ function SharingContent() {
60
61
  });
61
62
  }
62
63
  useEffect(reload, [mailboxUid]);
63
- async function handleAdd(e) {
64
- e.preventDefault();
65
- const email = emailInput.trim();
66
- if (!email) {
67
- return;
68
- }
69
- setAddError(null);
70
- setAdding(true);
71
- try {
72
- const found = await lookupMailboxOwnerByEmail(email);
73
- if (!found) {
74
- setAddError("No one found with that email on this platform.");
75
- return;
64
+ // Once the person is granted, the entry they replace (if any) is removed: the string it named never matched anyone.
65
+ async function handleGranted() {
66
+ const stale = replacing;
67
+ setReplacing(null);
68
+ if (stale) {
69
+ try {
70
+ await removeMailboxAccess(mailboxUid, stale.userOrRoleId);
71
+ }
72
+ catch (err) {
73
+ notifyApiError(err, "Couldn't remove the old entry");
76
74
  }
77
- await setMailboxAccess(mailboxUid, found.userUid, newRole);
78
- setEmailInput("");
79
- setNewRole("viewer");
80
- reload();
81
- }
82
- catch (err) {
83
- setAddError(err instanceof ApiRequestError ? err.message : "Could not grant access.");
84
- }
85
- finally {
86
- setAdding(false);
87
75
  }
76
+ reload();
88
77
  }
89
78
  async function handleRoleChange(userOrRoleId, role) {
90
79
  setPendingRoleChange(userOrRoleId);
91
- setError(null);
92
80
  try {
93
81
  await setMailboxAccess(mailboxUid, userOrRoleId, role);
94
82
  reload();
95
83
  }
96
84
  catch (err) {
97
- setError(err instanceof ApiRequestError ? err.message : "Could not update this member's role.");
85
+ notifyApiError(err, "Couldn't change this member's role");
98
86
  }
99
87
  finally {
100
88
  setPendingRoleChange(null);
@@ -107,18 +95,18 @@ function SharingContent() {
107
95
  // Only reachable from the confirm modal's own "Remove" button, and `Modal` renders nothing at all
108
96
  // unless `removeTarget` is set, so `removeTarget` is always non-null here.
109
97
  setRemoving(true);
110
- setError(null);
111
98
  try {
112
99
  await removeMailboxAccess(mailboxUid, removeTarget.userOrRoleId);
113
100
  setRemoveTarget(null);
114
101
  reload();
115
102
  }
116
103
  catch (err) {
117
- setError(err instanceof ApiRequestError ? err.message : "Could not remove this member.");
104
+ notifyApiError(err, "Couldn't remove this member");
118
105
  }
119
106
  finally {
120
107
  setRemoving(false);
121
108
  }
122
109
  }
123
- return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl flex flex-col gap-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Sharing" }), _jsxs("p", { className: "text-sm text-text-muted", children: ["Manage who else can access ", mailbox?.displayName ?? "this mailbox", "\u2019s mail, calendar, contacts, and tasks. Granting access here covers all of it at once - there\u2019s nothing else to configure per data type."] })] }), status === "loading" && _jsx(SkeletonList, { count: 3 }), status === "forbidden" && (_jsx(Alert, { children: "You don\u2019t have permission to manage sharing for this mailbox." })), status === "error" && error && _jsx(Alert, { children: error }), status === "ready" && (_jsxs(_Fragment, { children: [error && _jsx(Alert, { children: error }), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "People with access" }), members.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No one else has access to this mailbox yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: members.map((member) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsx("span", { className: "truncate", children: member.userOrRoleId }), _jsxs("span", { className: "flex items-center gap-2 shrink-0", children: [_jsxs("select", { "aria-label": `Role for ${member.userOrRoleId}`, className: "text-sm border border-border rounded-sm py-1 px-2 bg-surface", value: member.role, disabled: pendingRoleChange === member.userOrRoleId, onChange: (e) => handleRoleChange(member.userOrRoleId, e.target.value), children: [member.role === "custom" && (_jsx("option", { value: "custom", disabled: true, children: "Custom access" })), Object.keys(ROLE_LABELS).map((role) => (_jsx("option", { value: role, children: ROLE_LABELS[role] }, role)))] }), _jsx(Button, { type: "button", variant: "text", className: "!w-auto text-danger", onClick: () => setRemoveTarget(member), children: "Remove" })] })] }, member.userOrRoleId))) }))] }), _jsxs("form", { onSubmit: handleAdd, className: "flex flex-col gap-2", children: [_jsx("h2", { className: "text-sm font-semibold", children: "Add someone" }), addError && _jsx(Alert, { children: addError }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "email", "aria-label": "Email address", placeholder: "Email address", className: "flex-1 text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: emailInput, onChange: (e) => setEmailInput(e.target.value), disabled: adding }), _jsx("select", { "aria-label": "Role to grant", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: newRole, onChange: (e) => setNewRole(e.target.value), disabled: adding, children: Object.keys(ROLE_LABELS).map((role) => (_jsx("option", { value: role, children: ROLE_LABELS[role] }, role))) }), _jsx(Button, { type: "submit", loading: adding, disabled: adding, className: "!w-auto", children: "Add" })] })] })] })), _jsxs(Modal, { open: !!removeTarget, onClose: closeRemoveModal, title: "Remove access", children: [_jsxs("p", { className: "text-sm mb-5", children: [removeTarget?.userOrRoleId, " will no longer be able to access ", mailbox?.displayName ?? "this mailbox", "."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: removing, onClick: closeRemoveModal, children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", loading: removing, disabled: removing, onClick: handleRemove, children: "Remove" })] })] })] }) }));
110
+ return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl flex flex-col gap-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Sharing" }), _jsxs("p", { className: "text-sm text-text-muted", children: ["Manage who else can access ", mailbox?.displayName ?? "this mailbox", "\u2019s mail, calendar, contacts, and tasks. Granting access here covers all of it at once - there\u2019s nothing else to configure per data type."] })] }), status === "loading" && _jsx(SkeletonList, { count: 3 }), status === "forbidden" && (_jsx(Alert, { children: "You don\u2019t have permission to manage sharing for this mailbox." })), status === "error" && error && _jsx(Alert, { children: error }), status === "ready" && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "People with access" }), members.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No one else has access to this mailbox yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: members.map((member) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { className: "truncate", children: [member.userOrRoleId, member.noEffect && (_jsx("span", { className: "block text-xs text-danger", children: "Not a user - this entry has no effect. Replace it with the person it was meant for, or remove it." }))] }), _jsxs("span", { className: "flex items-center gap-2 shrink-0", children: [member.noEffect && (_jsx(Button, { type: "button", variant: "text", className: "!w-auto", onClick: () => setReplacing(member), children: "Replace with a user" })), _jsxs("select", { "aria-label": `Role for ${member.userOrRoleId}`, className: "text-sm border border-border rounded-sm py-1 px-2 bg-surface", value: member.role, disabled: pendingRoleChange === member.userOrRoleId, onChange: (e) => handleRoleChange(member.userOrRoleId, e.target.value), children: [member.role === "custom" && (_jsx("option", { value: "custom", disabled: true, children: "Custom access" })), Object.keys(ROLE_LABELS).map((role) => (_jsx("option", { value: role, children: ROLE_LABELS[role] }, role)))] }), _jsx(Button, { type: "button", variant: "text", className: "!w-auto text-danger", onClick: () => setRemoveTarget(member), children: "Remove" })] })] }, member.userOrRoleId))) }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h2", { className: "text-sm font-semibold", children: replacing ? `Replace ${replacing.userOrRoleId}` : "Add someone" }), _jsx(PrincipalPicker, { mailboxUid: mailboxUid, roleLabels: ROLE_LABELS, initialPrincipal: replacing?.userOrRoleId, defaultRole: replacing?.role === "manager" ? "manager" : "viewer", placeholder: "Email address or username", onGranted: handleGranted, onCancel: replacing ? () => setReplacing(null) : undefined }, replacing?.userOrRoleId ?? "new")] })] })), _jsxs(Modal, { open: !!removeTarget, onClose: closeRemoveModal, title: "Remove access", children: [_jsxs("p", { className: "text-sm mb-5", children: [removeTarget?.userOrRoleId, " will no longer be able to access ", mailbox?.displayName ?? "this mailbox", "."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: removing, onClick: closeRemoveModal, children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", loading: removing, disabled: removing, onClick: handleRemove, children: "Remove" })] })] })] }) }));
124
111
  }
112
+ export default routedPage("/settings/sharing", SettingsSharingPage);
@@ -1,8 +1,8 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SignatureDetailPageProps = Omit<SettingsShellProps, "active"> & {
4
3
  params: {
5
4
  uid: string;
6
5
  };
7
6
  };
8
- export default function SignatureDetailPage(props: SignatureDetailPageProps): React.JSX.Element;
7
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SignatureDetailPageProps>;
8
+ export default _default;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useEffect, useState } from "react";
7
8
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
9
  import { getMailSignature, listMailSignatures, updateMailSignature } from "@rapidmx/react-shared/mail/mailSignaturesApi.js";
@@ -12,13 +13,14 @@ import RichTextEditor from "../../../shared/components/mail/compose/RichTextEdit
12
13
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
13
14
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
14
15
  import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
16
+ import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
15
17
  const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
16
18
  /** Signatures aren't backed by a draft/attachment record the way Compose's own inline images are — see
17
19
  * `settings/signatures/new/index.tsx`'s identical stub for the full reasoning. */
18
20
  async function handleUploadImage() {
19
21
  return null;
20
22
  }
21
- export default function SignatureDetailPage(props) {
23
+ function SignatureDetailPage(props) {
22
24
  return (_jsx(SettingsShell, { ...props, active: "signatures", children: _jsx(SignatureDetailContent, { uid: props.params.uid }) }));
23
25
  }
24
26
  function SignatureDetailContent({ uid }) {
@@ -77,7 +79,8 @@ function SignatureDetailContent({ uid }) {
77
79
  setSaved(true);
78
80
  }
79
81
  catch (err) {
80
- setError(err instanceof ApiRequestError ? err.message : "Could not save this signature.");
82
+ // A pop-up: `error` is only this form's own validation (and the load failure above).
83
+ notifyApiError(err, "Couldn't save the signature");
81
84
  }
82
85
  finally {
83
86
  setSaving(false);
@@ -94,3 +97,4 @@ function SignatureDetailContent({ uid }) {
94
97
  }
95
98
  return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-3xl", children: [_jsx("a", { href: `/settings/signatures?mailboxUid=${encodeURIComponent(mailboxUid)}`, className: "text-sm text-primary-dark hover:underline", children: "\u2190 All signatures" }), _jsx("h1", { className: "text-lg font-bold tracking-tight mt-1 mb-5", children: original.name }), error && _jsx(Alert, { children: error }), saved && !error && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6 flex flex-col gap-4", children: [_jsx(FormField, { label: "Name", htmlFor: "name", children: _jsx("input", { id: "name", type: "text", className: INPUT_CLASS, value: name, onChange: (e) => setName(e.target.value) }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: isDefaultForNewMessages, onChange: (e) => setIsDefaultForNewMessages(e.target.checked) }), "Use for new messages"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: isDefaultForReplyForward, onChange: (e) => setIsDefaultForReplyForward(e.target.checked) }), "Use for replies and forwards"] })] }), _jsx("div", { className: "h-64", children: _jsx(RichTextEditor, { value: contentHtml, onChange: setContentHtml, fill: true, onUploadImage: handleUploadImage }) }), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save changes" }) })] })] }) }));
96
99
  }
100
+ export default routedPage("/settings/signatures/:uid", SignatureDetailPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type SettingsSignaturesPageProps = Omit<SettingsShellProps, "active">;
4
- export default function SettingsSignaturesPage(props: SettingsSignaturesPageProps): React.JSX.Element;
3
+ declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsSignaturesPageProps>;
4
+ export default _default;
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../_routedPage.js";
6
7
  import { useEffect, useState } from "react";
7
8
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
9
  import { listMailSignatures } from "@rapidmx/react-shared/mail/mailSignaturesApi.js";
9
10
  import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
10
11
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
12
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
12
- export default function SettingsSignaturesPage(props) {
13
+ function SettingsSignaturesPage(props) {
13
14
  return (_jsx(SettingsShell, { ...props, active: "signatures", children: _jsx(SignaturesContent, {}) }));
14
15
  }
15
16
  function SignaturesContent() {
@@ -32,3 +33,4 @@ function SignaturesContent() {
32
33
  .filter(Boolean)
33
34
  .join(" · ") }))] }), _jsx("a", { href: `/settings/signatures/${encodeURIComponent(sig.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`, className: "text-primary-dark hover:underline font-medium text-sm", children: "Edit" })] }, sig.uid))) }))] }) }));
34
35
  }
36
+ export default routedPage("/settings/signatures", SettingsSignaturesPage);
@@ -1,4 +1,4 @@
1
- import React from "react";
2
1
  import { SettingsShellProps } from "../../../../shared/components/settings/layout/SettingsShell.js";
3
2
  export type NewSignaturePageProps = Omit<SettingsShellProps, "active">;
4
- export default function NewSignaturePage(props: NewSignaturePageProps): React.JSX.Element;
3
+ declare const _default: import("../../../../shared/navigation/AppRouter.js").RoutedPageComponent<NewSignaturePageProps>;
4
+ export default _default;
@@ -3,8 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
+ import { routedPage } from "../../../_routedPage.js";
7
+ import { useNavigate } from "../../../../shared/navigation/AppRouter.js";
6
8
  import { useState } from "react";
7
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
9
  import { createMailSignature, listMailSignatures } from "@rapidmx/react-shared/mail/mailSignaturesApi.js";
9
10
  import { clearPreviousDefaults } from "../signatureDefaults.js";
10
11
  import SettingsShell, { useSettingsShell } from "../../../../shared/components/settings/layout/SettingsShell.js";
@@ -12,8 +13,9 @@ import RichTextEditor from "../../../../shared/components/mail/compose/RichTextE
12
13
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
13
14
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
14
15
  import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
16
+ import { notifyApiError } from "../../../../shared/notifications/apiErrors.js";
15
17
  const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
16
- export default function NewSignaturePage(props) {
18
+ function NewSignaturePage(props) {
17
19
  return (_jsx(SettingsShell, { ...props, active: "signatures", children: _jsx(NewSignatureForm, {}) }));
18
20
  }
19
21
  /** Signatures aren't backed by a draft/attachment record the way Compose's own inline images are (see
@@ -23,6 +25,7 @@ async function handleUploadImage() {
23
25
  return null;
24
26
  }
25
27
  function NewSignatureForm() {
28
+ const navigate = useNavigate();
26
29
  const { mailboxUid } = useSettingsShell();
27
30
  const [name, setName] = useState("");
28
31
  const [contentHtml, setContentHtml] = useState("");
@@ -50,10 +53,11 @@ function NewSignatureForm() {
50
53
  isDefaultForNewMessages,
51
54
  isDefaultForReplyForward,
52
55
  });
53
- window.location.href = `/settings/signatures/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`;
56
+ navigate(`/settings/signatures/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`);
54
57
  }
55
58
  catch (err) {
56
- setError(err instanceof ApiRequestError ? err.message : "Could not create the signature.");
59
+ // A pop-up: `error` above is only the form's own validation.
60
+ notifyApiError(err, "Couldn't create the signature");
57
61
  }
58
62
  finally {
59
63
  setSaving(false);
@@ -61,3 +65,4 @@ function NewSignatureForm() {
61
65
  }
62
66
  return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-3xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-5", children: "New signature" }), error && _jsx(Alert, { children: error }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6 flex flex-col gap-4", children: [_jsx(FormField, { label: "Name", htmlFor: "name", children: _jsx("input", { id: "name", type: "text", className: INPUT_CLASS, value: name, onChange: (e) => setName(e.target.value), placeholder: "Work signature" }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: isDefaultForNewMessages, onChange: (e) => setIsDefaultForNewMessages(e.target.checked) }), "Use for new messages"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: isDefaultForReplyForward, onChange: (e) => setIsDefaultForReplyForward(e.target.checked) }), "Use for replies and forwards"] })] }), _jsx("div", { className: "h-64", children: _jsx(RichTextEditor, { value: contentHtml, onChange: setContentHtml, fill: true, onUploadImage: handleUploadImage }) }), _jsxs("div", { className: "flex gap-3", children: [_jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Create signature" }), _jsx("a", { href: `/settings/signatures?mailboxUid=${encodeURIComponent(mailboxUid)}`, children: _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", children: "Cancel" }) })] })] })] }) }));
63
67
  }
68
+ export default routedPage("/settings/signatures/new", NewSignaturePage);
@@ -1,3 +1,3 @@
1
- import React from "react";
2
1
  import { TasksShellProps } from "../../shared/components/tasks/layout/TasksShell.js";
3
- export default function TasksPage(props: TasksShellProps): React.JSX.Element;
2
+ declare const _default: import("../../shared/navigation/AppRouter.js").RoutedPageComponent<TasksShellProps>;
3
+ export default _default;