@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
@@ -1,417 +1,438 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { useEffect, useMemo, useState } from "react";
6
- import { DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
7
- import { HiOutlineBars3 } from "react-icons/hi2";
8
- import {
9
- addDays,
10
- addMonths,
11
- addWeeks,
12
- eachDayOfInterval,
13
- endOfDay,
14
- endOfMonth,
15
- endOfWeek,
16
- format,
17
- startOfDay,
18
- startOfMonth,
19
- startOfWeek,
20
- } from "date-fns";
21
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
22
- import { CalendarEvent, listCalendarEvents } from "@rapidmx/react-shared/calendar/calendarApi.js";
23
- import { moveOccurrence, resizeOccurrenceEnd } from "@rapidmx/react-shared/calendar/calendarMutations.js";
24
- import { resolveDragAction } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
25
- import { createFolder } from "@rapidmx/react-shared/mail/mailApi.js";
26
- import { CalendarOccurrence, expandAllOccurrences } from "@rapidmx/react-shared/calendar/recurrence.js";
27
- import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
28
- import CalendarShell, { CalendarShellProps, useCalendarShell } from "../../shared/components/calendar/layout/CalendarShell.js";
29
- import CalendarListSidebar from "../../shared/components/calendar/CalendarListSidebar.js";
30
- import EventModal from "../../shared/components/calendar/EventModal.js";
31
- import MiniDatePicker from "@rapidmx/react-shared/components/pickers/MiniDatePicker.js";
32
- import MonthView from "../../shared/components/calendar/MonthView.js";
33
- import SplitDayView from "../../shared/components/calendar/SplitDayView.js";
34
- import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
35
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
36
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
37
-
38
- export default function CalendarPage(props: CalendarShellProps) {
39
- return (
40
- <CalendarShell {...props}>
41
- <CalendarContent />
42
- </CalendarShell>
43
- );
44
- }
45
-
46
- type ViewType = "month" | "week" | "workWeek" | "day" | "split";
47
- const VIEW_TYPES: ViewType[] = ["month", "week", "workWeek", "day", "split"];
48
- const VIEW_LABELS: Record<ViewType, string> = { month: "Month", week: "Week", workWeek: "Work Week", day: "Day", split: "Split" };
49
-
50
- interface ModalState {
51
- occurrence: CalendarOccurrence | null;
52
- initialStart?: Date;
53
- initialEnd?: Date;
54
- /** Overrides the default calendar a new event is created into (e.g. the column clicked in Split
55
- * view) — ignored when editing an existing occurrence, which always keeps its own `folderUid`. */
56
- targetFolderUid?: string;
57
- }
58
-
59
- function CalendarContent() {
60
- const { mailboxUid, folderUid, calendarFolders, mailboxCalendars, mailboxes, reloadFolders, colorFor } = useCalendarShell();
61
- // `PointerSensor` alone activates a drag on the very first touch-move, indistinguishable from a
62
- // scroll gesture on a touch device. `MouseSensor` (a small `distance` — desktop drags still start
63
- // immediately on a deliberate movement, no change from before) + `TouchSensor` (a `delay`+`tolerance`
64
- // — a touch drag only activates after a brief press-and-hold, so a quick swipe scrolls normally) is
65
- // dnd-kit's own documented pattern for this exact conflict.
66
- const sensors = useSensors(
67
- useSensor(MouseSensor, { activationConstraint: { distance: 8 } }),
68
- useSensor(TouchSensor, { activationConstraint: { delay: 250, tolerance: 8 } }),
69
- );
70
-
71
- const [drawerOpen, setDrawerOpen] = useState(false);
72
- const [view, setView] = useState<ViewType>("month");
73
- const [viewDate, setViewDate] = useState(() => startOfDay(new Date()));
74
- const [events, setEvents] = useState<CalendarEvent[]>([]);
75
- const [loading, setLoading] = useState(true);
76
- const [error, setError] = useState<string | null>(null);
77
- const [actionError, setActionError] = useState<string | null>(null);
78
- const [modal, setModal] = useState<ModalState | null>(null);
79
- // `null` means "not yet customized by the user" — every calendar is treated as checked without
80
- // needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
81
- // Once the user toggles anything, this becomes a real (possibly empty) set of exactly what's checked.
82
- const [checkedFolderUidsState, setCheckedFolderUidsState] = useState<Set<string> | null>(null);
83
-
84
- useEffect(() => {
85
- const params = new URLSearchParams(window.location.search);
86
- const requestedView = params.get("view");
87
- if ((VIEW_TYPES as string[]).includes(requestedView ?? "")) {
88
- setView(requestedView as ViewType);
89
- }
90
- const requestedDate = params.get("date");
91
- const parsedDate = requestedDate ? new Date(requestedDate) : null;
92
- if (parsedDate && !isNaN(parsedDate.getTime())) {
93
- setViewDate(startOfDay(parsedDate));
94
- }
95
- }, []);
96
-
97
- const checkedFolderUids = checkedFolderUidsState ?? new Set(calendarFolders.map((f) => f.uid));
98
- const folderColors = useMemo(
99
- () => Object.fromEntries(calendarFolders.map((f) => [f.uid, colorFor(f)])),
100
- [calendarFolders, colorFor],
101
- );
102
-
103
- function toggleCalendar(toggledUid: string) {
104
- setCheckedFolderUidsState((prev) => {
105
- const next = new Set(prev ?? calendarFolders.map((f) => f.uid));
106
- if (next.has(toggledUid)) {
107
- next.delete(toggledUid);
108
- } else {
109
- next.add(toggledUid);
110
- }
111
- return next;
112
- });
113
- }
114
-
115
- async function handleAddCalendar(targetMailboxUid: string, name: string, color: string) {
116
- const created = await createFolder({ mailboxUid: targetMailboxUid, name, type: "calendar", color });
117
- setCheckedFolderUidsState((prev) => new Set([...(prev ?? calendarFolders.map((f) => f.uid)), created.uid]));
118
- reloadFolders();
119
- }
120
-
121
- const { rangeStart, rangeEnd, days } = useMemo(() => {
122
- if (view === "month") {
123
- const start = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
124
- const end = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
125
- return { rangeStart: start, rangeEnd: end, days: [] as Date[] };
126
- }
127
- if (view === "week") {
128
- const start = startOfWeek(viewDate, { weekStartsOn: 1 });
129
- const end = endOfWeek(viewDate, { weekStartsOn: 1 });
130
- return { rangeStart: start, rangeEnd: end, days: eachDayOfInterval({ start, end }) };
131
- }
132
- if (view === "workWeek") {
133
- const start = startOfWeek(viewDate, { weekStartsOn: 1 });
134
- const weekdays = eachDayOfInterval({ start, end: endOfWeek(viewDate, { weekStartsOn: 1 }) }).slice(0, 5);
135
- return { rangeStart: start, rangeEnd: endOfDay(weekdays[weekdays.length - 1]), days: weekdays };
136
- }
137
- // "day" and "split" both show a single day, just laid out differently below.
138
- const start = startOfDay(viewDate);
139
- const end = endOfDay(viewDate);
140
- return { rangeStart: start, rangeEnd: end, days: [start] };
141
- }, [view, viewDate]);
142
-
143
- function reload() {
144
- const targets = calendarFolders.filter((f) => checkedFolderUids.has(f.uid));
145
- if (targets.length === 0) {
146
- setEvents([]);
147
- setLoading(false);
148
- return;
149
- }
150
- setLoading(true);
151
- setError(null);
152
- void Promise.allSettled(targets.map((f) => listCalendarEvents(f.uid)))
153
- .then((results) => {
154
- const loaded: CalendarEvent[] = [];
155
- const failures: { name: string; err: unknown }[] = [];
156
- results.forEach((result, i) => {
157
- if (result.status === "fulfilled") {
158
- loaded.push(...result.value);
159
- } else {
160
- failures.push({ name: targets[i].name, err: result.reason });
161
- }
162
- });
163
- setEvents(loaded);
164
- if (failures.length === 0) {
165
- return;
166
- }
167
- // A single calendar's failure (by far the common case — most mailboxes have exactly one)
168
- // surfaces that calendar's own error message unchanged, matching this page's original
169
- // single-calendar behavior exactly; only a genuine multi-calendar partial failure falls
170
- // back to a combined message naming which calendars didn't load.
171
- if (failures.length === 1 && targets.length === 1) {
172
- const err = failures[0].err;
173
- setError(err instanceof ApiRequestError ? err.message : "Could not load your calendar.");
174
- } else {
175
- setError(`Could not load events for: ${failures.map((f) => f.name).join(", ")}.`);
176
- }
177
- })
178
- .finally(() => setLoading(false));
179
- }
180
-
181
- // Keyed on stable string summaries (not the array/set objects themselves, which are new references
182
- // every render) so this only re-fires when the actual set of folders to fetch changes.
183
- const calendarFolderUidsKey = calendarFolders.map((f) => f.uid).sort().join(",");
184
- const checkedFolderUidsKey = Array.from(checkedFolderUids).sort().join(",");
185
- useEffect(reload, [calendarFolderUidsKey, checkedFolderUidsKey]);
186
-
187
- const occurrences = useMemo(
188
- () => expandAllOccurrences(events, rangeStart, rangeEnd),
189
- [events, rangeStart, rangeEnd],
190
- );
191
-
192
- const splitColumns = useMemo(
193
- () =>
194
- calendarFolders
195
- .filter((f) => checkedFolderUids.has(f.uid))
196
- .map((f) => ({ folderUid: f.uid, name: f.name, color: colorFor(f) })),
197
- [calendarFolders, checkedFolderUidsKey, colorFor],
198
- );
199
-
200
- function shiftView(direction: 1 | -1) {
201
- setViewDate((d) =>
202
- view === "month"
203
- ? addMonths(d, direction)
204
- : view === "week" || view === "workWeek"
205
- ? addWeeks(d, direction)
206
- : addDays(d, direction),
207
- );
208
- }
209
-
210
- function goToday() {
211
- setViewDate(startOfDay(new Date()));
212
- }
213
-
214
- function handleSelectDay(date: Date) {
215
- setView("day");
216
- setViewDate(startOfDay(date));
217
- }
218
-
219
- function openNewEvent(start?: Date, end?: Date, targetFolderUid?: string) {
220
- if (!mailboxUid || !folderUid) {
221
- return;
222
- }
223
- setModal({
224
- occurrence: null,
225
- initialStart: start ?? new Date(),
226
- initialEnd: end ?? new Date(Date.now() + 30 * 60_000),
227
- targetFolderUid,
228
- });
229
- }
230
-
231
- function openEvent(occurrence: CalendarOccurrence) {
232
- setModal({ occurrence });
233
- }
234
-
235
- function closeModal() {
236
- setModal(null);
237
- }
238
-
239
- function handleSaved() {
240
- setModal(null);
241
- reload();
242
- }
243
-
244
- function handleDeleted() {
245
- setModal(null);
246
- reload();
247
- }
248
-
249
- async function handleDragEnd(event: DragEndEvent) {
250
- const overId = event.over ? String(event.over.id) : undefined;
251
- const action = resolveDragAction(String(event.active.id), overId, occurrences);
252
- if (!action) {
253
- return;
254
- }
255
- setActionError(null);
256
- try {
257
- if (action.type === "move") {
258
- await moveOccurrence(action.occurrence, action.deltaMs);
259
- } else {
260
- await resizeOccurrenceEnd(action.occurrence, action.newEnd);
261
- }
262
- reload();
263
- } catch (err) {
264
- setActionError(err instanceof ApiRequestError ? err.message : "Could not update this event.");
265
- }
266
- }
267
-
268
- const title =
269
- view === "month"
270
- ? format(viewDate, "MMMM yyyy")
271
- : view === "week" || view === "workWeek"
272
- ? `${format(rangeStart, "MMM d")} – ${format(rangeEnd, "MMM d, yyyy")}`
273
- : format(viewDate, "EEEE, MMMM d, yyyy");
274
-
275
- const modalFolderUid = modal ? (modal.targetFolderUid ?? modal.occurrence?.folderUid ?? folderUid) : undefined;
276
- const modalMailboxUid = calendarFolders.find((f) => f.uid === modalFolderUid)?.mailboxUid ?? mailboxUid;
277
- const modalMailbox = mailboxes.find((mb) => mb.uid === modalMailboxUid);
278
- // Mailboxes a new event can be created in - only those with at least one calendar.
279
- const mailboxOptions = useMemo(
280
- () =>
281
- mailboxCalendars
282
- .filter((mc) => mc.calendarFolders.length > 0)
283
- .map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })),
284
- [mailboxCalendars],
285
- );
286
-
287
- const sidebarContent = (
288
- <>
289
- <MiniDatePicker selected={viewDate} onSelect={(date) => setViewDate(startOfDay(date))} />
290
- <CalendarListSidebar
291
- mailboxCalendars={mailboxCalendars}
292
- checkedFolderUids={checkedFolderUids}
293
- onToggle={toggleCalendar}
294
- onAddCalendar={handleAddCalendar}
295
- colorFor={colorFor}
296
- />
297
- </>
298
- );
299
-
300
- return (
301
- <div className="flex-1 flex min-h-0">
302
- <div className="hidden md:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto">{sidebarContent}</div>
303
- <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Calendars">
304
- <div className="flex flex-col">{sidebarContent}</div>
305
- </Drawer>
306
- <div className="flex-1 flex flex-col min-h-0">
307
- <div className="flex items-center gap-3 p-3 border-b border-border shrink-0">
308
- <button
309
- type="button"
310
- className="md:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0"
311
- aria-label="Open calendars"
312
- onClick={() => setDrawerOpen(true)}
313
- >
314
- <HiOutlineBars3 size={20} aria-hidden="true" />
315
- </button>
316
- <Button type="button" onClick={() => openNewEvent()} className="!w-auto shrink-0">
317
- + New event
318
- </Button>
319
- <div className="flex items-center gap-1">
320
- <button type="button" onClick={() => shiftView(-1)} aria-label="Previous" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
321
- &lsaquo;
322
- </button>
323
- <button type="button" onClick={goToday} className="px-2 h-7 text-sm rounded-sm hover:bg-surface-alt">
324
- Today
325
- </button>
326
- <button type="button" onClick={() => shiftView(1)} aria-label="Next" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
327
- &rsaquo;
328
- </button>
329
- </div>
330
- <h1 className="hidden md:block text-lg font-bold tracking-tight">{title}</h1>
331
- <div className="ml-auto flex gap-1 overflow-x-auto">
332
- {VIEW_TYPES.map((v) => (
333
- <button
334
- key={v}
335
- type="button"
336
- onClick={() => setView(v)}
337
- className={[
338
- "px-3 h-7 text-sm rounded-sm whitespace-nowrap",
339
- view === v ? "bg-primary text-white" : "hover:bg-surface-alt",
340
- (v === "workWeek" || v === "split") && "hidden md:inline-block",
341
- ]
342
- .filter(Boolean)
343
- .join(" ")}
344
- >
345
- {VIEW_LABELS[v]}
346
- </button>
347
- ))}
348
- </div>
349
- </div>
350
-
351
- {error && (
352
- <div className="p-3">
353
- <Alert>{error}</Alert>
354
- </div>
355
- )}
356
- {actionError && (
357
- <div className="p-3">
358
- <Alert>{actionError}</Alert>
359
- </div>
360
- )}
361
-
362
- {loading ? (
363
- <p className="p-4 text-sm text-text-muted">Loading&hellip;</p>
364
- ) : (
365
- <DndContext sensors={sensors} onDragEnd={handleDragEnd}>
366
- {view === "month" ? (
367
- <MonthView
368
- viewDate={viewDate}
369
- occurrences={occurrences}
370
- folderColors={folderColors}
371
- onSelectDay={handleSelectDay}
372
- onSelectEvent={openEvent}
373
- />
374
- ) : view === "split" ? (
375
- <SplitDayView
376
- day={viewDate}
377
- columns={splitColumns}
378
- occurrences={occurrences}
379
- onSelectEvent={openEvent}
380
- onSelectSlot={(start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid)}
381
- />
382
- ) : (
383
- <TimeGridView
384
- days={days}
385
- occurrences={occurrences}
386
- folderColors={folderColors}
387
- onSelectEvent={openEvent}
388
- onSelectSlot={openNewEvent}
389
- />
390
- )}
391
- </DndContext>
392
- )}
393
-
394
- {/* `folderUid` is guaranteed defined whenever `modal` is: `openNewEvent` only sets it after
395
- checking it, and `openEvent` only fires from an occurrence that itself required a
396
- successful, folder-scoped load to render. The modal is scoped to that folder's own mailbox
397
- (which may be a shared one) - its organizer address and calendar picker come from there. */}
398
- {modal && modalMailbox && (
399
- <EventModal
400
- open
401
- onClose={closeModal}
402
- mailboxUid={modalMailbox.uid}
403
- folderUid={modalFolderUid!}
404
- calendars={calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name }))}
405
- mailboxOptions={mailboxOptions}
406
- organizerAddress={modalMailbox.primarySmtpAddress}
407
- occurrence={modal.occurrence}
408
- initialStart={modal.initialStart}
409
- initialEnd={modal.initialEnd}
410
- onSaved={handleSaved}
411
- onDeleted={handleDeleted}
412
- />
413
- )}
414
- </div>
415
- </div>
416
- );
417
- }
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { useEffect, useMemo, useRef, useState } from "react";
6
+ import { DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
7
+ import { HiOutlineBars3 } from "react-icons/hi2";
8
+ import {
9
+ addDays,
10
+ addMonths,
11
+ addWeeks,
12
+ eachDayOfInterval,
13
+ endOfDay,
14
+ endOfMonth,
15
+ endOfWeek,
16
+ format,
17
+ parseISO,
18
+ startOfDay,
19
+ startOfMonth,
20
+ startOfWeek,
21
+ } from "date-fns";
22
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
23
+ import { CalendarEvent, listCalendarEvents } from "@rapidmx/react-shared/calendar/calendarApi.js";
24
+ import { moveOccurrence, resizeOccurrenceEnd } from "@rapidmx/react-shared/calendar/calendarMutations.js";
25
+ import { resolveDragAction } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
26
+ import { createFolder } from "@rapidmx/react-shared/mail/mailApi.js";
27
+ import { CalendarOccurrence, expandAllOccurrences } from "@rapidmx/react-shared/calendar/recurrence.js";
28
+ import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
29
+ import CalendarShell, { CalendarShellProps, useCalendarShell } from "../../shared/components/calendar/layout/CalendarShell.js";
30
+ import CalendarListSidebar from "../../shared/components/calendar/CalendarListSidebar.js";
31
+ import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
32
+ import EventModal from "../../shared/components/calendar/EventModal.js";
33
+ import MiniDatePicker from "@rapidmx/react-shared/components/pickers/MiniDatePicker.js";
34
+ import MonthView from "../../shared/components/calendar/MonthView.js";
35
+ import SplitDayView from "../../shared/components/calendar/SplitDayView.js";
36
+ import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
37
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
38
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
39
+
40
+ export default function CalendarPage(props: CalendarShellProps) {
41
+ return (
42
+ <CalendarShell {...props}>
43
+ <CalendarContent userUid={props.userUid} />
44
+ </CalendarShell>
45
+ );
46
+ }
47
+
48
+ type ViewType = "month" | "week" | "workWeek" | "day" | "split";
49
+ const VIEW_TYPES: ViewType[] = ["month", "week", "workWeek", "day", "split"];
50
+ const VIEW_LABELS: Record<ViewType, string> = { month: "Month", week: "Week", workWeek: "Work Week", day: "Day", split: "Split" };
51
+
52
+ interface ModalState {
53
+ occurrence: CalendarOccurrence | null;
54
+ initialStart?: Date;
55
+ initialEnd?: Date;
56
+ /** Overrides the default calendar a new event is created into (e.g. the column clicked in Split
57
+ * view) — ignored when editing an existing occurrence, which always keeps its own `folderUid`. */
58
+ targetFolderUid?: string;
59
+ }
60
+
61
+ function CalendarContent({ userUid }: { userUid?: string }) {
62
+ const { mailboxUid, folderUid, calendarFolders, mailboxCalendars, mailboxes, reloadFolders, colorFor } = useCalendarShell();
63
+ // `PointerSensor` alone activates a drag on the very first touch-move, indistinguishable from a
64
+ // scroll gesture on a touch device. `MouseSensor` (a small `distance` — desktop drags still start
65
+ // immediately on a deliberate movement, no change from before) + `TouchSensor` (a `delay`+`tolerance`
66
+ // — a touch drag only activates after a brief press-and-hold, so a quick swipe scrolls normally) is
67
+ // dnd-kit's own documented pattern for this exact conflict.
68
+ const sensors = useSensors(
69
+ useSensor(MouseSensor, { activationConstraint: { distance: 8 } }),
70
+ useSensor(TouchSensor, { activationConstraint: { delay: 250, tolerance: 8 } }),
71
+ );
72
+
73
+ const [drawerOpen, setDrawerOpen] = useState(false);
74
+ const [view, setView] = useState<ViewType>("month");
75
+ const [viewDate, setViewDate] = useState(() => startOfDay(new Date()));
76
+ const [events, setEvents] = useState<CalendarEvent[]>([]);
77
+ const [loading, setLoading] = useState(true);
78
+ const [error, setError] = useState<string | null>(null);
79
+ const [actionError, setActionError] = useState<string | null>(null);
80
+ const [modal, setModal] = useState<ModalState | null>(null);
81
+ // `null` means "not yet customized by the user" — every calendar is treated as checked without
82
+ // needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
83
+ // Once the user toggles anything, this becomes a real (possibly empty) set of exactly what's checked.
84
+ const [checkedFolderUidsState, setCheckedFolderUidsState] = useState<Set<string> | null>(null);
85
+
86
+ useEffect(() => {
87
+ const params = new URLSearchParams(window.location.search);
88
+ const requestedView = params.get("view");
89
+ if ((VIEW_TYPES as string[]).includes(requestedView ?? "")) {
90
+ setView(requestedView as ViewType);
91
+ }
92
+ const requestedDate = params.get("date");
93
+ // `parseISO()`, not `new Date()` - `?date=2026-06-16` is a local calendar day, but `new Date()` reads a
94
+ // date-only string as UTC midnight, i.e. the previous day anywhere west of UTC.
95
+ const parsedDate = requestedDate ? parseISO(requestedDate) : null;
96
+ if (parsedDate && !isNaN(parsedDate.getTime())) {
97
+ setViewDate(startOfDay(parsedDate));
98
+ }
99
+ }, []);
100
+
101
+ const checkedFolderUids = checkedFolderUidsState ?? new Set(calendarFolders.map((f) => f.uid));
102
+ const folderColors = useMemo(
103
+ () => Object.fromEntries(calendarFolders.map((f) => [f.uid, colorFor(f)])),
104
+ [calendarFolders, colorFor],
105
+ );
106
+
107
+ function toggleCalendar(toggledUid: string) {
108
+ setCheckedFolderUidsState((prev) => {
109
+ const next = new Set(prev ?? calendarFolders.map((f) => f.uid));
110
+ if (next.has(toggledUid)) {
111
+ next.delete(toggledUid);
112
+ } else {
113
+ next.add(toggledUid);
114
+ }
115
+ return next;
116
+ });
117
+ }
118
+
119
+ async function handleAddCalendar(targetMailboxUid: string, name: string, color: string) {
120
+ const created = await createFolder({ mailboxUid: targetMailboxUid, name, type: "calendar", color });
121
+ setCheckedFolderUidsState((prev) => new Set([...(prev ?? calendarFolders.map((f) => f.uid)), created.uid]));
122
+ reloadFolders();
123
+ }
124
+
125
+ const { rangeStart, rangeEnd, days } = useMemo(() => {
126
+ if (view === "month") {
127
+ const start = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
128
+ const end = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
129
+ return { rangeStart: start, rangeEnd: end, days: [] as Date[] };
130
+ }
131
+ if (view === "week") {
132
+ const start = startOfWeek(viewDate, { weekStartsOn: 1 });
133
+ const end = endOfWeek(viewDate, { weekStartsOn: 1 });
134
+ return { rangeStart: start, rangeEnd: end, days: eachDayOfInterval({ start, end }) };
135
+ }
136
+ if (view === "workWeek") {
137
+ const start = startOfWeek(viewDate, { weekStartsOn: 1 });
138
+ const weekdays = eachDayOfInterval({ start, end: endOfWeek(viewDate, { weekStartsOn: 1 }) }).slice(0, 5);
139
+ return { rangeStart: start, rangeEnd: endOfDay(weekdays[weekdays.length - 1]), days: weekdays };
140
+ }
141
+ // "day" and "split" both show a single day, just laid out differently below.
142
+ const start = startOfDay(viewDate);
143
+ const end = endOfDay(viewDate);
144
+ return { rangeStart: start, rangeEnd: end, days: [start] };
145
+ }, [view, viewDate]);
146
+
147
+ // Only the most recent reload() may apply its results - toggling calendars (or saving/dragging an event)
148
+ // while an earlier, slower fetch is still in flight must not let that stale response overwrite newer
149
+ // events. (No date range is passed: `listCalendarEvents()` deliberately fetches the whole calendar and
150
+ // filters client-side - see its own doc comment on the server-side range query it works around.)
151
+ const reloadSeqRef = useRef(0);
152
+
153
+ function reload() {
154
+ const seq = ++reloadSeqRef.current;
155
+ const targets = calendarFolders.filter((f) => checkedFolderUids.has(f.uid));
156
+ if (targets.length === 0) {
157
+ setEvents([]);
158
+ setLoading(false);
159
+ return;
160
+ }
161
+ setLoading(true);
162
+ setError(null);
163
+ void Promise.allSettled(targets.map((f) => listCalendarEvents(f.uid)))
164
+ .then((results) => {
165
+ if (seq !== reloadSeqRef.current) {
166
+ return;
167
+ }
168
+ const loaded: CalendarEvent[] = [];
169
+ const failures: { name: string; err: unknown }[] = [];
170
+ results.forEach((result, i) => {
171
+ if (result.status === "fulfilled") {
172
+ loaded.push(...result.value);
173
+ } else {
174
+ failures.push({ name: targets[i].name, err: result.reason });
175
+ }
176
+ });
177
+ setEvents(loaded);
178
+ if (failures.length === 0) {
179
+ return;
180
+ }
181
+ // A single calendar's failure (by far the common case — most mailboxes have exactly one)
182
+ // surfaces that calendar's own error message unchanged, matching this page's original
183
+ // single-calendar behavior exactly; only a genuine multi-calendar partial failure falls
184
+ // back to a combined message naming which calendars didn't load.
185
+ if (failures.length === 1 && targets.length === 1) {
186
+ const err = failures[0].err;
187
+ setError(err instanceof ApiRequestError ? err.message : "Could not load your calendar.");
188
+ } else {
189
+ setError(`Could not load events for: ${failures.map((f) => f.name).join(", ")}.`);
190
+ }
191
+ })
192
+ .finally(() => {
193
+ if (seq === reloadSeqRef.current) {
194
+ setLoading(false);
195
+ }
196
+ });
197
+ }
198
+
199
+ // Keyed on stable string summaries (not the array/set objects themselves, which are new references
200
+ // every render) so this only re-fires when the actual set of folders to fetch changes.
201
+ const calendarFolderUidsKey = calendarFolders.map((f) => f.uid).sort().join(",");
202
+ const checkedFolderUidsKey = Array.from(checkedFolderUids).sort().join(",");
203
+ useEffect(reload, [calendarFolderUidsKey, checkedFolderUidsKey]);
204
+
205
+ const occurrences = useMemo(
206
+ () => expandAllOccurrences(events, rangeStart, rangeEnd),
207
+ [events, rangeStart, rangeEnd],
208
+ );
209
+
210
+ const splitColumns = useMemo(
211
+ () =>
212
+ calendarFolders
213
+ .filter((f) => checkedFolderUids.has(f.uid))
214
+ .map((f) => ({ folderUid: f.uid, name: f.name, color: colorFor(f) })),
215
+ [calendarFolders, checkedFolderUidsKey, colorFor],
216
+ );
217
+
218
+ function shiftView(direction: 1 | -1) {
219
+ setViewDate((d) =>
220
+ view === "month"
221
+ ? addMonths(d, direction)
222
+ : view === "week" || view === "workWeek"
223
+ ? addWeeks(d, direction)
224
+ : addDays(d, direction),
225
+ );
226
+ }
227
+
228
+ function goToday() {
229
+ setViewDate(startOfDay(new Date()));
230
+ }
231
+
232
+ function handleSelectDay(date: Date) {
233
+ setView("day");
234
+ setViewDate(startOfDay(date));
235
+ }
236
+
237
+ function openNewEvent(start?: Date, end?: Date, targetFolderUid?: string) {
238
+ if (!mailboxUid || !folderUid) {
239
+ return;
240
+ }
241
+ setModal({
242
+ occurrence: null,
243
+ initialStart: start ?? new Date(),
244
+ initialEnd: end ?? new Date(Date.now() + 30 * 60_000),
245
+ targetFolderUid,
246
+ });
247
+ }
248
+
249
+ function openEvent(occurrence: CalendarOccurrence) {
250
+ setModal({ occurrence });
251
+ }
252
+
253
+ function closeModal() {
254
+ setModal(null);
255
+ }
256
+
257
+ function handleSaved() {
258
+ setModal(null);
259
+ reload();
260
+ }
261
+
262
+ function handleDeleted() {
263
+ setModal(null);
264
+ reload();
265
+ }
266
+
267
+ async function handleDragEnd(event: DragEndEvent) {
268
+ const overId = event.over ? String(event.over.id) : undefined;
269
+ const action = resolveDragAction(String(event.active.id), overId, occurrences);
270
+ if (!action) {
271
+ return;
272
+ }
273
+ setActionError(null);
274
+ try {
275
+ if (action.type === "move") {
276
+ await moveOccurrence(action.occurrence, action.deltaMs);
277
+ } else {
278
+ await resizeOccurrenceEnd(action.occurrence, action.newEnd);
279
+ }
280
+ reload();
281
+ } catch (err) {
282
+ setActionError(err instanceof ApiRequestError ? err.message : "Could not update this event.");
283
+ }
284
+ }
285
+
286
+ const title =
287
+ view === "month"
288
+ ? format(viewDate, "MMMM yyyy")
289
+ : view === "week" || view === "workWeek"
290
+ ? `${format(rangeStart, "MMM d")} – ${format(rangeEnd, "MMM d, yyyy")}`
291
+ : format(viewDate, "EEEE, MMMM d, yyyy");
292
+
293
+ const modalFolderUid = modal ? (modal.targetFolderUid ?? modal.occurrence?.folderUid ?? folderUid) : undefined;
294
+ const modalMailboxUid = calendarFolders.find((f) => f.uid === modalFolderUid)?.mailboxUid ?? mailboxUid;
295
+ const modalMailbox = mailboxes.find((mb) => mb.uid === modalMailboxUid);
296
+ // Mailboxes a new event can be created in - only those with at least one calendar that the caller can
297
+ // write to (a view-only share is left out; see writableMailboxes.ts).
298
+ const writableMailboxes = useWritableMailboxes(mailboxes, userUid, modalMailboxUid);
299
+ const mailboxOptions = useMemo(
300
+ () =>
301
+ mailboxCalendars
302
+ .filter((mc) => mc.calendarFolders.length > 0 && writableMailboxes.some((mb) => mb.uid === mc.mailbox.uid))
303
+ .map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })),
304
+ [mailboxCalendars, writableMailboxes],
305
+ );
306
+
307
+ const sidebarContent = (
308
+ <>
309
+ <MiniDatePicker selected={viewDate} onSelect={(date) => setViewDate(startOfDay(date))} />
310
+ <CalendarListSidebar
311
+ mailboxCalendars={mailboxCalendars}
312
+ checkedFolderUids={checkedFolderUids}
313
+ onToggle={toggleCalendar}
314
+ onAddCalendar={handleAddCalendar}
315
+ colorFor={colorFor}
316
+ />
317
+ </>
318
+ );
319
+
320
+ return (
321
+ <div className="flex-1 flex min-h-0">
322
+ <div className="hidden md:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto">{sidebarContent}</div>
323
+ <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Calendars">
324
+ <div className="flex flex-col">{sidebarContent}</div>
325
+ </Drawer>
326
+ <div className="flex-1 flex flex-col min-h-0">
327
+ <div className="flex items-center gap-3 p-3 border-b border-border shrink-0">
328
+ <button
329
+ type="button"
330
+ className="md:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0"
331
+ aria-label="Open calendars"
332
+ onClick={() => setDrawerOpen(true)}
333
+ >
334
+ <HiOutlineBars3 size={20} aria-hidden="true" />
335
+ </button>
336
+ <Button type="button" onClick={() => openNewEvent()} className="!w-auto shrink-0">
337
+ + New event
338
+ </Button>
339
+ <div className="flex items-center gap-1">
340
+ <button type="button" onClick={() => shiftView(-1)} aria-label="Previous" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
341
+ &lsaquo;
342
+ </button>
343
+ <button type="button" onClick={goToday} className="px-2 h-7 text-sm rounded-sm hover:bg-surface-alt">
344
+ Today
345
+ </button>
346
+ <button type="button" onClick={() => shiftView(1)} aria-label="Next" className="w-7 h-7 text-sm rounded-sm hover:bg-surface-alt">
347
+ &rsaquo;
348
+ </button>
349
+ </div>
350
+ <h1 className="hidden md:block text-lg font-bold tracking-tight">{title}</h1>
351
+ <div className="ml-auto flex gap-1 overflow-x-auto">
352
+ {VIEW_TYPES.map((v) => (
353
+ <button
354
+ key={v}
355
+ type="button"
356
+ onClick={() => setView(v)}
357
+ className={[
358
+ "px-3 h-7 text-sm rounded-sm whitespace-nowrap",
359
+ view === v ? "bg-primary text-white" : "hover:bg-surface-alt",
360
+ (v === "workWeek" || v === "split") && "hidden md:inline-block",
361
+ ]
362
+ .filter(Boolean)
363
+ .join(" ")}
364
+ >
365
+ {VIEW_LABELS[v]}
366
+ </button>
367
+ ))}
368
+ </div>
369
+ </div>
370
+
371
+ {error && (
372
+ <div className="p-3">
373
+ <Alert>{error}</Alert>
374
+ </div>
375
+ )}
376
+ {actionError && (
377
+ <div className="p-3">
378
+ <Alert>{actionError}</Alert>
379
+ </div>
380
+ )}
381
+
382
+ {loading ? (
383
+ <p className="p-4 text-sm text-text-muted">Loading&hellip;</p>
384
+ ) : (
385
+ <DndContext sensors={sensors} onDragEnd={handleDragEnd}>
386
+ {view === "month" ? (
387
+ <MonthView
388
+ viewDate={viewDate}
389
+ occurrences={occurrences}
390
+ folderColors={folderColors}
391
+ onSelectDay={handleSelectDay}
392
+ onSelectEvent={openEvent}
393
+ />
394
+ ) : view === "split" ? (
395
+ <SplitDayView
396
+ day={viewDate}
397
+ columns={splitColumns}
398
+ occurrences={occurrences}
399
+ onSelectEvent={openEvent}
400
+ onSelectSlot={(start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid)}
401
+ />
402
+ ) : (
403
+ <TimeGridView
404
+ days={days}
405
+ occurrences={occurrences}
406
+ folderColors={folderColors}
407
+ onSelectEvent={openEvent}
408
+ onSelectSlot={openNewEvent}
409
+ />
410
+ )}
411
+ </DndContext>
412
+ )}
413
+
414
+ {/* `folderUid` is guaranteed defined whenever `modal` is: `openNewEvent` only sets it after
415
+ checking it, and `openEvent` only fires from an occurrence that itself required a
416
+ successful, folder-scoped load to render. The modal is scoped to that folder's own mailbox
417
+ (which may be a shared one) - its organizer address and calendar picker come from there. */}
418
+ {modal && modalMailbox && (
419
+ <EventModal
420
+ open
421
+ onClose={closeModal}
422
+ mailboxUid={modalMailbox.uid}
423
+ folderUid={modalFolderUid!}
424
+ calendars={calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name }))}
425
+ mailboxOptions={mailboxOptions}
426
+ organizerAddress={modalMailbox.primarySmtpAddress}
427
+ organizerAliases={modalMailbox.aliasAddresses}
428
+ occurrence={modal.occurrence}
429
+ initialStart={modal.initialStart}
430
+ initialEnd={modal.initialEnd}
431
+ onSaved={handleSaved}
432
+ onDeleted={handleDeleted}
433
+ />
434
+ )}
435
+ </div>
436
+ </div>
437
+ );
438
+ }