@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
@@ -11,6 +11,7 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
11
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
12
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
13
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
14
+ import { mergePluginNavItems, PluginNav } from "../../../plugins/pluginNav.js";
14
15
 
15
16
  export interface SettingsSectionDef {
16
17
  id: string;
@@ -26,16 +27,26 @@ export const SETTINGS_SECTIONS: SettingsSectionDef[] = [
26
27
  { id: "labels", href: "/settings/labels", label: "Labels" },
27
28
  { id: "focused-inbox", href: "/settings/focused-inbox", label: "Focused Inbox" },
28
29
  { id: "read-receipts", href: "/settings/read-receipts", label: "Read Receipts" },
29
- { id: "booking-types", href: "/settings/booking-types", label: "Booking Links" },
30
30
  { id: "encryption", href: "/settings/encryption", label: "Encryption" },
31
31
  { id: "sharing", href: "/settings/sharing", label: "Sharing" },
32
32
  { id: "privacy", href: "/settings/privacy", label: "Privacy & Data" },
33
33
  ];
34
34
 
35
- export type SettingsSectionId = (typeof SETTINGS_SECTIONS)[number]["id"];
35
+ /** A `SETTINGS_SECTIONS` id, or a plugin's `settingsSections` item id (see `PluginNav`). */
36
+ export type SettingsSectionId = string;
37
+
38
+ /** `SETTINGS_SECTIONS` followed by the plugins' `settingsSections` items, core ids winning - see
39
+ * `mergePluginNavItems`. */
40
+ export function settingsSections(pluginNav?: PluginNav): SettingsSectionDef[] {
41
+ return mergePluginNavItems<SettingsSectionDef>(SETTINGS_SECTIONS, pluginNav?.settingsSections, ({ id, href, label }) => ({
42
+ id,
43
+ href,
44
+ label,
45
+ }));
46
+ }
36
47
 
37
48
  export interface SettingsShellProps extends Omit<AppShellProps, "active"> {
38
- /** Which entry in `SETTINGS_SECTIONS` is highlighted in this shell's own sidebar — distinct from
49
+ /** Which entry in `SETTINGS_SECTIONS` (or plugin `settingsSections` item) is highlighted in this shell's own sidebar — distinct from
39
50
  * `AppShell`'s own `active` (always hardcoded to `"settings"` below), since Settings isn't a rail
40
51
  * icon (see `AppShell.tsx`'s `AppShellApp` doc comment) but still needs its own section
41
52
  * highlighting. Each settings page passes its own section id explicitly, the same "gotcha" this
@@ -74,6 +85,7 @@ export default function SettingsShell({
74
85
  impersonating,
75
86
  impersonationBaseUrl,
76
87
  trusted,
88
+ pluginNav,
77
89
  children,
78
90
  }: PropsWithChildren<SettingsShellProps>) {
79
91
  const [status, setStatus] = useState<Status>("checking");
@@ -130,6 +142,7 @@ export default function SettingsShell({
130
142
  </div>
131
143
  );
132
144
  } else if (userUid && status === "ready") {
145
+ const sections = settingsSections(pluginNav);
133
146
  // A function, not a plain JSX constant — same reasoning as ContactsShell's identical comment:
134
147
  // rendered twice (desktop `<aside>` + mobile `Drawer`), possibly simultaneously mounted, so the
135
148
  // `<select>`'s `id`/its `<label>`'s `htmlFor` need a distinct value per instance.
@@ -148,11 +161,11 @@ export default function SettingsShell({
148
161
  className="w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface"
149
162
  value={mailboxUid}
150
163
  onChange={(e) => {
151
- // `active` always names a real entry in `SETTINGS_SECTIONS` — every settings
152
- // page passes its own section id explicitly (see `SettingsShellProps.active`'s
153
- // own doc comment), so this lookup can't miss.
154
- const section = SETTINGS_SECTIONS.find((s) => s.id === active)!;
155
- window.location.href = `${section.href}?mailboxUid=${encodeURIComponent(e.target.value)}`;
164
+ // Every core settings page passes its own section id (see
165
+ // `SettingsShellProps.active`), but a plugin page's section is missing when the
166
+ // server didn't send its nav item, so that falls back to the current path.
167
+ const href = sections.find((s) => s.id === active)?.href ?? window.location.pathname;
168
+ window.location.href = `${href}?mailboxUid=${encodeURIComponent(e.target.value)}`;
156
169
  }}
157
170
  >
158
171
  {mailboxes.map((mb) => (
@@ -165,7 +178,7 @@ export default function SettingsShell({
165
178
  </div>
166
179
  )}
167
180
  <nav aria-label="Settings sections" className="flex flex-col gap-0.5">
168
- {SETTINGS_SECTIONS.map((section) => (
181
+ {sections.map((section) => (
169
182
  <a
170
183
  key={section.id}
171
184
  // `inner` (this whole branch) is only ever computed once `status === "ready"`
@@ -220,6 +233,7 @@ export default function SettingsShell({
220
233
  impersonating={impersonating}
221
234
  impersonationBaseUrl={impersonationBaseUrl}
222
235
  trusted={trusted}
236
+ pluginNav={pluginNav}
223
237
  >
224
238
  {inner}
225
239
  </AppShell>
@@ -1,199 +1,201 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useMemo, useState } from "react";
6
- import { HiOutlineBars3 } from "react-icons/hi2";
7
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
- import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
9
- import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
10
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
- import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
- import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
- import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
14
-
15
- export type TasksShellProps = Omit<AppShellProps, "active">;
16
-
17
- export interface TasksShellContextValue {
18
- /** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox). */
19
- mailboxUid?: string;
20
- /** The selected mailbox's single `tasks`-type folder. */
21
- folderUid?: string;
22
- mailboxes: Mailbox[];
23
- /** The caller's own uid — exposed here (rather than only as `AppShell`'s prop) so the page content
24
- * can compute "assigned to me" without needing it threaded through separately. */
25
- userUid?: string;
26
- }
27
-
28
- const TasksShellContext = createContext<TasksShellContextValue>({ mailboxes: [] });
29
-
30
- /** Reads the mailbox/folder Tasks is currently showing, as resolved by the enclosing `TasksShell`. */
31
- export function useTasksShell(): TasksShellContextValue {
32
- return useContext(TasksShellContext);
33
- }
34
-
35
- type Status = "checking" | "error" | "ready";
36
-
37
- /**
38
- * The Tasks app's shell — structurally identical to `ContactsShell` (a mailbox has exactly one
39
- * well-known `tasks` folder, guaranteed to exist by `BaseMailboxRoute.create()`'s eager provisioning
40
- * in `@rapidmx/restapi`), just bound to a different folder type. Kept as its own small component
41
- * (rather than a single generic "SingleFolderShell" parameterized by type) so each app's sidebar can
42
- * grow its own app-specific controls later without threading extra props through a shared one.
43
- */
44
- export default function TasksShell({
45
- userUid,
46
- authServerUrl,
47
- impersonating,
48
- impersonationBaseUrl,
49
- trusted,
50
- children,
51
- }: PropsWithChildren<TasksShellProps>) {
52
- const [status, setStatus] = useState<Status>("checking");
53
- const [error, setError] = useState<string | null>(null);
54
- const [mailboxes, setMailboxes] = useState<Mailbox[]>([]);
55
- const [folders, setFolders] = useState<Folder[]>([]);
56
- const [folderError, setFolderError] = useState<string | null>(null);
57
- const [requestedMailboxUid, setRequestedMailboxUid] = useState<string | null>(null);
58
- const [drawerOpen, setDrawerOpen] = useState(false);
59
-
60
- useEffect(() => {
61
- setRequestedMailboxUid(new URLSearchParams(window.location.search).get("mailboxUid"));
62
- }, []);
63
-
64
- useEffect(() => {
65
- if (!userUid) {
66
- return;
67
- }
68
- listMailboxes({ limit: 100 })
69
- .then((result) => {
70
- setMailboxes(result);
71
- setStatus("ready");
72
- })
73
- .catch((err) => {
74
- setError(err instanceof ApiRequestError ? err.message : "Could not load your mailboxes.");
75
- setStatus("error");
76
- });
77
- }, [userUid]);
78
-
79
- const mailboxUid: string | undefined =
80
- (requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined) ??
81
- mailboxes[0]?.uid;
82
-
83
- useEffect(() => {
84
- if (!mailboxUid) {
85
- setFolders([]);
86
- return;
87
- }
88
- setFolderError(null);
89
- listFolders(mailboxUid)
90
- .then(setFolders)
91
- .catch((err) => setFolderError(err instanceof ApiRequestError ? err.message : "Could not load this mailbox's tasks folder."));
92
- }, [mailboxUid]);
93
-
94
- const folderUid: string | undefined = folders.find((f) => f.type === "tasks")?.uid;
95
-
96
- const contextValue = useMemo<TasksShellContextValue>(
97
- () => ({ mailboxUid, folderUid, mailboxes, userUid }),
98
- [mailboxUid, folderUid, mailboxes, userUid],
99
- );
100
-
101
- // A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else
102
- // for a mailbox-less caller to do here yet, so the icon rail/header don't render at all.
103
- if (userUid && status === "ready" && !mailboxUid) {
104
- return <MailboxProvisioning />;
105
- }
106
-
107
- let inner: ReactNode = null;
108
- if (userUid && status === "checking") {
109
- // Renders immediately (no network round trip needed) so switching into Tasks never shows a
110
- // blank pane while `listMailboxes()` is in flight — the real sidebar (My Day/Important/Planned/
111
- // lists) lives in `apps/www/tasks/index.tsx`'s own content, so this mimics its rough shape rather
112
- // than the (minimal, mailbox-switcher-only) shape of this shell's own sidebar.
113
- inner = (
114
- <aside className="w-56 shrink-0 bg-surface border-r border-border flex flex-col p-3 gap-4">
115
- <Skeleton height="h-9" />
116
- <SkeletonList count={5} />
117
- </aside>
118
- );
119
- } else if (userUid && status === "error") {
120
- inner = (
121
- <div className="flex-1 flex items-center justify-center p-8">
122
- <div className="w-full max-w-md">
123
- <Alert>{error}</Alert>
124
- </div>
125
- </div>
126
- );
127
- } else if (userUid && status === "ready") {
128
- // A function, not a plain JSX constant — see MailShell/ContactsShell/CalendarShell's identical
129
- // comment: it's rendered twice (desktop `<aside>` + mobile `Drawer`), possibly simultaneously
130
- // mounted, so the `<select>`'s `id`/its `<label>`'s `htmlFor` need a distinct value per instance.
131
- const sidebarContent = (idPrefix: string) => (
132
- <>
133
- {mailboxes.length > 1 && (
134
- <div>
135
- <label
136
- className="block text-xs font-bold uppercase tracking-wide text-text-muted mb-1"
137
- htmlFor={`${idPrefix}-tasks-mailbox-switcher`}
138
- >
139
- Mailbox
140
- </label>
141
- <select
142
- id={`${idPrefix}-tasks-mailbox-switcher`}
143
- className="w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface"
144
- value={mailboxUid}
145
- onChange={(e) => {
146
- window.location.href = `/tasks?mailboxUid=${encodeURIComponent(e.target.value)}`;
147
- }}
148
- >
149
- {mailboxes.map((mb) => (
150
- <option key={mb.uid} value={mb.uid}>
151
- {mb.displayName}
152
- {mb.ownerUserUid ? "" : " (shared)"}
153
- </option>
154
- ))}
155
- </select>
156
- </div>
157
- )}
158
- {folderError && <Alert>{folderError}</Alert>}
159
- </>
160
- );
161
-
162
- inner = (
163
- <>
164
- <aside className="hidden md:flex w-56 shrink-0 bg-surface border-r border-border flex-col p-3 gap-3">
165
- {sidebarContent("desktop")}
166
- </aside>
167
- <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Mailbox">
168
- <div className="flex flex-col gap-3">{sidebarContent("mobile")}</div>
169
- </Drawer>
170
- <main className="flex-1 min-w-0 flex flex-col">
171
- {(mailboxes.length > 1 || folderError) && (
172
- <button
173
- type="button"
174
- 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"
175
- aria-label="Open mailbox switcher"
176
- onClick={() => setDrawerOpen(true)}
177
- >
178
- <HiOutlineBars3 size={20} aria-hidden="true" />
179
- </button>
180
- )}
181
- <TasksShellContext.Provider value={contextValue}>{children}</TasksShellContext.Provider>
182
- </main>
183
- </>
184
- );
185
- }
186
-
187
- return (
188
- <AppShell
189
- active="tasks"
190
- userUid={userUid}
191
- authServerUrl={authServerUrl}
192
- impersonating={impersonating}
193
- impersonationBaseUrl={impersonationBaseUrl}
194
- trusted={trusted}
195
- >
196
- {inner}
197
- </AppShell>
198
- );
199
- }
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useMemo, useState } from "react";
6
+ import { HiOutlineBars3 } from "react-icons/hi2";
7
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
+ import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
9
+ import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
10
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
+ import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
+ import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
+ import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
14
+
15
+ export type TasksShellProps = Omit<AppShellProps, "active">;
16
+
17
+ export interface TasksShellContextValue {
18
+ /** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox). */
19
+ mailboxUid?: string;
20
+ /** The selected mailbox's single `tasks`-type folder. */
21
+ folderUid?: string;
22
+ mailboxes: Mailbox[];
23
+ /** The caller's own uid — exposed here (rather than only as `AppShell`'s prop) so the page content
24
+ * can compute "assigned to me" without needing it threaded through separately. */
25
+ userUid?: string;
26
+ }
27
+
28
+ const TasksShellContext = createContext<TasksShellContextValue>({ mailboxes: [] });
29
+
30
+ /** Reads the mailbox/folder Tasks is currently showing, as resolved by the enclosing `TasksShell`. */
31
+ export function useTasksShell(): TasksShellContextValue {
32
+ return useContext(TasksShellContext);
33
+ }
34
+
35
+ type Status = "checking" | "error" | "ready";
36
+
37
+ /**
38
+ * The Tasks app's shell — structurally identical to `ContactsShell` (a mailbox has exactly one
39
+ * well-known `tasks` folder, guaranteed to exist by `BaseMailboxRoute.create()`'s eager provisioning
40
+ * in `@rapidmx/restapi`), just bound to a different folder type. Kept as its own small component
41
+ * (rather than a single generic "SingleFolderShell" parameterized by type) so each app's sidebar can
42
+ * grow its own app-specific controls later without threading extra props through a shared one.
43
+ */
44
+ export default function TasksShell({
45
+ userUid,
46
+ authServerUrl,
47
+ impersonating,
48
+ impersonationBaseUrl,
49
+ trusted,
50
+ pluginNav,
51
+ children,
52
+ }: PropsWithChildren<TasksShellProps>) {
53
+ const [status, setStatus] = useState<Status>("checking");
54
+ const [error, setError] = useState<string | null>(null);
55
+ const [mailboxes, setMailboxes] = useState<Mailbox[]>([]);
56
+ const [folders, setFolders] = useState<Folder[]>([]);
57
+ const [folderError, setFolderError] = useState<string | null>(null);
58
+ const [requestedMailboxUid, setRequestedMailboxUid] = useState<string | null>(null);
59
+ const [drawerOpen, setDrawerOpen] = useState(false);
60
+
61
+ useEffect(() => {
62
+ setRequestedMailboxUid(new URLSearchParams(window.location.search).get("mailboxUid"));
63
+ }, []);
64
+
65
+ useEffect(() => {
66
+ if (!userUid) {
67
+ return;
68
+ }
69
+ listMailboxes({ limit: 100 })
70
+ .then((result) => {
71
+ setMailboxes(result);
72
+ setStatus("ready");
73
+ })
74
+ .catch((err) => {
75
+ setError(err instanceof ApiRequestError ? err.message : "Could not load your mailboxes.");
76
+ setStatus("error");
77
+ });
78
+ }, [userUid]);
79
+
80
+ const mailboxUid: string | undefined =
81
+ (requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined) ??
82
+ mailboxes[0]?.uid;
83
+
84
+ useEffect(() => {
85
+ if (!mailboxUid) {
86
+ setFolders([]);
87
+ return;
88
+ }
89
+ setFolderError(null);
90
+ listFolders(mailboxUid)
91
+ .then(setFolders)
92
+ .catch((err) => setFolderError(err instanceof ApiRequestError ? err.message : "Could not load this mailbox's tasks folder."));
93
+ }, [mailboxUid]);
94
+
95
+ const folderUid: string | undefined = folders.find((f) => f.type === "tasks")?.uid;
96
+
97
+ const contextValue = useMemo<TasksShellContextValue>(
98
+ () => ({ mailboxUid, folderUid, mailboxes, userUid }),
99
+ [mailboxUid, folderUid, mailboxes, userUid],
100
+ );
101
+
102
+ // A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else
103
+ // for a mailbox-less caller to do here yet, so the icon rail/header don't render at all.
104
+ if (userUid && status === "ready" && !mailboxUid) {
105
+ return <MailboxProvisioning />;
106
+ }
107
+
108
+ let inner: ReactNode = null;
109
+ if (userUid && status === "checking") {
110
+ // Renders immediately (no network round trip needed) so switching into Tasks never shows a
111
+ // blank pane while `listMailboxes()` is in flight — the real sidebar (My Day/Important/Planned/
112
+ // lists) lives in `apps/www/tasks/index.tsx`'s own content, so this mimics its rough shape rather
113
+ // than the (minimal, mailbox-switcher-only) shape of this shell's own sidebar.
114
+ inner = (
115
+ <aside className="w-56 shrink-0 bg-surface border-r border-border flex flex-col p-3 gap-4">
116
+ <Skeleton height="h-9" />
117
+ <SkeletonList count={5} />
118
+ </aside>
119
+ );
120
+ } else if (userUid && status === "error") {
121
+ inner = (
122
+ <div className="flex-1 flex items-center justify-center p-8">
123
+ <div className="w-full max-w-md">
124
+ <Alert>{error}</Alert>
125
+ </div>
126
+ </div>
127
+ );
128
+ } else if (userUid && status === "ready") {
129
+ // A function, not a plain JSX constant — see MailShell/ContactsShell/CalendarShell's identical
130
+ // comment: it's rendered twice (desktop `<aside>` + mobile `Drawer`), possibly simultaneously
131
+ // mounted, so the `<select>`'s `id`/its `<label>`'s `htmlFor` need a distinct value per instance.
132
+ const sidebarContent = (idPrefix: string) => (
133
+ <>
134
+ {mailboxes.length > 1 && (
135
+ <div>
136
+ <label
137
+ className="block text-xs font-bold uppercase tracking-wide text-text-muted mb-1"
138
+ htmlFor={`${idPrefix}-tasks-mailbox-switcher`}
139
+ >
140
+ Mailbox
141
+ </label>
142
+ <select
143
+ id={`${idPrefix}-tasks-mailbox-switcher`}
144
+ className="w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface"
145
+ value={mailboxUid}
146
+ onChange={(e) => {
147
+ window.location.href = `/tasks?mailboxUid=${encodeURIComponent(e.target.value)}`;
148
+ }}
149
+ >
150
+ {mailboxes.map((mb) => (
151
+ <option key={mb.uid} value={mb.uid}>
152
+ {mb.displayName}
153
+ {mb.ownerUserUid ? "" : " (shared)"}
154
+ </option>
155
+ ))}
156
+ </select>
157
+ </div>
158
+ )}
159
+ {folderError && <Alert>{folderError}</Alert>}
160
+ </>
161
+ );
162
+
163
+ inner = (
164
+ <>
165
+ <aside className="hidden md:flex w-56 shrink-0 bg-surface border-r border-border flex-col p-3 gap-3">
166
+ {sidebarContent("desktop")}
167
+ </aside>
168
+ <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Mailbox">
169
+ <div className="flex flex-col gap-3">{sidebarContent("mobile")}</div>
170
+ </Drawer>
171
+ <main className="flex-1 min-w-0 flex flex-col">
172
+ {(mailboxes.length > 1 || folderError) && (
173
+ <button
174
+ type="button"
175
+ 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"
176
+ aria-label="Open mailbox switcher"
177
+ onClick={() => setDrawerOpen(true)}
178
+ >
179
+ <HiOutlineBars3 size={20} aria-hidden="true" />
180
+ </button>
181
+ )}
182
+ <TasksShellContext.Provider value={contextValue}>{children}</TasksShellContext.Provider>
183
+ </main>
184
+ </>
185
+ );
186
+ }
187
+
188
+ return (
189
+ <AppShell
190
+ active="tasks"
191
+ userUid={userUid}
192
+ authServerUrl={authServerUrl}
193
+ impersonating={impersonating}
194
+ impersonationBaseUrl={impersonationBaseUrl}
195
+ trusted={trusted}
196
+ pluginNav={pluginNav}
197
+ >
198
+ {inner}
199
+ </AppShell>
200
+ );
201
+ }
@@ -0,0 +1,39 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+
6
+ /** Page size the list pages (Contacts, Tasks) request per call - restapi caps `limit` at 500. */
7
+ export const LIST_PAGE_SIZE = 500;
8
+
9
+ /** Safety cap on pages fetched - stops a server that ignores `page` (and keeps returning a full first
10
+ * page) from looping forever. 40 pages x 500 = 20,000 items. */
11
+ export const MAX_LIST_PAGES = 40;
12
+
13
+ export interface ListAllPagesResult<T> {
14
+ items: T[];
15
+ /** `true` when fetching stopped at `maxPages` with the last page still full - there may be more items
16
+ * the caller isn't showing, so it should say so rather than presenting `items` as the whole list. */
17
+ truncated: boolean;
18
+ }
19
+
20
+ /**
21
+ * Fetches every page of a zero-based `limit`/`page` list endpoint, stopping at the first short page
22
+ * (fewer than `pageSize` items) - so a folder with more items than one page holds is shown in full
23
+ * rather than silently cut off at the first page. Stops at `maxPages` regardless, reporting `truncated`.
24
+ */
25
+ export async function listAllPages<T>(
26
+ fetchPage: (page: number) => Promise<T[]>,
27
+ pageSize: number = LIST_PAGE_SIZE,
28
+ maxPages: number = MAX_LIST_PAGES,
29
+ ): Promise<ListAllPagesResult<T>> {
30
+ const items: T[] = [];
31
+ for (let page = 0; page < maxPages; page++) {
32
+ const batch = await fetchPage(page);
33
+ items.push(...batch);
34
+ if (batch.length < pageSize) {
35
+ return { items, truncated: false };
36
+ }
37
+ }
38
+ return { items, truncated: true };
39
+ }
@@ -0,0 +1,73 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+
6
+ /**
7
+ * One navigation entry a plugin contributes through its manifest's `ui` field. Structurally identical to
8
+ * `@rapidmx/restapi`'s `PluginUiNavItem`, defined locally so this package doesn't depend on restapi.
9
+ */
10
+ export interface PluginUiNavItem {
11
+ /** Unique within its list. A plugin page passes this as its shell's `active` to highlight the entry. */
12
+ id: string;
13
+ label: string;
14
+ /** A same-origin path under the host's prefix (`/settings/...`, `/admin/...`, `/...`). */
15
+ href: string;
16
+ }
17
+
18
+ /**
19
+ * The navigation every enabled plugin with a successfully built UI contributes, supplied by the server's
20
+ * `WwwRoute`/`AdminConsoleRoute` `fetchProps` as the `pluginNav` page prop.
21
+ */
22
+ export interface PluginNav {
23
+ /** Appended to `SettingsShell`'s section list. */
24
+ settingsSections?: PluginUiNavItem[];
25
+ /** Appended to `AdminShell`'s icon rail and mobile tab bar. */
26
+ adminNav?: PluginUiNavItem[];
27
+ /** Appended to `AppShell`'s app rail and mobile tab bar. */
28
+ appRail?: PluginUiNavItem[];
29
+ }
30
+
31
+ /** The page prop every www and admin page receives - extended by `AppShellProps` and `AdminShellProps`, so
32
+ * a page that spreads its props onto its shell passes `pluginNav` through with no code of its own. */
33
+ export interface PluginNavProps {
34
+ pluginNav?: PluginNav;
35
+ }
36
+
37
+ /** A same-origin absolute path: starts with `/`, but not `//` or `/\` (both of which browsers treat as
38
+ * protocol-relative URLs to another host). */
39
+ export function isSafePluginHref(href: string): boolean {
40
+ return /^\/(?![/\\])/.test(href);
41
+ }
42
+
43
+ /**
44
+ * Appends a plugin's nav items after the core ones. Skips any item that isn't well-formed, whose `href`
45
+ * isn't a same-origin path (see `isSafePluginHref`), or whose id is already taken - by a core item or
46
+ * `reservedIds` (core ids always win) or by an earlier plugin item.
47
+ */
48
+ export function mergePluginNavItems<T extends { id: string }>(
49
+ coreItems: T[],
50
+ pluginItems: PluginUiNavItem[] | undefined,
51
+ toItem: (item: PluginUiNavItem) => T,
52
+ reservedIds: string[] = [],
53
+ ): T[] {
54
+ if (!pluginItems?.length) {
55
+ return coreItems;
56
+ }
57
+ const taken = new Set([...coreItems.map((item) => item.id), ...reservedIds]);
58
+ const merged = [...coreItems];
59
+ for (const item of pluginItems) {
60
+ if (
61
+ typeof item?.id !== "string" ||
62
+ typeof item.label !== "string" ||
63
+ typeof item.href !== "string" ||
64
+ !isSafePluginHref(item.href) ||
65
+ taken.has(item.id)
66
+ ) {
67
+ continue;
68
+ }
69
+ taken.add(item.id);
70
+ merged.push(toItem(item));
71
+ }
72
+ return merged;
73
+ }