@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
@@ -2,6 +2,7 @@
2
2
  // Copyright (C) 2026 Jean-Philippe Steinmetz
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
+ import { routedPage } from "../_routedPage.js";
5
6
  import React, { useEffect, useMemo, useRef, useState } from "react";
6
7
  import { DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
7
8
  import { HiOutlineBars3 } from "react-icons/hi2";
@@ -36,8 +37,12 @@ import SplitDayView from "../../shared/components/calendar/SplitDayView.js";
36
37
  import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
37
38
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
38
39
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
40
+ import { SHORTCUTS, ShortcutDef } from "../../shared/keyboard/keymap.js";
41
+ import { useShortcut } from "../../shared/keyboard/useShortcut.js";
42
+ import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
43
+ import { notifyApiError } from "../../shared/notifications/apiErrors.js";
39
44
 
40
- export default function CalendarPage(props: CalendarShellProps) {
45
+ function CalendarPage(props: CalendarShellProps) {
41
46
  return (
42
47
  <CalendarShell {...props}>
43
48
  <CalendarContent userUid={props.userUid} />
@@ -48,6 +53,35 @@ export default function CalendarPage(props: CalendarShellProps) {
48
53
  type ViewType = "month" | "week" | "workWeek" | "day" | "split";
49
54
  const VIEW_TYPES: ViewType[] = ["month", "week", "workWeek", "day", "split"];
50
55
  const VIEW_LABELS: Record<ViewType, string> = { month: "Month", week: "Week", workWeek: "Work Week", day: "Day", split: "Split" };
56
+ /** The shortcut that switches to a view (Outlook's Ctrl+Alt+1-4); the split view has none. */
57
+ const VIEW_SHORTCUTS: Partial<Record<ViewType, ShortcutDef>> = {
58
+ day: SHORTCUTS.calendar.day,
59
+ workWeek: SHORTCUTS.calendar.workWeek,
60
+ week: SHORTCUTS.calendar.week,
61
+ month: SHORTCUTS.calendar.month,
62
+ };
63
+
64
+ /** One of the view switcher's buttons, with its shortcut in the tooltip. */
65
+ function ViewButton({ view, current, onSelect }: { view: ViewType; current: ViewType; onSelect: (view: ViewType) => void }) {
66
+ const shortcut = VIEW_SHORTCUTS[view];
67
+ const hint = useShortcutProps(VIEW_LABELS[view], shortcut ?? SHORTCUTS.calendar.day, !!shortcut);
68
+ return (
69
+ <button
70
+ type="button"
71
+ onClick={() => onSelect(view)}
72
+ {...hint}
73
+ className={[
74
+ "px-3 h-7 text-sm rounded-sm whitespace-nowrap",
75
+ current === view ? "bg-primary text-white" : "hover:bg-surface-alt",
76
+ (view === "workWeek" || view === "split") && "hidden md:inline-block",
77
+ ]
78
+ .filter(Boolean)
79
+ .join(" ")}
80
+ >
81
+ {VIEW_LABELS[view]}
82
+ </button>
83
+ );
84
+ }
51
85
 
52
86
  interface ModalState {
53
87
  occurrence: CalendarOccurrence | null;
@@ -76,7 +110,6 @@ function CalendarContent({ userUid }: { userUid?: string }) {
76
110
  const [events, setEvents] = useState<CalendarEvent[]>([]);
77
111
  const [loading, setLoading] = useState(true);
78
112
  const [error, setError] = useState<string | null>(null);
79
- const [actionError, setActionError] = useState<string | null>(null);
80
113
  const [modal, setModal] = useState<ModalState | null>(null);
81
114
  // `null` means "not yet customized by the user" — every calendar is treated as checked without
82
115
  // needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
@@ -254,6 +287,20 @@ function CalendarContent({ userUid }: { userUid?: string }) {
254
287
  setModal(null);
255
288
  }
256
289
 
290
+ // Keyboard shortcuts - the toolbar's own actions. (A dialog open - the event editor - silences them.)
291
+ useShortcut(SHORTCUTS.calendar.create, () => openNewEvent(), { enabled: !!mailboxUid && !!folderUid });
292
+ useShortcut(SHORTCUTS.calendar.today, goToday);
293
+ useShortcut(SHORTCUTS.calendar.previous, () => shiftView(-1));
294
+ useShortcut(SHORTCUTS.calendar.next, () => shiftView(1));
295
+ useShortcut(SHORTCUTS.calendar.day, () => setView("day"));
296
+ useShortcut(SHORTCUTS.calendar.workWeek, () => setView("workWeek"));
297
+ useShortcut(SHORTCUTS.calendar.week, () => setView("week"));
298
+ useShortcut(SHORTCUTS.calendar.month, () => setView("month"));
299
+ const newEventHint = useShortcutProps("New event", SHORTCUTS.calendar.create, !!mailboxUid && !!folderUid);
300
+ const previousHint = useShortcutProps("Previous", SHORTCUTS.calendar.previous);
301
+ const todayHint = useShortcutProps("Today", SHORTCUTS.calendar.today);
302
+ const nextHint = useShortcutProps("Next", SHORTCUTS.calendar.next);
303
+
257
304
  function handleSaved() {
258
305
  setModal(null);
259
306
  reload();
@@ -270,7 +317,6 @@ function CalendarContent({ userUid }: { userUid?: string }) {
270
317
  if (!action) {
271
318
  return;
272
319
  }
273
- setActionError(null);
274
320
  try {
275
321
  if (action.type === "move") {
276
322
  await moveOccurrence(action.occurrence, action.deltaMs);
@@ -279,7 +325,7 @@ function CalendarContent({ userUid }: { userUid?: string }) {
279
325
  }
280
326
  reload();
281
327
  } catch (err) {
282
- setActionError(err instanceof ApiRequestError ? err.message : "Could not update this event.");
328
+ notifyApiError(err, action.type === "move" ? "Couldn't move the event" : "Couldn't resize the event");
283
329
  }
284
330
  }
285
331
 
@@ -318,52 +364,40 @@ function CalendarContent({ userUid }: { userUid?: string }) {
318
364
  );
319
365
 
320
366
  return (
321
- <div className="flex-1 flex min-h-0">
322
- <div className="hidden md:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto">{sidebarContent}</div>
367
+ <div className="flex-1 min-w-0 flex min-h-0">
368
+ <div className="hidden lg:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto">{sidebarContent}</div>
323
369
  <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Calendars">
324
370
  <div className="flex flex-col">{sidebarContent}</div>
325
371
  </Drawer>
326
- <div className="flex-1 flex flex-col min-h-0">
327
- <div className="flex items-center gap-3 p-3 border-b border-border shrink-0">
372
+ {/* `min-w-0`: without it the column is as wide as its widest row (the toolbar, the month grid) and runs off the window instead of the row wrapping. */}
373
+ <div className="flex-1 min-w-0 flex flex-col min-h-0">
374
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-2 p-3 border-b border-border shrink-0">
328
375
  <button
329
376
  type="button"
330
- className="md:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0"
377
+ className="lg:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0"
331
378
  aria-label="Open calendars"
332
379
  onClick={() => setDrawerOpen(true)}
333
380
  >
334
381
  <HiOutlineBars3 size={20} aria-hidden="true" />
335
382
  </button>
336
- <Button type="button" onClick={() => openNewEvent()} className="!w-auto shrink-0">
383
+ <Button type="button" onClick={() => openNewEvent()} className="!w-auto shrink-0" {...newEventHint}>
337
384
  + New event
338
385
  </Button>
339
386
  <div className="flex items-center gap-1">
340
- <button type="button" onClick={() => shiftView(-1)} aria-label="Previous" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
387
+ <button type="button" onClick={() => shiftView(-1)} aria-label="Previous" {...previousHint} className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
341
388
  &lsaquo;
342
389
  </button>
343
- <button type="button" onClick={goToday} className="px-2 h-7 text-sm rounded-sm hover:bg-surface-alt">
390
+ <button type="button" onClick={goToday} {...todayHint} className="px-2 h-7 text-sm rounded-sm hover:bg-surface-alt">
344
391
  Today
345
392
  </button>
346
- <button type="button" onClick={() => shiftView(1)} aria-label="Next" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
393
+ <button type="button" onClick={() => shiftView(1)} aria-label="Next" {...nextHint} className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
347
394
  &rsaquo;
348
395
  </button>
349
396
  </div>
350
397
  <h1 className="hidden md:block text-lg font-bold tracking-tight">{title}</h1>
351
- <div className="ml-auto flex gap-1 overflow-x-auto">
398
+ <div className="ml-auto flex flex-wrap gap-1">
352
399
  {VIEW_TYPES.map((v) => (
353
- <button
354
- key={v}
355
- type="button"
356
- onClick={() => setView(v)}
357
- className={[
358
- "px-3 h-7 text-sm rounded-sm whitespace-nowrap",
359
- view === v ? "bg-primary text-white" : "hover:bg-surface-alt",
360
- (v === "workWeek" || v === "split") && "hidden md:inline-block",
361
- ]
362
- .filter(Boolean)
363
- .join(" ")}
364
- >
365
- {VIEW_LABELS[v]}
366
- </button>
400
+ <ViewButton key={v} view={v} current={view} onSelect={setView} />
367
401
  ))}
368
402
  </div>
369
403
  </div>
@@ -373,11 +407,6 @@ function CalendarContent({ userUid }: { userUid?: string }) {
373
407
  <Alert>{error}</Alert>
374
408
  </div>
375
409
  )}
376
- {actionError && (
377
- <div className="p-3">
378
- <Alert>{actionError}</Alert>
379
- </div>
380
- )}
381
410
 
382
411
  {loading ? (
383
412
  <p className="p-4 text-sm text-text-muted">Loading&hellip;</p>
@@ -436,3 +465,5 @@ function CalendarContent({ userUid }: { userUid?: string }) {
436
465
  </div>
437
466
  );
438
467
  }
468
+
469
+ export default routedPage("/calendar", CalendarPage);
@@ -1,107 +1,109 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { useEffect, useState } from "react";
6
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
7
- import { Contact, deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
8
- import ContactsShell, { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
9
- import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
10
- import ContactForm from "../../shared/components/contacts/ContactForm.js";
11
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
- import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
13
-
14
- /**
15
- * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
16
- * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
17
- * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
18
- * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
19
- */
20
- export default function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
21
- return (
22
- <ContactsShell {...props}>
23
- <ContactDetailContent uid={props.params.uid} />
24
- </ContactsShell>
25
- );
26
- }
27
-
28
- type Mode = "view" | "edit";
29
-
30
- function ContactDetailContent({ uid }: { uid: string }) {
31
- const [contact, setContact] = useState<Contact | null>(null);
32
- const [loading, setLoading] = useState(true);
33
- const [error, setError] = useState<string | null>(null);
34
- // Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
35
- // reason) rather than replacing the whole page with an error as if it had never loaded.
36
- const [deleteError, setDeleteError] = useState<string | null>(null);
37
- const [mode, setMode] = useState<Mode>("view");
38
-
39
- useEffect(() => {
40
- setLoading(true);
41
- setError(null);
42
- getContact(uid)
43
- .then(setContact)
44
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
45
- .finally(() => setLoading(false));
46
- }, [uid]);
47
-
48
- // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
49
- // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
50
- // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
51
- // never runs with a stale/absent contact — no redundant null check needed here.
52
- async function handleDelete(contact: Contact) {
53
- setDeleteError(null);
54
- try {
55
- await deleteContact(contact.uid, contact.version);
56
- // A deleted contact's pinned signing keys must stop vouching for signatures.
57
- clearPinnedSignerCache();
58
- window.location.href = "/contacts";
59
- } catch (err) {
60
- setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this contact.");
61
- }
62
- }
63
-
64
- // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
65
- function handleKeysChanged() {
66
- getContact(uid).then(setContact, () => undefined);
67
- }
68
-
69
- if (loading) {
70
- return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
71
- }
72
- if (error || !contact) {
73
- return <Alert>{error ?? "Contact not found."}</Alert>;
74
- }
75
-
76
- const backHref = "/contacts";
77
- if (mode === "edit") {
78
- return (
79
- <div className="p-6">
80
- <a href={backHref} className="text-sm text-primary-dark hover:underline block mb-4">
81
- &larr; Back to contacts
82
- </a>
83
- <ContactForm
84
- contact={contact}
85
- onSaved={(saved) => {
86
- setContact(saved);
87
- setMode("view");
88
- }}
89
- onCancel={() => setMode("view")}
90
- />
91
- </div>
92
- );
93
- }
94
-
95
- return (
96
- <div className="p-6">
97
- {deleteError && <Alert>{deleteError}</Alert>}
98
- <ContactDetailPane
99
- contact={contact}
100
- onEdit={() => setMode("edit")}
101
- onDelete={() => handleDelete(contact)}
102
- backHref={backHref}
103
- onKeysChanged={handleKeysChanged}
104
- />
105
- </div>
106
- );
107
- }
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { routedPage } from "../_routedPage.js";
6
+ import { useNavigate } from "../../shared/navigation/AppRouter.js";
7
+ import React, { useEffect, useState } from "react";
8
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
9
+ import { Contact, deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
10
+ import ContactsShell, { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
11
+ import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
12
+ import ContactForm from "../../shared/components/contacts/ContactForm.js";
13
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
+ import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
15
+ import { notifyApiError } from "../../shared/notifications/apiErrors.js";
16
+
17
+ /**
18
+ * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
19
+ * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
20
+ * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
21
+ * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
22
+ */
23
+ function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
24
+ return (
25
+ <ContactsShell {...props}>
26
+ <ContactDetailContent uid={props.params.uid} />
27
+ </ContactsShell>
28
+ );
29
+ }
30
+
31
+ type Mode = "view" | "edit";
32
+
33
+ function ContactDetailContent({ uid }: { uid: string }) {
34
+ const navigate = useNavigate();
35
+ const [contact, setContact] = useState<Contact | null>(null);
36
+ const [loading, setLoading] = useState(true);
37
+ const [error, setError] = useState<string | null>(null);
38
+ const [mode, setMode] = useState<Mode>("view");
39
+
40
+ useEffect(() => {
41
+ setLoading(true);
42
+ setError(null);
43
+ getContact(uid)
44
+ .then(setContact)
45
+ .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
46
+ .finally(() => setLoading(false));
47
+ }, [uid]);
48
+
49
+ // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
50
+ // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
51
+ // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
52
+ // never runs with a stale/absent contact — no redundant null check needed here.
53
+ async function handleDelete(contact: Contact) {
54
+ try {
55
+ await deleteContact(contact.uid, contact.version);
56
+ // A deleted contact's pinned signing keys must stop vouching for signatures.
57
+ clearPinnedSignerCache();
58
+ navigate("/contacts");
59
+ } catch (err) {
60
+ // A pop-up: a failed delete leaves the contact on screen rather than replacing the page with an error.
61
+ notifyApiError(err, "Couldn't delete the contact");
62
+ }
63
+ }
64
+
65
+ // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
66
+ function handleKeysChanged() {
67
+ getContact(uid).then(setContact, () => undefined);
68
+ }
69
+
70
+ if (loading) {
71
+ return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
72
+ }
73
+ if (error || !contact) {
74
+ return <Alert>{error ?? "Contact not found."}</Alert>;
75
+ }
76
+
77
+ const backHref = "/contacts";
78
+ if (mode === "edit") {
79
+ return (
80
+ <div>
81
+ <a href={backHref} className="text-sm text-primary-dark hover:underline block px-6 pt-6">
82
+ &larr; Back to contacts
83
+ </a>
84
+ <ContactForm
85
+ contact={contact}
86
+ onSaved={(saved) => {
87
+ setContact(saved);
88
+ setMode("view");
89
+ }}
90
+ onCancel={() => setMode("view")}
91
+ />
92
+ </div>
93
+ );
94
+ }
95
+
96
+ return (
97
+ <div className="p-6">
98
+ <ContactDetailPane
99
+ contact={contact}
100
+ onEdit={() => setMode("edit")}
101
+ onDelete={() => handleDelete(contact)}
102
+ backHref={backHref}
103
+ onKeysChanged={handleKeysChanged}
104
+ />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ export default routedPage("/contacts/:uid", ContactDetailPage);