@rapidmx/web-client 0.4.0 → 0.6.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 (320) hide show
  1. package/README.md +88 -0
  2. package/apps/admin/audit-log/index.tsx +35 -8
  3. package/apps/admin/data-requests/index.tsx +480 -485
  4. package/apps/admin/distribution-lists/[uid].tsx +19 -1
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -237
  6. package/apps/admin/escrow-scopes/new/index.tsx +8 -2
  7. package/apps/admin/index.tsx +19 -1
  8. package/apps/admin/ingest-queue/index.tsx +44 -5
  9. package/apps/admin/mailboxes/[uid].tsx +211 -184
  10. package/apps/admin/quarantine/index.tsx +46 -14
  11. package/apps/admin/transport-rules/[uid].tsx +30 -1
  12. package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
  13. package/apps/admin/transport-rules/index.tsx +24 -6
  14. package/apps/admin/transport-rules/new/index.tsx +30 -1
  15. package/apps/escrow/_layout.tsx +2 -3
  16. package/apps/escrow/audit-log/index.tsx +196 -168
  17. package/apps/escrow/matters/[uid].tsx +621 -521
  18. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
  19. package/apps/shared/components/admin/layout/AdminShell.tsx +28 -9
  20. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
  21. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  22. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
  23. package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
  24. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
  25. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
  26. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
  27. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
  28. package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
  29. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
  30. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
  31. package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
  32. package/apps/shared/components/admin/signOut.ts +79 -0
  33. package/apps/shared/components/admin/usePagedList.tsx +129 -0
  34. package/apps/shared/components/calendar/EventModal.tsx +717 -549
  35. package/apps/shared/components/calendar/MonthView.tsx +173 -139
  36. package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
  37. package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
  38. package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
  39. package/apps/shared/components/calendar/allDay.ts +124 -0
  40. package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
  41. package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
  42. package/apps/shared/components/contacts/ContactForm.tsx +377 -348
  43. package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
  44. package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
  45. package/apps/shared/components/contacts/contactKeys.ts +78 -0
  46. package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
  47. package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
  48. package/apps/shared/components/layout/AppShell.tsx +123 -33
  49. package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
  50. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
  51. package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
  52. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
  53. package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
  54. package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
  55. package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
  56. package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
  57. package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
  58. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
  59. package/apps/shared/components/mail/layout/MailShell.tsx +420 -408
  60. package/apps/shared/components/mail/pinnedSigners.ts +124 -0
  61. package/apps/shared/components/mail/verificationSeals.ts +125 -0
  62. package/apps/shared/components/mail/writableMailboxes.ts +101 -0
  63. package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
  64. package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -9
  65. package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
  66. package/apps/shared/mail/listAllPages.ts +39 -0
  67. package/apps/shared/plugins/pluginNav.ts +73 -0
  68. package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
  69. package/apps/shared/search/localIndexBuilder.ts +481 -179
  70. package/apps/shared/search/localIndexRpcClient.ts +231 -29
  71. package/apps/shared/search/localIndexSchema.ts +15 -6
  72. package/apps/shared/search/localIndexStorage.ts +98 -0
  73. package/apps/shared/search/localIndexVFS.ts +32 -1
  74. package/apps/shared/search/localIndexWorker.ts +648 -186
  75. package/apps/www/calendar/index.tsx +438 -417
  76. package/apps/www/contacts/[uid].tsx +21 -2
  77. package/apps/www/contacts/index.tsx +116 -12
  78. package/apps/www/index.tsx +1314 -1041
  79. package/apps/www/messages/[uid].tsx +18 -6
  80. package/apps/www/settings/auto-reply/index.tsx +134 -129
  81. package/apps/www/settings/encryption/index.tsx +739 -216
  82. package/apps/www/settings/filters/[uid].tsx +171 -155
  83. package/apps/www/settings/filters/new/index.tsx +8 -1
  84. package/apps/www/settings/focused-inbox/index.tsx +150 -148
  85. package/apps/www/settings/privacy/index.tsx +494 -436
  86. package/apps/www/settings/read-receipts/index.tsx +148 -125
  87. package/apps/www/settings/sharing/index.tsx +17 -14
  88. package/apps/www/tasks/index.tsx +638 -579
  89. package/dist/apps/admin/_404.d.ts +2 -0
  90. package/dist/apps/admin/_500.d.ts +2 -0
  91. package/dist/apps/admin/_layout.d.ts +8 -0
  92. package/dist/apps/admin/audit-log/index.d.ts +11 -0
  93. package/dist/apps/admin/audit-log/index.js +38 -8
  94. package/dist/apps/admin/branding/index.d.ts +3 -0
  95. package/dist/apps/admin/data-requests/index.d.ts +3 -0
  96. package/dist/apps/admin/data-requests/index.js +52 -72
  97. package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
  98. package/dist/apps/admin/distribution-lists/[uid].js +1 -1
  99. package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
  100. package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
  101. package/dist/apps/admin/domains/[uid].d.ts +7 -0
  102. package/dist/apps/admin/domains/index.d.ts +3 -0
  103. package/dist/apps/admin/domains/new/index.d.ts +3 -0
  104. package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
  105. package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
  106. package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
  107. package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
  108. package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
  109. package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
  110. package/dist/apps/admin/index.d.ts +3 -0
  111. package/dist/apps/admin/index.js +4 -1
  112. package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
  113. package/dist/apps/admin/ingest-queue/index.js +24 -6
  114. package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
  115. package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
  116. package/dist/apps/admin/mailboxes/[uid].js +20 -4
  117. package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
  118. package/dist/apps/admin/plugins/index.d.ts +3 -0
  119. package/dist/apps/admin/quarantine/index.d.ts +6 -0
  120. package/dist/apps/admin/quarantine/index.js +20 -11
  121. package/dist/apps/admin/retention-policy/index.d.ts +3 -0
  122. package/dist/apps/admin/setup/index.d.ts +3 -0
  123. package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
  124. package/dist/apps/admin/transport-rules/[uid].js +18 -2
  125. package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
  126. package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
  127. package/dist/apps/admin/transport-rules/index.d.ts +3 -0
  128. package/dist/apps/admin/transport-rules/index.js +22 -6
  129. package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
  130. package/dist/apps/admin/transport-rules/new/index.js +18 -2
  131. package/dist/apps/escrow/_layout.d.ts +10 -0
  132. package/dist/apps/escrow/_layout.js +3 -3
  133. package/dist/apps/escrow/audit-log/index.d.ts +9 -0
  134. package/dist/apps/escrow/audit-log/index.js +26 -1
  135. package/dist/apps/escrow/index.d.ts +3 -0
  136. package/dist/apps/escrow/matters/[uid].d.ts +7 -0
  137. package/dist/apps/escrow/matters/[uid].js +77 -42
  138. package/dist/apps/escrow/matters/new/index.d.ts +3 -0
  139. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
  140. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
  141. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
  142. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
  143. package/dist/apps/shared/components/admin/layout/AdminShell.js +15 -6
  144. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
  145. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
  146. package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
  147. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
  148. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  149. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
  150. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
  151. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +9 -0
  152. package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
  153. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
  154. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
  155. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +12 -0
  156. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
  157. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
  158. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
  159. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
  160. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +10 -0
  161. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
  162. package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +4 -0
  163. package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
  164. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +9 -0
  165. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
  166. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
  167. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
  168. package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
  169. package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
  170. package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
  171. package/dist/apps/shared/components/admin/signOut.js +75 -0
  172. package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
  173. package/dist/apps/shared/components/admin/usePagedList.js +99 -0
  174. package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
  175. package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
  176. package/dist/apps/shared/components/calendar/EventModal.js +142 -21
  177. package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
  178. package/dist/apps/shared/components/calendar/MonthView.js +12 -5
  179. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
  180. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
  181. package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
  182. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
  183. package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
  184. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
  185. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
  186. package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
  187. package/dist/apps/shared/components/calendar/allDay.js +111 -0
  188. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
  189. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -3
  190. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
  191. package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
  192. package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
  193. package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
  194. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
  195. package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
  196. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
  197. package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
  198. package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
  199. package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
  200. package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
  201. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
  202. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
  203. package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
  204. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
  205. package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
  206. package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
  207. package/dist/apps/shared/components/layout/AppShell.js +97 -26
  208. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
  209. package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
  210. package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
  211. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
  212. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
  213. package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
  214. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
  215. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
  216. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
  217. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
  218. package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
  219. package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -0
  220. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +23 -0
  221. package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
  222. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +93 -0
  223. package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
  224. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +58 -0
  225. package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
  226. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
  227. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
  228. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
  229. package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
  230. package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
  231. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +34 -0
  232. package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
  233. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
  234. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
  235. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +57 -0
  236. package/dist/apps/shared/components/mail/layout/MailShell.js +13 -4
  237. package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
  238. package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
  239. package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
  240. package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
  241. package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
  242. package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
  243. package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
  244. package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
  245. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
  246. package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -9
  247. package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
  248. package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
  249. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
  250. package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
  251. package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
  252. package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
  253. package/dist/apps/shared/mail/listAllPages.js +25 -0
  254. package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
  255. package/dist/apps/shared/plugins/pluginNav.js +33 -0
  256. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
  257. package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
  258. package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
  259. package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
  260. package/dist/apps/shared/search/localIndexBuilder.js +292 -37
  261. package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
  262. package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
  263. package/dist/apps/shared/search/localIndexRpcClient.js +191 -26
  264. package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
  265. package/dist/apps/shared/search/localIndexSchema.js +12 -6
  266. package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
  267. package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
  268. package/dist/apps/shared/search/localIndexStorage.js +86 -0
  269. package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
  270. package/dist/apps/shared/search/localIndexVFS.js +68 -40
  271. package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
  272. package/dist/apps/shared/search/localIndexWorker.js +519 -173
  273. package/dist/apps/shared/search/searchTier2.d.ts +30 -0
  274. package/dist/apps/www/_404.d.ts +2 -0
  275. package/dist/apps/www/_500.d.ts +2 -0
  276. package/dist/apps/www/_layout.d.ts +14 -0
  277. package/dist/apps/www/calendar/index.d.ts +3 -0
  278. package/dist/apps/www/calendar/index.js +28 -10
  279. package/dist/apps/www/contacts/[uid].d.ts +13 -0
  280. package/dist/apps/www/contacts/[uid].js +13 -2
  281. package/dist/apps/www/contacts/index.d.ts +3 -0
  282. package/dist/apps/www/contacts/index.js +80 -10
  283. package/dist/apps/www/index.d.ts +3 -0
  284. package/dist/apps/www/index.js +339 -108
  285. package/dist/apps/www/messages/[uid].d.ts +11 -0
  286. package/dist/apps/www/messages/[uid].js +18 -6
  287. package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
  288. package/dist/apps/www/settings/auto-reply/index.js +9 -4
  289. package/dist/apps/www/settings/encryption/index.d.ts +7 -0
  290. package/dist/apps/www/settings/encryption/index.js +514 -97
  291. package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
  292. package/dist/apps/www/settings/filters/[uid].js +22 -7
  293. package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
  294. package/dist/apps/www/settings/filters/index.d.ts +4 -0
  295. package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
  296. package/dist/apps/www/settings/filters/new/index.js +8 -2
  297. package/dist/apps/www/settings/focused-inbox/index.d.ts +4 -0
  298. package/dist/apps/www/settings/focused-inbox/index.js +3 -1
  299. package/dist/apps/www/settings/labels/index.d.ts +4 -0
  300. package/dist/apps/www/settings/privacy/index.d.ts +4 -0
  301. package/dist/apps/www/settings/privacy/index.js +57 -22
  302. package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
  303. package/dist/apps/www/settings/read-receipts/index.js +10 -3
  304. package/dist/apps/www/settings/sharing/index.d.ts +4 -0
  305. package/dist/apps/www/settings/sharing/index.js +7 -11
  306. package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
  307. package/dist/apps/www/settings/signatures/index.d.ts +4 -0
  308. package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
  309. package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
  310. package/dist/apps/www/tasks/index.d.ts +3 -0
  311. package/dist/apps/www/tasks/index.js +50 -10
  312. package/package.json +3 -3
  313. package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
  314. package/apps/www/settings/booking-types/[uid].tsx +0 -300
  315. package/apps/www/settings/booking-types/index.tsx +0 -98
  316. package/apps/www/settings/booking-types/new/index.tsx +0 -212
  317. package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
  318. package/dist/apps/www/settings/booking-types/[uid].js +0 -129
  319. package/dist/apps/www/settings/booking-types/index.js +0 -29
  320. package/dist/apps/www/settings/booking-types/new/index.js +0 -86
@@ -0,0 +1,87 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * Which of the caller's accessible mailboxes they can actually create things in (send from, create
7
+ * events/contacts/to-dos in). `listMailboxes()` returns view-only shares too, and `Mailbox` itself carries
8
+ * no per-caller access role, so a "viewer" delegate used to be offered mailboxes every create would 403 in.
9
+ *
10
+ * The signal: an owned mailbox (or any mailbox, for a trusted caller) is always writable; for any other,
11
+ * `getMyMailboxAccess()`'s `canCreate` - the server's own ACL evaluation of the exact action a create needs.
12
+ * One request per mailbox per page load: results are cached in a module-level map, with concurrent callers
13
+ * sharing one in-flight request. A failure (network, an older server without that route) is "unknown" and
14
+ * isn't cached: the server still enforces access on the create itself, and hiding a usable mailbox over a
15
+ * transient error would be worse than offering one that later errors.
16
+ *
17
+ * Nothing waits on these checks: pickers list every mailbox straight away and drop view-only ones as each
18
+ * answer arrives.
19
+ */
20
+ import { useEffect, useMemo, useState } from "react";
21
+ import { getMyMailboxAccess } from "@rapidmx/react-shared/mail/mailboxAccessApi.js";
22
+ const inFlight = new Map();
23
+ const settled = new Map();
24
+ /** Forgets every cached answer - for tests, and anything that changes the caller's access mid-session. */
25
+ export function clearMailboxWritabilityCache() {
26
+ inFlight.clear();
27
+ settled.clear();
28
+ }
29
+ function isImplicitlyWritable(mailbox, userUid, trusted) {
30
+ return !!trusted || (!!userUid && mailbox.ownerUserUid === userUid);
31
+ }
32
+ /** The already-known answer for `mailbox`, without making a request. */
33
+ export function peekMailboxWritability(mailbox, userUid, trusted) {
34
+ return isImplicitlyWritable(mailbox, userUid, trusted) ? true : settled.get(mailbox.uid);
35
+ }
36
+ /** Whether the caller can create items in `mailbox`. Never rejects. */
37
+ export function getMailboxWritability(mailbox, userUid, trusted) {
38
+ const known = peekMailboxWritability(mailbox, userUid, trusted);
39
+ if (known !== undefined) {
40
+ return Promise.resolve(known);
41
+ }
42
+ let request = inFlight.get(mailbox.uid);
43
+ if (!request) {
44
+ request = getMyMailboxAccess(mailbox.uid).then((access) => {
45
+ settled.set(mailbox.uid, access.canCreate);
46
+ inFlight.delete(mailbox.uid);
47
+ return access.canCreate;
48
+ }, () => {
49
+ // Not cached - a later check may succeed.
50
+ inFlight.delete(mailbox.uid);
51
+ return undefined;
52
+ });
53
+ inFlight.set(mailbox.uid, request);
54
+ }
55
+ return request;
56
+ }
57
+ /** Per-mailbox writability answers for `mailboxes`, filled in as each one arrives (already-known answers are
58
+ * present from the first render). */
59
+ export function useMailboxWritability(mailboxes, userUid, trusted) {
60
+ const [answers, setAnswers] = useState({});
61
+ useEffect(() => {
62
+ let cancelled = false;
63
+ for (const mailbox of mailboxes) {
64
+ void getMailboxWritability(mailbox, userUid, trusted).then((writable) => {
65
+ if (!cancelled) {
66
+ setAnswers((prev) => ({ ...prev, [mailbox.uid]: writable }));
67
+ }
68
+ });
69
+ }
70
+ return () => {
71
+ cancelled = true;
72
+ };
73
+ }, [mailboxes, userUid, trusted]);
74
+ return useMemo(() => {
75
+ const merged = {};
76
+ for (const mailbox of mailboxes) {
77
+ merged[mailbox.uid] = answers[mailbox.uid] ?? peekMailboxWritability(mailbox, userUid, trusted);
78
+ }
79
+ return merged;
80
+ }, [answers, mailboxes, userUid, trusted]);
81
+ }
82
+ /** `mailboxes` minus any known to be view-only, always keeping `keepUid` (the picker's current/default value,
83
+ * so the control never shows a value missing from its own options). Lists everything until told otherwise. */
84
+ export function useWritableMailboxes(mailboxes, userUid, keepUid, trusted) {
85
+ const writability = useMailboxWritability(mailboxes, userUid, trusted);
86
+ return useMemo(() => mailboxes.filter((mb) => mb.uid === keepUid || writability[mb.uid] !== false), [mailboxes, writability, keepUid]);
87
+ }
@@ -0,0 +1,70 @@
1
+ import React, { ReactNode } from "react";
2
+ /** One condition field this scope's `Conditions` shape supports — `"list"` renders an add/remove chip
3
+ * editor for a `string[]` field, `"boolean"` a plain checkbox, `"select"` a fixed-choice dropdown for a
4
+ * field with a small closed set of string values (e.g. `MailFilterConditions.importance`). */
5
+ export interface ConditionFieldDef {
6
+ key: string;
7
+ label: string;
8
+ kind: "list" | "boolean" | "select";
9
+ placeholder?: string;
10
+ /** Required when `kind` is `"select"` — the fixed set of values this field may hold, plus an
11
+ * implicit "Any" option that clears the field entirely. */
12
+ options?: {
13
+ value: string;
14
+ label: string;
15
+ }[];
16
+ }
17
+ /** One action type this scope's `Action` union supports — the per-scope registry the shared condition
18
+ * editor/chrome below is deliberately kept out of (transport-rule and mail-filter actions are
19
+ * different enough vocabularies that one component special-casing both would be worse than this). */
20
+ export interface ActionTypeDef<A> {
21
+ value: string;
22
+ label: string;
23
+ /** Builds a fresh default action of this type when picked from "Add action". */
24
+ createDefault: () => A;
25
+ /** Renders this action's own type-specific fields (e.g. header name/value). `null` for an action
26
+ * type with nothing further to configure. */
27
+ render: (action: A, onChange: (next: A) => void) => ReactNode;
28
+ }
29
+ export interface RuleBuilderValue<C extends object, A extends {
30
+ type: string;
31
+ }> {
32
+ enabled: boolean;
33
+ sequence: number;
34
+ stopProcessingRules: boolean;
35
+ conditions: C;
36
+ actions: A[];
37
+ }
38
+ export interface RuleBuilderProps<C extends object, A extends {
39
+ type: string;
40
+ }> {
41
+ value: RuleBuilderValue<C, A>;
42
+ onChange: (next: RuleBuilderValue<C, A>) => void;
43
+ conditionFields: ConditionFieldDef[];
44
+ actionTypes: ActionTypeDef<A>[];
45
+ /** Set by the page once the user has tried to save a rule `hasConditions()` rejects - turns the
46
+ * "add at least one condition" hint into an error. */
47
+ showValidation?: boolean;
48
+ }
49
+ /**
50
+ * Whether `conditions` holds at least one real condition: a list with a non-blank entry, a ticked
51
+ * boolean, or a chosen select value. A rule with none would match *every* message, so the pages that save
52
+ * a `RuleBuilder` value (mail filters, transport rules) must refuse to save unless this returns `true`.
53
+ */
54
+ export declare function hasConditions(conditions: object): boolean;
55
+ /**
56
+ * Shared condition-row editor and enabled/sequence/stop-processing chrome for a rule that matches
57
+ * conditions and runs actions in order — `TransportRule` (admin, org-wide) and `MailFilterRule`
58
+ * (mailbox-scoped) both have this same `{enabled, sequence, stopProcessingRules, conditions, actions}`
59
+ * shape. Conditions are driven by a declarative `conditionFields` list so both scopes share this one
60
+ * editor despite their `Conditions` types not being identical — `MailFilterConditions.importance`
61
+ * (`MailFilterRule`'s one condition field `TransportRuleConditions` has no equivalent of) is what forced
62
+ * this list beyond `"list"`/`"boolean"` to add a third `"select"` kind for a small fixed-choice field,
63
+ * confirming the shared editor still generalizes rather than needing a fork; actions are driven by a
64
+ * small per-scope `actionTypes` registry instead, since the two action vocabularies (reject/quarantine/
65
+ * add-header/add-recipient vs. move/copy/delete/mark-read/forward) are different enough that one
66
+ * component special-casing both would be worse than this split.
67
+ */
68
+ export default function RuleBuilder<C extends object, A extends {
69
+ type: string;
70
+ }>({ value, onChange, conditionFields, actionTypes, showValidation, }: RuleBuilderProps<C, A>): React.JSX.Element;
@@ -6,6 +6,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  import { useState } from "react";
7
7
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
8
8
  const INPUT_CLASS = "flex-1 text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
9
+ /**
10
+ * Whether `conditions` holds at least one real condition: a list with a non-blank entry, a ticked
11
+ * boolean, or a chosen select value. A rule with none would match *every* message, so the pages that save
12
+ * a `RuleBuilder` value (mail filters, transport rules) must refuse to save unless this returns `true`.
13
+ */
14
+ export function hasConditions(conditions) {
15
+ return Object.values(conditions).some((v) => {
16
+ if (Array.isArray(v))
17
+ return v.some((entry) => typeof entry !== "string" || entry.trim() !== "");
18
+ if (typeof v === "string")
19
+ return v.trim() !== "";
20
+ return v !== undefined && v !== null && v !== false;
21
+ });
22
+ }
9
23
  /**
10
24
  * Shared condition-row editor and enabled/sequence/stop-processing chrome for a rule that matches
11
25
  * conditions and runs actions in order — `TransportRule` (admin, org-wide) and `MailFilterRule`
@@ -19,7 +33,7 @@ const INPUT_CLASS = "flex-1 text-sm py-2 px-3 border border-border rounded-sm bg
19
33
  * add-header/add-recipient vs. move/copy/delete/mark-read/forward) are different enough that one
20
34
  * component special-casing both would be worse than this split.
21
35
  */
22
- export default function RuleBuilder({ value, onChange, conditionFields, actionTypes, }) {
36
+ export default function RuleBuilder({ value, onChange, conditionFields, actionTypes, showValidation = false, }) {
23
37
  const [draftText, setDraftText] = useState({});
24
38
  const [newActionType, setNewActionType] = useState(actionTypes[0]?.value ?? "");
25
39
  const conditions = value.conditions;
@@ -36,12 +50,22 @@ export default function RuleBuilder({ value, onChange, conditionFields, actionTy
36
50
  patch({ conditions: { ...conditions, [key]: [...existing, text] } });
37
51
  setDraftText({ ...draftText, [key]: "" });
38
52
  }
53
+ // Only invoked from a chip's own "Remove" button, which only renders while `conditions[key]` holds
54
+ // that entry. Removing the last entry deletes the key rather than leaving an empty list behind.
39
55
  function removeListEntry(key, entry) {
40
- const existing = conditions[key] ?? [];
41
- patch({ conditions: { ...conditions, [key]: existing.filter((e) => e !== entry) } });
56
+ const remaining = conditions[key].filter((e) => e !== entry);
57
+ const next = { ...conditions };
58
+ if (remaining.length > 0) {
59
+ next[key] = remaining;
60
+ }
61
+ else {
62
+ delete next[key];
63
+ }
64
+ patch({ conditions: next });
42
65
  }
43
66
  function toggleBoolean(key) {
44
- patch({ conditions: { ...conditions, [key]: !conditions[key] } });
67
+ // Unticking stores `undefined` (no condition), not `false`.
68
+ patch({ conditions: { ...conditions, [key]: conditions[key] ? undefined : true } });
45
69
  }
46
70
  function setSelect(key, value) {
47
71
  patch({ conditions: { ...conditions, [key]: value || undefined } });
@@ -58,14 +82,18 @@ export default function RuleBuilder({ value, onChange, conditionFields, actionTy
58
82
  function removeAction(index) {
59
83
  patch({ actions: value.actions.filter((_, i) => i !== index) });
60
84
  }
61
- return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-4", children: "Conditions" }), _jsx("div", { className: "flex flex-col gap-4", children: conditionFields.map((field) => {
85
+ return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-4", children: "Conditions" }), !hasConditions(value.conditions) && (_jsx("p", { role: showValidation ? "alert" : undefined, className: `text-sm mb-4 ${showValidation ? "text-danger" : "text-text-muted"}`, children: "Add at least one condition. A rule without conditions would apply to every message." })), _jsx("div", { className: "flex flex-col gap-4", children: conditionFields.map((field) => {
62
86
  if (field.kind === "boolean") {
63
87
  return (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: !!conditions[field.key], onChange: () => toggleBoolean(field.key) }), field.label] }, field.key));
64
88
  }
65
89
  if (field.kind === "select") {
66
90
  return (_jsxs("div", { children: [_jsx("label", { className: "text-sm font-semibold mb-1.5 block", htmlFor: `condition-${field.key}`, children: field.label }), _jsxs("select", { id: `condition-${field.key}`, className: INPUT_CLASS, value: conditions[field.key] ?? "", onChange: (e) => setSelect(field.key, e.target.value), children: [_jsx("option", { value: "", children: "Any" }), (field.options ?? []).map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })] }, field.key));
67
91
  }
68
- return (_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold mb-1.5", children: field.label }), _jsx("div", { className: "flex flex-wrap gap-2 mb-2", children: (conditions[field.key] ?? []).map((entry) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs py-1 px-2.5 rounded-pill bg-surface-alt", children: [entry, _jsx("button", { type: "button", "aria-label": `Remove ${entry}`, onClick: () => removeListEntry(field.key, entry), className: "text-text-muted hover:text-danger", children: "\u00D7" })] }, entry))) }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", "aria-label": field.label, className: INPUT_CLASS, placeholder: field.placeholder ?? "Add a value", value: draftText[field.key] ?? "", onChange: (e) => setDraftText({ ...draftText, [field.key]: e.target.value }), onKeyDown: (e) => {
92
+ return (_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold mb-1.5", children: field.label }), _jsx("div", { className: "flex flex-wrap gap-2 mb-2", children: (conditions[field.key] ?? []).map((entry) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs py-1 px-2.5 rounded-pill bg-surface-alt", children: [entry, _jsx("button", { type: "button", "aria-label": `Remove ${entry}`, onClick: () => removeListEntry(field.key, entry), className: "text-text-muted hover:text-danger", children: "\u00D7" })] }, entry))) }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", "aria-label": field.label, className: INPUT_CLASS, placeholder: field.placeholder ?? "Add a value", value: draftText[field.key] ?? "", onChange: (e) => setDraftText({ ...draftText, [field.key]: e.target.value }),
93
+ // Text typed but never added (no Add/Enter) is committed when the field
94
+ // loses focus - e.g. to the page's Save button - so it's never silently
95
+ // dropped from the saved rule.
96
+ onBlur: () => addListEntry(field.key), onKeyDown: (e) => {
69
97
  if (e.key === "Enter") {
70
98
  e.preventDefault();
71
99
  addListEntry(field.key);
@@ -0,0 +1,41 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
3
+ import { AppShellProps } from "../../layout/AppShell.js";
4
+ import { PluginNav } from "../../../plugins/pluginNav.js";
5
+ export interface SettingsSectionDef {
6
+ id: string;
7
+ href: string;
8
+ label: string;
9
+ }
10
+ /** Every settings section with its own sidebar entry. */
11
+ export declare const SETTINGS_SECTIONS: SettingsSectionDef[];
12
+ /** A `SETTINGS_SECTIONS` id, or a plugin's `settingsSections` item id (see `PluginNav`). */
13
+ export type SettingsSectionId = string;
14
+ /** `SETTINGS_SECTIONS` followed by the plugins' `settingsSections` items, core ids winning - see
15
+ * `mergePluginNavItems`. */
16
+ export declare function settingsSections(pluginNav?: PluginNav): SettingsSectionDef[];
17
+ export interface SettingsShellProps extends Omit<AppShellProps, "active"> {
18
+ /** Which entry in `SETTINGS_SECTIONS` (or plugin `settingsSections` item) is highlighted in this shell's own sidebar — distinct from
19
+ * `AppShell`'s own `active` (always hardcoded to `"settings"` below), since Settings isn't a rail
20
+ * icon (see `AppShell.tsx`'s `AppShellApp` doc comment) but still needs its own section
21
+ * highlighting. Each settings page passes its own section id explicitly, the same "gotcha" this
22
+ * plan already calls out for every shell's `active` prop. */
23
+ active: SettingsSectionId;
24
+ }
25
+ export interface SettingsShellContextValue {
26
+ /** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox) —
27
+ * every settings section so far (Automatic Replies) edits mailbox-scoped fields. */
28
+ mailboxUid?: string;
29
+ mailboxes: Mailbox[];
30
+ }
31
+ /** Reads the mailbox Settings is currently scoped to, as resolved by the enclosing `SettingsShell`. */
32
+ export declare function useSettingsShell(): SettingsShellContextValue;
33
+ /**
34
+ * The Settings area's shell — reached via a `UserMenu` entry rather than a 5th `AppShell` icon (per
35
+ * this plan's own confirmed decision), so it always passes `active="settings"` to `AppShell` regardless
36
+ * of which section is showing. Unlike `ContactsShell`/`TasksShell` (whose sidebar is just a mailbox
37
+ * switcher, since each has exactly one well-known folder), this shell's sidebar is a real list of
38
+ * settings sections (`SETTINGS_SECTIONS`) — full-page navigation between them, same as the top-level
39
+ * app rail, since this framework has no client-side router.
40
+ */
41
+ export default function SettingsShell({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }: PropsWithChildren<SettingsShellProps>): React.JSX.Element;
@@ -12,6 +12,7 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
12
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
13
13
  import AppShell from "../../layout/AppShell.js";
14
14
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
15
+ import { mergePluginNavItems } from "../../../plugins/pluginNav.js";
15
16
  /** Every settings section with its own sidebar entry. */
16
17
  export const SETTINGS_SECTIONS = [
17
18
  { id: "auto-reply", href: "/settings/auto-reply", label: "Automatic Replies" },
@@ -20,11 +21,19 @@ export const SETTINGS_SECTIONS = [
20
21
  { id: "labels", href: "/settings/labels", label: "Labels" },
21
22
  { id: "focused-inbox", href: "/settings/focused-inbox", label: "Focused Inbox" },
22
23
  { id: "read-receipts", href: "/settings/read-receipts", label: "Read Receipts" },
23
- { id: "booking-types", href: "/settings/booking-types", label: "Booking Links" },
24
24
  { id: "encryption", href: "/settings/encryption", label: "Encryption" },
25
25
  { id: "sharing", href: "/settings/sharing", label: "Sharing" },
26
26
  { id: "privacy", href: "/settings/privacy", label: "Privacy & Data" },
27
27
  ];
28
+ /** `SETTINGS_SECTIONS` followed by the plugins' `settingsSections` items, core ids winning - see
29
+ * `mergePluginNavItems`. */
30
+ export function settingsSections(pluginNav) {
31
+ return mergePluginNavItems(SETTINGS_SECTIONS, pluginNav?.settingsSections, ({ id, href, label }) => ({
32
+ id,
33
+ href,
34
+ label,
35
+ }));
36
+ }
28
37
  const SettingsShellContext = createContext({ mailboxes: [] });
29
38
  /** Reads the mailbox Settings is currently scoped to, as resolved by the enclosing `SettingsShell`. */
30
39
  export function useSettingsShell() {
@@ -38,7 +47,7 @@ export function useSettingsShell() {
38
47
  * settings sections (`SETTINGS_SECTIONS`) — full-page navigation between them, same as the top-level
39
48
  * app rail, since this framework has no client-side router.
40
49
  */
41
- export default function SettingsShell({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, children, }) {
50
+ export default function SettingsShell({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }) {
42
51
  const [status, setStatus] = useState("checking");
43
52
  const [error, setError] = useState(null);
44
53
  const [mailboxes, setMailboxes] = useState([]);
@@ -77,16 +86,17 @@ export default function SettingsShell({ active, userUid, authServerUrl, imperson
77
86
  inner = (_jsx("div", { className: "flex-1 flex items-center justify-center p-8", children: _jsx("div", { className: "w-full max-w-md", children: _jsx(Alert, { children: error }) }) }));
78
87
  }
79
88
  else if (userUid && status === "ready") {
89
+ const sections = settingsSections(pluginNav);
80
90
  // A function, not a plain JSX constant — same reasoning as ContactsShell's identical comment:
81
91
  // rendered twice (desktop `<aside>` + mobile `Drawer`), possibly simultaneously mounted, so the
82
92
  // `<select>`'s `id`/its `<label>`'s `htmlFor` need a distinct value per instance.
83
93
  const sidebarContent = (idPrefix) => (_jsxs(_Fragment, { children: [mailboxes.length > 1 && (_jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold uppercase tracking-wide text-text-muted mb-1", htmlFor: `${idPrefix}-settings-mailbox-switcher`, children: "Mailbox" }), _jsx("select", { id: `${idPrefix}-settings-mailbox-switcher`, className: "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: mailboxUid, onChange: (e) => {
84
- // `active` always names a real entry in `SETTINGS_SECTIONS` — every settings
85
- // page passes its own section id explicitly (see `SettingsShellProps.active`'s
86
- // own doc comment), so this lookup can't miss.
87
- const section = SETTINGS_SECTIONS.find((s) => s.id === active);
88
- window.location.href = `${section.href}?mailboxUid=${encodeURIComponent(e.target.value)}`;
89
- }, children: mailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid ? "" : " (shared)"] }, mb.uid))) })] })), _jsx("nav", { "aria-label": "Settings sections", className: "flex flex-col gap-0.5", children: SETTINGS_SECTIONS.map((section) => (_jsx("a", {
94
+ // Every core settings page passes its own section id (see
95
+ // `SettingsShellProps.active`), but a plugin page's section is missing when the
96
+ // server didn't send its nav item, so that falls back to the current path.
97
+ const href = sections.find((s) => s.id === active)?.href ?? window.location.pathname;
98
+ window.location.href = `${href}?mailboxUid=${encodeURIComponent(e.target.value)}`;
99
+ }, children: mailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid ? "" : " (shared)"] }, mb.uid))) })] })), _jsx("nav", { "aria-label": "Settings sections", className: "flex flex-col gap-0.5", children: sections.map((section) => (_jsx("a", {
90
100
  // `inner` (this whole branch) is only ever computed once `status === "ready"`
91
101
  // and `mailboxUid` has already resolved truthy — a falsy `mailboxUid` returns
92
102
  // `<MailboxProvisioning />` above instead (and TypeScript's own narrowing of
@@ -100,5 +110,5 @@ export default function SettingsShell({ active, userUid, authServerUrl, imperson
100
110
  ].join(" "), children: section.label }, section.id))) })] }));
101
111
  inner = (_jsxs(_Fragment, { children: [_jsx("aside", { className: "hidden md:flex w-56 shrink-0 bg-surface border-r border-border flex-col p-3 gap-3", children: sidebarContent("desktop") }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Settings", children: _jsx("div", { className: "flex flex-col gap-3", children: sidebarContent("mobile") }) }), _jsxs("main", { className: "flex-1 min-w-0 flex flex-col", children: [_jsx("button", { type: "button", className: "md:hidden m-3 self-start w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open settings menu", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx(SettingsShellContext.Provider, { value: contextValue, children: children })] })] }));
102
112
  }
103
- return (_jsx(AppShell, { active: "settings", userUid: userUid, authServerUrl: authServerUrl, impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl, trusted: trusted, children: inner }));
113
+ return (_jsx(AppShell, { active: "settings", userUid: userUid, authServerUrl: authServerUrl, impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl, trusted: trusted, pluginNav: pluginNav, children: inner }));
104
114
  }
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { Task } from "@rapidmx/react-shared/tasks/tasksApi.js";
3
+ export type TasksView = {
4
+ type: "myDay";
5
+ } | {
6
+ type: "important";
7
+ } | {
8
+ type: "planned";
9
+ } | {
10
+ type: "assignedToMe";
11
+ } | {
12
+ type: "flagged";
13
+ } | {
14
+ type: "all";
15
+ } | {
16
+ type: "list";
17
+ uid: string;
18
+ name: string;
19
+ };
20
+ export declare function tasksViewKey(view: TasksView): string;
21
+ export interface TasksSidebarProps {
22
+ mailboxUid?: string;
23
+ /** The caller's own (incomplete) tasks — used to compute every smart-filter's count. Deliberately not
24
+ * fetched again here; the parent already has this loaded for the main list. */
25
+ tasks: Task[];
26
+ userUid?: string;
27
+ active: TasksView;
28
+ onSelect: (view: TasksView) => void;
29
+ /** Bumped by the parent to force a contact-list-style re-fetch of task lists (mirrors
30
+ * `ContactsSidebar`'s identical prop) — not currently used by any caller, kept for parity/future use. */
31
+ refreshToken?: number;
32
+ }
33
+ /**
34
+ * Tasks' Outlook To-Do-style left sidebar. "My Day"/"Assigned to me" are real persisted fields
35
+ * (`Task.myDay`/`assignedTo`, from Phase 0); "Important"/"Planned" are pure smart-filters derived from
36
+ * already-loaded fields (`priority === "high"`/`dueDate != null`) with no backend concept of their own;
37
+ * "Flagged email" is a `Message` smart-filter (see `flaggedMessages.ts`), not a `Task` one at all — the
38
+ * page renders it as a distinct list, not the task table. "Tasks" is the flat, unfiltered default. Custom
39
+ * lists are real `TaskList` records, fetched here (same pattern as `ContactsSidebar`'s contact lists).
40
+ */
41
+ export default function TasksSidebar({ mailboxUid, tasks, userUid, active, onSelect, refreshToken }: TasksSidebarProps): React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ export type TasksViewMode = "list" | "grid";
3
+ export interface TasksToolbarProps {
4
+ viewMode: TasksViewMode;
5
+ onViewModeChange: (mode: TasksViewMode) => void;
6
+ selectedCount: number;
7
+ onComplete: () => void;
8
+ onDelete: () => void;
9
+ onAddToMyDay: () => void;
10
+ }
11
+ /** Tasks' toolbar: a Grid/List view-mode toggle (local, not persisted) plus bulk actions for whatever
12
+ * rows are checked (Complete, Add to My Day, Delete) — deliberately presentational, matching
13
+ * `ContactsToolbar`'s convention, since the page owns the actual task data/selection. */
14
+ export default function TasksToolbar({ viewMode, onViewModeChange, selectedCount, onComplete, onDelete, onAddToMyDay }: TasksToolbarProps): React.JSX.Element;
@@ -0,0 +1,24 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
3
+ import { AppShellProps } from "../../layout/AppShell.js";
4
+ export type TasksShellProps = Omit<AppShellProps, "active">;
5
+ export interface TasksShellContextValue {
6
+ /** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox). */
7
+ mailboxUid?: string;
8
+ /** The selected mailbox's single `tasks`-type folder. */
9
+ folderUid?: string;
10
+ mailboxes: Mailbox[];
11
+ /** The caller's own uid — exposed here (rather than only as `AppShell`'s prop) so the page content
12
+ * can compute "assigned to me" without needing it threaded through separately. */
13
+ userUid?: string;
14
+ }
15
+ /** Reads the mailbox/folder Tasks is currently showing, as resolved by the enclosing `TasksShell`. */
16
+ export declare function useTasksShell(): TasksShellContextValue;
17
+ /**
18
+ * The Tasks app's shell — structurally identical to `ContactsShell` (a mailbox has exactly one
19
+ * well-known `tasks` folder, guaranteed to exist by `BaseMailboxRoute.create()`'s eager provisioning
20
+ * in `@rapidmx/restapi`), just bound to a different folder type. Kept as its own small component
21
+ * (rather than a single generic "SingleFolderShell" parameterized by type) so each app's sidebar can
22
+ * grow its own app-specific controls later without threading extra props through a shared one.
23
+ */
24
+ export default function TasksShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }: PropsWithChildren<TasksShellProps>): React.JSX.Element;
@@ -24,7 +24,7 @@ export function useTasksShell() {
24
24
  * (rather than a single generic "SingleFolderShell" parameterized by type) so each app's sidebar can
25
25
  * grow its own app-specific controls later without threading extra props through a shared one.
26
26
  */
27
- export default function TasksShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, children, }) {
27
+ export default function TasksShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }) {
28
28
  const [status, setStatus] = useState("checking");
29
29
  const [error, setError] = useState(null);
30
30
  const [mailboxes, setMailboxes] = useState([]);
@@ -88,5 +88,5 @@ export default function TasksShell({ userUid, authServerUrl, impersonating, impe
88
88
  }, children: mailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid ? "" : " (shared)"] }, mb.uid))) })] })), folderError && _jsx(Alert, { children: folderError })] }));
89
89
  inner = (_jsxs(_Fragment, { children: [_jsx("aside", { className: "hidden md:flex w-56 shrink-0 bg-surface border-r border-border flex-col p-3 gap-3", children: sidebarContent("desktop") }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Mailbox", children: _jsx("div", { className: "flex flex-col gap-3", children: sidebarContent("mobile") }) }), _jsxs("main", { className: "flex-1 min-w-0 flex flex-col", children: [(mailboxes.length > 1 || folderError) && (_jsx("button", { type: "button", className: "md:hidden m-3 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open mailbox switcher", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) })), _jsx(TasksShellContext.Provider, { value: contextValue, children: children })] })] }));
90
90
  }
91
- return (_jsx(AppShell, { active: "tasks", userUid: userUid, authServerUrl: authServerUrl, impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl, trusted: trusted, children: inner }));
91
+ return (_jsx(AppShell, { active: "tasks", userUid: userUid, authServerUrl: authServerUrl, impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl, trusted: trusted, pluginNav: pluginNav, children: inner }));
92
92
  }
@@ -0,0 +1,5 @@
1
+ import { FolderType } from "@rapidmx/react-shared/mail/mailApi.js";
2
+ /** The uid of a mailbox's well-known folder of `type` (e.g. its Contacts or Tasks folder), for creating an
3
+ * item in a mailbox other than the one an app currently has loaded. Resolves `undefined` if the mailbox has
4
+ * no such folder; rejects if the folder list can't be fetched. */
5
+ export declare function findWellKnownFolderUid(mailboxUid: string, type: FolderType): Promise<string | undefined>;
@@ -0,0 +1,17 @@
1
+ /** Page size the list pages (Contacts, Tasks) request per call - restapi caps `limit` at 500. */
2
+ export declare const LIST_PAGE_SIZE = 500;
3
+ /** Safety cap on pages fetched - stops a server that ignores `page` (and keeps returning a full first
4
+ * page) from looping forever. 40 pages x 500 = 20,000 items. */
5
+ export declare const MAX_LIST_PAGES = 40;
6
+ export interface ListAllPagesResult<T> {
7
+ items: T[];
8
+ /** `true` when fetching stopped at `maxPages` with the last page still full - there may be more items
9
+ * the caller isn't showing, so it should say so rather than presenting `items` as the whole list. */
10
+ truncated: boolean;
11
+ }
12
+ /**
13
+ * Fetches every page of a zero-based `limit`/`page` list endpoint, stopping at the first short page
14
+ * (fewer than `pageSize` items) - so a folder with more items than one page holds is shown in full
15
+ * rather than silently cut off at the first page. Stops at `maxPages` regardless, reporting `truncated`.
16
+ */
17
+ export declare function listAllPages<T>(fetchPage: (page: number) => Promise<T[]>, pageSize?: number, maxPages?: number): Promise<ListAllPagesResult<T>>;
@@ -0,0 +1,25 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /** Page size the list pages (Contacts, Tasks) request per call - restapi caps `limit` at 500. */
6
+ export const LIST_PAGE_SIZE = 500;
7
+ /** Safety cap on pages fetched - stops a server that ignores `page` (and keeps returning a full first
8
+ * page) from looping forever. 40 pages x 500 = 20,000 items. */
9
+ export const MAX_LIST_PAGES = 40;
10
+ /**
11
+ * Fetches every page of a zero-based `limit`/`page` list endpoint, stopping at the first short page
12
+ * (fewer than `pageSize` items) - so a folder with more items than one page holds is shown in full
13
+ * rather than silently cut off at the first page. Stops at `maxPages` regardless, reporting `truncated`.
14
+ */
15
+ export async function listAllPages(fetchPage, pageSize = LIST_PAGE_SIZE, maxPages = MAX_LIST_PAGES) {
16
+ const items = [];
17
+ for (let page = 0; page < maxPages; page++) {
18
+ const batch = await fetchPage(page);
19
+ items.push(...batch);
20
+ if (batch.length < pageSize) {
21
+ return { items, truncated: false };
22
+ }
23
+ }
24
+ return { items, truncated: true };
25
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * One navigation entry a plugin contributes through its manifest's `ui` field. Structurally identical to
3
+ * `@rapidmx/restapi`'s `PluginUiNavItem`, defined locally so this package doesn't depend on restapi.
4
+ */
5
+ export interface PluginUiNavItem {
6
+ /** Unique within its list. A plugin page passes this as its shell's `active` to highlight the entry. */
7
+ id: string;
8
+ label: string;
9
+ /** A same-origin path under the host's prefix (`/settings/...`, `/admin/...`, `/...`). */
10
+ href: string;
11
+ }
12
+ /**
13
+ * The navigation every enabled plugin with a successfully built UI contributes, supplied by the server's
14
+ * `WwwRoute`/`AdminConsoleRoute` `fetchProps` as the `pluginNav` page prop.
15
+ */
16
+ export interface PluginNav {
17
+ /** Appended to `SettingsShell`'s section list. */
18
+ settingsSections?: PluginUiNavItem[];
19
+ /** Appended to `AdminShell`'s icon rail and mobile tab bar. */
20
+ adminNav?: PluginUiNavItem[];
21
+ /** Appended to `AppShell`'s app rail and mobile tab bar. */
22
+ appRail?: PluginUiNavItem[];
23
+ }
24
+ /** The page prop every www and admin page receives - extended by `AppShellProps` and `AdminShellProps`, so
25
+ * a page that spreads its props onto its shell passes `pluginNav` through with no code of its own. */
26
+ export interface PluginNavProps {
27
+ pluginNav?: PluginNav;
28
+ }
29
+ /** A same-origin absolute path: starts with `/`, but not `//` or `/\` (both of which browsers treat as
30
+ * protocol-relative URLs to another host). */
31
+ export declare function isSafePluginHref(href: string): boolean;
32
+ /**
33
+ * Appends a plugin's nav items after the core ones. Skips any item that isn't well-formed, whose `href`
34
+ * isn't a same-origin path (see `isSafePluginHref`), or whose id is already taken - by a core item or
35
+ * `reservedIds` (core ids always win) or by an earlier plugin item.
36
+ */
37
+ export declare function mergePluginNavItems<T extends {
38
+ id: string;
39
+ }>(coreItems: T[], pluginItems: PluginUiNavItem[] | undefined, toItem: (item: PluginUiNavItem) => T, reservedIds?: string[]): T[];
@@ -0,0 +1,33 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /** A same-origin absolute path: starts with `/`, but not `//` or `/\` (both of which browsers treat as
6
+ * protocol-relative URLs to another host). */
7
+ export function isSafePluginHref(href) {
8
+ return /^\/(?![/\\])/.test(href);
9
+ }
10
+ /**
11
+ * Appends a plugin's nav items after the core ones. Skips any item that isn't well-formed, whose `href`
12
+ * isn't a same-origin path (see `isSafePluginHref`), or whose id is already taken - by a core item or
13
+ * `reservedIds` (core ids always win) or by an earlier plugin item.
14
+ */
15
+ export function mergePluginNavItems(coreItems, pluginItems, toItem, reservedIds = []) {
16
+ if (!pluginItems?.length) {
17
+ return coreItems;
18
+ }
19
+ const taken = new Set([...coreItems.map((item) => item.id), ...reservedIds]);
20
+ const merged = [...coreItems];
21
+ for (const item of pluginItems) {
22
+ if (typeof item?.id !== "string" ||
23
+ typeof item.label !== "string" ||
24
+ typeof item.href !== "string" ||
25
+ !isSafePluginHref(item.href) ||
26
+ taken.has(item.id)) {
27
+ continue;
28
+ }
29
+ taken.add(item.id);
30
+ merged.push(toItem(item));
31
+ }
32
+ return merged;
33
+ }
@@ -0,0 +1,13 @@
1
+ import type { PublicKey } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
2
+ import type { Folder } from "@rapidmx/react-shared/mail/mailApi.js";
3
+ export declare const POLL_INTERVAL_MS = 5000;
4
+ export interface LocalIndexLifecycleProps {
5
+ mailboxUid?: string;
6
+ mailboxKeys?: PublicKey[];
7
+ folders: Folder[];
8
+ /** Every mailbox the signed-in user can currently access. Once known, indexes on this device for any
9
+ * other mailbox (e.g. left behind by a different user who never signed out) are removed. */
10
+ accessibleMailboxUids?: string[];
11
+ }
12
+ /** Renders nothing - pure side-effect component, mounted by `MailShell.tsx`. */
13
+ export default function LocalIndexLifecycle({ mailboxUid, folders, accessibleMailboxUids }: LocalIndexLifecycleProps): null;