@rapidmx/web-client 0.14.1 → 0.16.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 (314) hide show
  1. package/README.md +373 -373
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -480
  4. package/apps/admin/domains/[uid].tsx +164 -163
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -348
  6. package/apps/admin/index.tsx +127 -124
  7. package/apps/admin/mailboxes/[uid].tsx +269 -224
  8. package/apps/admin/mailboxes/new/index.tsx +28 -28
  9. package/apps/admin/plugins/index.tsx +15 -15
  10. package/apps/admin/retention-policy/index.tsx +39 -39
  11. package/apps/admin/signing-certificates/index.tsx +343 -343
  12. package/apps/escrow/audit-log/index.tsx +196 -196
  13. package/apps/escrow/matters/[uid].tsx +621 -621
  14. package/apps/shared/appearance/AppearanceForm.tsx +9 -1
  15. package/apps/shared/auth/adminAccess.ts +99 -99
  16. package/apps/shared/calendar/bookingPlugin.ts +100 -0
  17. package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
  18. package/apps/shared/components/admin/layout/AdminShell.tsx +378 -346
  19. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
  20. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  21. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
  22. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  23. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  24. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +53 -4
  25. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +64 -10
  27. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  28. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  29. package/apps/shared/components/admin/setup/SetupWizard.tsx +444 -444
  30. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  31. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
  32. package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
  33. package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
  34. package/apps/shared/components/calendar/EventDetails.tsx +401 -0
  35. package/apps/shared/components/calendar/EventEditor.tsx +635 -0
  36. package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
  37. package/apps/shared/components/calendar/EventFormParts.tsx +591 -0
  38. package/apps/shared/components/calendar/EventModal.tsx +206 -946
  39. package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
  40. package/apps/shared/components/calendar/EventShell.tsx +202 -0
  41. package/apps/shared/components/calendar/FindATime.tsx +379 -0
  42. package/apps/shared/components/calendar/GuestInput.tsx +100 -0
  43. package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
  44. package/apps/shared/components/calendar/MonthView.tsx +185 -173
  45. package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
  46. package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
  47. package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
  48. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -222
  49. package/apps/shared/components/calendar/RequestChangeForm.tsx +240 -0
  50. package/apps/shared/components/calendar/SplitDayView.tsx +144 -143
  51. package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
  52. package/apps/shared/components/calendar/TimeGridView.tsx +246 -244
  53. package/apps/shared/components/calendar/allDay.ts +124 -124
  54. package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
  55. package/apps/shared/components/calendar/eventForm.ts +108 -0
  56. package/apps/shared/components/calendar/eventFormat.ts +210 -0
  57. package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
  58. package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
  59. package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
  60. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  61. package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
  62. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
  63. package/apps/shared/components/contacts/layout/ContactsShell.tsx +217 -216
  64. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  65. package/apps/shared/components/layout/AppShell.tsx +480 -480
  66. package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
  67. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  68. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  69. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  70. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  71. package/apps/shared/components/layout/UserMenu.tsx +439 -372
  72. package/apps/shared/components/mail/ConversationList.tsx +323 -271
  73. package/apps/shared/components/mail/ConversationThreadPane.tsx +130 -5
  74. package/apps/shared/components/mail/InviteCard.tsx +387 -0
  75. package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
  76. package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1537
  77. package/apps/shared/components/mail/SwipeRow.tsx +120 -0
  78. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  79. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  80. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1673
  81. package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
  82. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  83. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  84. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  85. package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
  86. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
  87. package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
  88. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
  89. package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
  90. package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
  91. package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
  92. package/apps/shared/components/mail/layout/MailShell.tsx +546 -490
  93. package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
  94. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
  95. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  96. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  97. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +255 -253
  99. package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
  101. package/apps/shared/gestures/useEnterSlide.ts +54 -0
  102. package/apps/shared/gestures/useSwipe.ts +114 -0
  103. package/apps/shared/gestures/useSwipeSlide.ts +113 -0
  104. package/apps/shared/gestures/useWheelPaging.ts +107 -0
  105. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  106. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  107. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  108. package/apps/shared/keyboard/dispatch.ts +124 -124
  109. package/apps/shared/keyboard/format.ts +89 -89
  110. package/apps/shared/keyboard/keymap.ts +114 -114
  111. package/apps/shared/keyboard/registry.ts +65 -65
  112. package/apps/shared/keyboard/targets.ts +79 -79
  113. package/apps/shared/mail/folderCounts.ts +17 -0
  114. package/apps/shared/mail/folderTree.ts +143 -143
  115. package/apps/shared/mail/listAllPages.ts +39 -39
  116. package/apps/shared/mail/newMailNotifications.ts +3 -15
  117. package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
  118. package/apps/shared/mail/outbox/sendJob.ts +19 -1
  119. package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
  120. package/apps/shared/mail/primaryMailbox.ts +71 -0
  121. package/apps/shared/mail/useCollapsedSections.ts +146 -0
  122. package/apps/shared/mail/useMailConnection.ts +205 -203
  123. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  124. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  125. package/apps/shared/mail/useNewMailNotifications.ts +2 -2
  126. package/apps/shared/navigation/AppRouter.tsx +300 -300
  127. package/apps/shared/navigation/routerContext.tsx +83 -83
  128. package/apps/shared/notifications/preferences.ts +36 -0
  129. package/apps/shared/notifications/store.ts +9 -0
  130. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  131. package/apps/shared/search/crossMailboxSearch.ts +169 -0
  132. package/apps/shared/search/localIndexBuilder.ts +481 -481
  133. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  134. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  135. package/apps/shared/signing/enrollmentView.ts +251 -251
  136. package/apps/shared/signing/useNow.ts +19 -19
  137. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  138. package/apps/shared/styles/app.css +396 -386
  139. package/apps/www/_routes.ts +1 -0
  140. package/apps/www/calendar/index.tsx +578 -469
  141. package/apps/www/contacts/[uid].tsx +109 -109
  142. package/apps/www/contacts/index.tsx +6 -0
  143. package/apps/www/index.tsx +2847 -2332
  144. package/apps/www/messages/[uid].tsx +135 -108
  145. package/apps/www/settings/auto-reply/index.tsx +133 -133
  146. package/apps/www/settings/encryption/index.tsx +1287 -1287
  147. package/apps/www/settings/filters/[uid].tsx +176 -176
  148. package/apps/www/settings/filters/index.tsx +102 -102
  149. package/apps/www/settings/filters/new/index.tsx +140 -140
  150. package/apps/www/settings/labels/index.tsx +204 -204
  151. package/apps/www/settings/privacy/index.tsx +492 -492
  152. package/apps/www/settings/profile/index.tsx +248 -0
  153. package/apps/www/settings/read-receipts/index.tsx +147 -147
  154. package/apps/www/settings/sharing/index.tsx +256 -256
  155. package/apps/www/settings/signatures/[uid].tsx +172 -172
  156. package/apps/www/settings/signatures/index.tsx +88 -88
  157. package/apps/www/settings/signatures/new/index.tsx +135 -135
  158. package/apps/www/tasks/index.tsx +649 -649
  159. package/dist/apps/admin/data-requests/index.js +1 -1
  160. package/dist/apps/admin/domains/[uid].js +4 -2
  161. package/dist/apps/admin/index.js +2 -1
  162. package/dist/apps/admin/mailboxes/[uid].js +12 -2
  163. package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
  164. package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
  165. package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
  166. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
  167. package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
  168. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  169. package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
  170. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
  171. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
  172. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
  173. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
  174. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +28 -4
  176. package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
  177. package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
  178. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
  179. package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
  180. package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
  181. package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
  182. package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
  183. package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
  184. package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
  185. package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
  186. package/dist/apps/shared/components/calendar/EventEditor.js +544 -0
  187. package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
  188. package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
  189. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +70 -0
  190. package/dist/apps/shared/components/calendar/EventFormParts.js +191 -0
  191. package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
  192. package/dist/apps/shared/components/calendar/EventModal.js +57 -459
  193. package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
  194. package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
  195. package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
  196. package/dist/apps/shared/components/calendar/EventShell.js +134 -0
  197. package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
  198. package/dist/apps/shared/components/calendar/FindATime.js +190 -0
  199. package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
  200. package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
  201. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
  202. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
  203. package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
  204. package/dist/apps/shared/components/calendar/MonthView.js +11 -5
  205. package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
  206. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
  207. package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
  208. package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
  209. package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
  210. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
  211. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  212. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
  213. package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
  214. package/dist/apps/shared/components/calendar/RequestChangeForm.js +127 -0
  215. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
  216. package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
  217. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
  218. package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
  219. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
  220. package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
  221. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
  222. package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
  223. package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
  224. package/dist/apps/shared/components/calendar/eventForm.js +1 -0
  225. package/dist/apps/shared/components/calendar/eventFormat.d.ts +80 -0
  226. package/dist/apps/shared/components/calendar/eventFormat.js +190 -0
  227. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
  228. package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
  229. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
  230. package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
  231. package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
  232. package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
  233. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  234. package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
  235. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -3
  236. package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
  237. package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
  238. package/dist/apps/shared/components/layout/UserMenu.d.ts +14 -9
  239. package/dist/apps/shared/components/layout/UserMenu.js +74 -26
  240. package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
  241. package/dist/apps/shared/components/mail/ConversationList.js +21 -4
  242. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  243. package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
  244. package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
  245. package/dist/apps/shared/components/mail/InviteCard.js +150 -0
  246. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
  247. package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
  248. package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
  249. package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
  250. package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
  251. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +16 -7
  252. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
  253. package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
  254. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
  255. package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
  256. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
  257. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
  258. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
  259. package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
  260. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
  261. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
  262. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
  263. package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
  264. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
  265. package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
  266. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
  267. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
  268. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +10 -0
  269. package/dist/apps/shared/components/mail/layout/MailShell.js +76 -46
  270. package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
  271. package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
  272. package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
  273. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
  274. package/dist/apps/shared/components/settings/layout/SettingsShell.js +7 -5
  275. package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
  276. package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
  277. package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
  278. package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
  279. package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
  280. package/dist/apps/shared/gestures/useSwipe.js +90 -0
  281. package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
  282. package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
  283. package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
  284. package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
  285. package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
  286. package/dist/apps/shared/mail/folderCounts.js +12 -0
  287. package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
  288. package/dist/apps/shared/mail/newMailNotifications.js +3 -12
  289. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
  290. package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
  291. package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
  292. package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
  293. package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
  294. package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
  295. package/dist/apps/shared/mail/primaryMailbox.js +61 -0
  296. package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
  297. package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
  298. package/dist/apps/shared/mail/useMailConnection.js +3 -1
  299. package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
  300. package/dist/apps/shared/notifications/preferences.d.ts +13 -0
  301. package/dist/apps/shared/notifications/preferences.js +36 -0
  302. package/dist/apps/shared/notifications/store.d.ts +1 -0
  303. package/dist/apps/shared/notifications/store.js +7 -23
  304. package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
  305. package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
  306. package/dist/apps/shared/styles/app.css +396 -386
  307. package/dist/apps/www/_routes.js +1 -0
  308. package/dist/apps/www/calendar/index.js +78 -7
  309. package/dist/apps/www/contacts/index.js +3 -1
  310. package/dist/apps/www/index.js +548 -210
  311. package/dist/apps/www/messages/[uid].js +13 -5
  312. package/dist/apps/www/settings/profile/index.d.ts +4 -0
  313. package/dist/apps/www/settings/profile/index.js +124 -0
  314. package/package.json +2 -2
@@ -0,0 +1,591 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { ReactNode, useRef, useState } from "react";
6
+ import { HiOutlineBars3BottomLeft, HiOutlineBell, HiOutlineMapPin, HiOutlineUserGroup, HiOutlineVideoCamera, HiOutlineXMark } from "react-icons/hi2";
7
+ import { AttendeeRole, EventVisibility, RecurrenceRule, WeekdayCode } from "@rapidmx/react-shared/calendar/calendarApi.js";
8
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
9
+ import GuestInput from "./GuestInput.js";
10
+ import LazyDescriptionEditor from "./LazyDescriptionEditor.js";
11
+ import RecurrenceEditor from "./RecurrenceEditor.js";
12
+ import ResourcePicker from "./ResourcePicker.js";
13
+ import { EventFormController } from "./eventForm.js";
14
+ import { hasDescriptionText } from "./eventDialogFields.js";
15
+ import {
16
+ REMINDER_UNITS,
17
+ ReminderUnit,
18
+ RESPONSE_STATUS_LABEL,
19
+ VISIBILITIES,
20
+ VISIBILITY_HELP,
21
+ VISIBILITY_LABEL,
22
+ bestReminderUnit,
23
+ } from "./eventFormat.js";
24
+
25
+ export const INPUT_CLASS =
26
+ "w-full text-sm py-2 px-3 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
27
+ export const SELECT_CLASS =
28
+ "text-sm py-2 px-2 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
29
+ /** A text field that reads as plain text until it is hovered or focused, as a row of the quick-create popover does. */
30
+ const QUIET_INPUT_CLASS =
31
+ "w-full min-w-0 text-sm py-1.5 bg-transparent text-text border-0 border-b border-transparent hover:border-border focus:border-primary focus:outline-none placeholder:text-text-muted";
32
+ const ROW_ICON_CLASS = "shrink-0 text-text-muted mt-1.5";
33
+
34
+ const ATTENDEE_ROLES: AttendeeRole[] = ["required", "optional", "resource"];
35
+
36
+ /** A form row: an icon in the left gutter and its content. */
37
+ export function IconRow({ icon, children }: { icon: ReactNode; children: ReactNode }) {
38
+ return (
39
+ <div className="flex items-start gap-3">
40
+ <span aria-hidden="true" className={ROW_ICON_CLASS}>
41
+ {icon}
42
+ </span>
43
+ <div className="flex-1 min-w-0">{children}</div>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ /** The location row: a text box that is plain text until it is used. */
49
+ export function LocationRow({ c }: { c: EventFormController }) {
50
+ return (
51
+ <IconRow icon={<HiOutlineMapPin size={20} />}>
52
+ <input
53
+ type="text"
54
+ aria-label="Location"
55
+ placeholder="Add location"
56
+ className={QUIET_INPUT_CLASS}
57
+ value={c.values.location}
58
+ onChange={(e) => c.update({ location: e.target.value })}
59
+ />
60
+ </IconRow>
61
+ );
62
+ }
63
+
64
+ /** "Add video conferencing" and, once the event has a meeting, the button that joins it. */
65
+ export function VideoConferencingRow({ c }: { c: EventFormController }) {
66
+ const { values } = c;
67
+ return (
68
+ <IconRow icon={<HiOutlineVideoCamera size={20} />}>
69
+ <label className="flex items-center gap-2 text-sm py-1.5">
70
+ <input type="checkbox" checked={values.videoEnabled} onChange={(e) => c.update({ videoEnabled: e.target.checked })} />
71
+ Add video conferencing
72
+ </label>
73
+ {values.videoEnabled && (
74
+ <p className="text-xs text-text-muted mb-1">
75
+ Changing attendees after enabling video conferencing won&rsquo;t update meeting invitees &mdash; turn this off and back on to
76
+ reissue links to the current attendee list.
77
+ </p>
78
+ )}
79
+ {c.videoError && (
80
+ <p role="alert" className="text-xs text-danger mb-1">
81
+ {c.videoError}
82
+ </p>
83
+ )}
84
+ {c.videoMeetingUid && (
85
+ <div className="flex items-center gap-3 mb-1">
86
+ <Button
87
+ type="button"
88
+ variant="secondary"
89
+ className="!w-auto"
90
+ disabled={!c.joinUrl}
91
+ onClick={() => window.open(c.joinUrl, "_blank", "noopener,noreferrer")}
92
+ >
93
+ Join video call
94
+ </Button>
95
+ {!c.joinUrl && (
96
+ <span className="text-xs text-text-muted">
97
+ {c.organizerJoinUrl === undefined ? "Loading the join link…" : "This meeting’s join link isn’t available."}
98
+ </span>
99
+ )}
100
+ </div>
101
+ )}
102
+ </IconRow>
103
+ );
104
+ }
105
+
106
+ /**
107
+ * The guests field: compose's recipient field (`GuestInput`), which looks a name up among the contacts and the server's mailboxes and lists and
108
+ * turns what is picked, typed or pasted into chips - drawn in the field itself in the quick popover. In the full form the guests are rows with a
109
+ * role, the answer so far and a remove button instead (the field then draws only what is not an address), and rooms and equipment can be added.
110
+ */
111
+ export function GuestsField({ c, compact }: { c: EventFormController; compact: boolean }) {
112
+ const { values } = c;
113
+ const [pickerOpen, setPickerOpen] = useState(false);
114
+ const pickerAnchor = useRef<HTMLButtonElement>(null);
115
+
116
+ return (
117
+ <div className="flex flex-col gap-2">
118
+ <GuestInput
119
+ id="event-guests"
120
+ label="Add guests"
121
+ placeholder="Add guests"
122
+ variant={compact ? "quiet" : "box"}
123
+ listedElsewhere={!compact}
124
+ guests={values.attendees}
125
+ invalid={values.guestInvalid}
126
+ draft={values.guestDraft}
127
+ skipAddresses={[c.organizerAddress]}
128
+ mailboxUid={values.targetMailboxUid}
129
+ onChange={(next) => c.update({ attendees: next.guests, guestInvalid: next.invalid, guestDraft: next.draft })}
130
+ />
131
+ {compact ? null : (
132
+ <>
133
+ {values.attendees.length > 0 && (
134
+ <ul className="flex flex-col gap-2">
135
+ {values.attendees.map((attendee, i) => (
136
+ <li key={i} className="flex items-center gap-2">
137
+ <span
138
+ aria-hidden="true"
139
+ className="w-7 h-7 shrink-0 rounded-full bg-primary/15 text-primary-dark text-xs font-semibold flex items-center justify-center uppercase"
140
+ >
141
+ {(attendee.displayName || attendee.address).charAt(0)}
142
+ </span>
143
+ <div className="flex-1 min-w-0">
144
+ <div className="text-sm truncate">{attendee.displayName || attendee.address}</div>
145
+ <div className="text-xs text-text-muted truncate">
146
+ {attendee.displayName ? `${attendee.address} · ` : ""}
147
+ {RESPONSE_STATUS_LABEL[attendee.responseStatus]}
148
+ </div>
149
+ </div>
150
+ <select
151
+ className={SELECT_CLASS}
152
+ value={attendee.role}
153
+ onChange={(e) => c.updateAttendee(i, { role: e.target.value as AttendeeRole })}
154
+ aria-label={`Attendee role ${i + 1}`}
155
+ >
156
+ {ATTENDEE_ROLES.map((role) => (
157
+ <option key={role} value={role}>
158
+ {role}
159
+ </option>
160
+ ))}
161
+ </select>
162
+ <button
163
+ type="button"
164
+ onClick={() => c.removeAttendee(i)}
165
+ className="w-7 h-7 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text"
166
+ aria-label={`Remove attendee ${i + 1}`}
167
+ >
168
+ <HiOutlineXMark size={16} aria-hidden="true" />
169
+ </button>
170
+ </li>
171
+ ))}
172
+ </ul>
173
+ )}
174
+ <button
175
+ ref={pickerAnchor}
176
+ type="button"
177
+ onClick={() => setPickerOpen(true)}
178
+ className="self-start text-xs font-medium text-primary-dark hover:underline"
179
+ >
180
+ + Add room/equipment
181
+ </button>
182
+ {pickerOpen && (
183
+ <ResourcePicker
184
+ anchorRef={pickerAnchor}
185
+ onClose={() => setPickerOpen(false)}
186
+ onSelect={(mailbox) => {
187
+ c.addResource(mailbox);
188
+ setPickerOpen(false);
189
+ }}
190
+ excludeAddresses={c.guestAddresses}
191
+ />
192
+ )}
193
+ </>
194
+ )}
195
+ </div>
196
+ );
197
+ }
198
+
199
+ /** The guests row of the quick popover. */
200
+ export function GuestsRow({ c }: { c: EventFormController }) {
201
+ return (
202
+ <IconRow icon={<HiOutlineUserGroup size={20} />}>
203
+ <GuestsField c={c} compact />
204
+ </IconRow>
205
+ );
206
+ }
207
+
208
+ /** The reminder row: the one notification an event has - an amount, its unit and a way to remove it - or "Add notification". */
209
+ export function ReminderRow({ value, onChange }: { value: string; onChange: (minutes: string) => void }) {
210
+ const [active, setActive] = useState(value.trim() !== "");
211
+ const [unit, setUnit] = useState<ReminderUnit>(() => (value.trim() === "" ? "minutes" : bestReminderUnit(Number(value)).unit));
212
+ const factor = REMINDER_UNITS.find((u) => u.unit === unit)!.factor;
213
+ // What the amount box shows: the stored minutes in the chosen unit. A blank stays blank while the row is open.
214
+ const amount = value.trim() === "" ? "" : String(Math.round((Number(value) / factor) * 100) / 100);
215
+
216
+ function change(nextAmount: string, nextUnit: ReminderUnit) {
217
+ const nextFactor = REMINDER_UNITS.find((u) => u.unit === nextUnit)!.factor;
218
+ onChange(nextAmount.trim() === "" ? "" : String(Math.round(Number(nextAmount) * nextFactor)));
219
+ }
220
+
221
+ if (!active) {
222
+ return (
223
+ <IconRow icon={<HiOutlineBell size={20} />}>
224
+ <button
225
+ type="button"
226
+ className="text-sm text-text-muted hover:text-text py-1.5"
227
+ onClick={() => {
228
+ setActive(true);
229
+ setUnit("minutes");
230
+ onChange("30");
231
+ }}
232
+ >
233
+ Add notification
234
+ </button>
235
+ </IconRow>
236
+ );
237
+ }
238
+ return (
239
+ <IconRow icon={<HiOutlineBell size={20} />}>
240
+ <div className="flex items-center gap-2 text-sm">
241
+ <span>Notification</span>
242
+ <input
243
+ type="number"
244
+ min={0}
245
+ className={`${INPUT_CLASS} !w-20`}
246
+ aria-label="Reminder (minutes before)"
247
+ value={amount}
248
+ onChange={(e) => change(e.target.value, unit)}
249
+ />
250
+ <select
251
+ className={SELECT_CLASS}
252
+ aria-label="Reminder unit"
253
+ value={unit}
254
+ onChange={(e) => {
255
+ const nextUnit = e.target.value as ReminderUnit;
256
+ setUnit(nextUnit);
257
+ change(amount, nextUnit);
258
+ }}
259
+ >
260
+ {REMINDER_UNITS.map((u) => (
261
+ <option key={u.unit} value={u.unit}>
262
+ {u.unit}
263
+ </option>
264
+ ))}
265
+ </select>
266
+ <span className="text-text-muted">before</span>
267
+ <button
268
+ type="button"
269
+ aria-label="Remove notification"
270
+ className="ml-auto w-7 h-7 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text"
271
+ onClick={() => {
272
+ setActive(false);
273
+ onChange("");
274
+ }}
275
+ >
276
+ <HiOutlineXMark size={16} aria-hidden="true" />
277
+ </button>
278
+ </div>
279
+ </IconRow>
280
+ );
281
+ }
282
+
283
+ type RecurrencePresetKey = "none" | "daily" | "weekly" | "monthly" | "yearly" | "weekdays" | "custom";
284
+
285
+ const WEEKDAYS_ONLY: WeekdayCode[] = ["MO", "TU", "WE", "TH", "FR"];
286
+ const WEEKDAY_NAME: Record<WeekdayCode, string> = {
287
+ MO: "Monday",
288
+ TU: "Tuesday",
289
+ WE: "Wednesday",
290
+ TH: "Thursday",
291
+ FR: "Friday",
292
+ SA: "Saturday",
293
+ SU: "Sunday",
294
+ };
295
+
296
+ function presetRule(key: Exclude<RecurrencePresetKey, "none" | "custom">, startWeekday: WeekdayCode | undefined): RecurrenceRule {
297
+ switch (key) {
298
+ case "daily":
299
+ return { freq: "daily", interval: 1, exceptions: [] };
300
+ case "weekly":
301
+ return { freq: "weekly", interval: 1, byDay: [startWeekday ?? "MO"], exceptions: [] };
302
+ case "monthly":
303
+ return { freq: "monthly", interval: 1, exceptions: [] };
304
+ case "yearly":
305
+ return { freq: "yearly", interval: 1, exceptions: [] };
306
+ case "weekdays":
307
+ return { freq: "weekly", interval: 1, byDay: [...WEEKDAYS_ONLY], exceptions: [] };
308
+ }
309
+ }
310
+
311
+ /** Which menu entry a rule is: one of the plain repeats, or "custom" for anything with an interval, an end or other weekdays. */
312
+ function presetOf(rule: RecurrenceRule | null, startWeekday: WeekdayCode | undefined): RecurrencePresetKey {
313
+ if (!rule) {
314
+ return "none";
315
+ }
316
+ if (rule.interval !== 1 || rule.count || rule.until || rule.byMonthDay?.length || rule.byMonth?.length) {
317
+ return "custom";
318
+ }
319
+ if (rule.freq === "weekly") {
320
+ const days = [...(rule.byDay ?? [])].sort().join(",");
321
+ if (days === [...WEEKDAYS_ONLY].sort().join(",")) {
322
+ return "weekdays";
323
+ }
324
+ return days === (startWeekday ?? "MO") ? "weekly" : "custom";
325
+ }
326
+ return rule.byDay?.length ? "custom" : rule.freq;
327
+ }
328
+
329
+ /**
330
+ * "Does not repeat" and the plain repeats as a menu, with "Custom…" opening `RecurrenceEditor`'s full set (an interval, weekdays, an end).
331
+ * Choosing one of the plain repeats keeps the rule's skipped dates; the menu shows "Custom…" for any rule it has no plain entry for.
332
+ */
333
+ export function RecurrenceSelect({
334
+ value,
335
+ onChange,
336
+ allDay,
337
+ startWeekday,
338
+ }: {
339
+ value: RecurrenceRule | null;
340
+ onChange: (rule: RecurrenceRule | null) => void;
341
+ allDay: boolean;
342
+ startWeekday: WeekdayCode | undefined;
343
+ }) {
344
+ // Picking "Custom…" for a rule that is also a plain repeat still opens the editor.
345
+ const [customRequested, setCustomRequested] = useState(false);
346
+ const selected: RecurrencePresetKey = customRequested && value ? "custom" : presetOf(value, startWeekday);
347
+
348
+ function choose(key: RecurrencePresetKey) {
349
+ setCustomRequested(key === "custom");
350
+ if (key === "none") {
351
+ onChange(null);
352
+ } else if (key === "custom") {
353
+ onChange(value ?? presetRule("weekly", startWeekday));
354
+ } else {
355
+ onChange({ ...presetRule(key, startWeekday), exceptions: value?.exceptions ?? [] });
356
+ }
357
+ }
358
+
359
+ return (
360
+ <div className="flex flex-col gap-2">
361
+ <select
362
+ id="event-recurrence"
363
+ aria-label="Recurrence"
364
+ className={SELECT_CLASS}
365
+ value={selected}
366
+ onChange={(e) => choose(e.target.value as RecurrencePresetKey)}
367
+ >
368
+ <option value="none">Does not repeat</option>
369
+ <option value="daily">Daily</option>
370
+ <option value="weekly">{startWeekday ? `Weekly on ${WEEKDAY_NAME[startWeekday]}` : "Weekly"}</option>
371
+ <option value="monthly">Monthly</option>
372
+ <option value="yearly">Annually</option>
373
+ <option value="weekdays">Every weekday (Monday to Friday)</option>
374
+ <option value="custom">Custom…</option>
375
+ </select>
376
+ {selected === "custom" && <RecurrenceEditor value={value} onChange={onChange} allDay={allDay} startWeekday={startWeekday} hideToggle />}
377
+ </div>
378
+ );
379
+ }
380
+
381
+ /** [start date] [start time] to [end time] [end date], then "All day", the "Time zone" link (and its menu) and how the event repeats. */
382
+ export function DateTimeControls({ c }: { c: EventFormController }) {
383
+ const { values } = c;
384
+ const [zoneOpen, setZoneOpen] = useState(false);
385
+ return (
386
+ <div className="flex flex-col gap-3">
387
+ <div className="flex flex-wrap items-center gap-2 text-sm">
388
+ <input
389
+ type="date"
390
+ aria-label="Event start date"
391
+ className={`${SELECT_CLASS} min-w-0`}
392
+ value={values.start.slice(0, 10)}
393
+ onChange={(e) => c.setStart(`${e.target.value}${values.start.slice(10)}`)}
394
+ />
395
+ {!values.allDay && (
396
+ <input
397
+ type="time"
398
+ aria-label="Event start time"
399
+ className={`${SELECT_CLASS} min-w-0`}
400
+ value={values.start.slice(11, 16)}
401
+ onChange={(e) => c.setStart(`${values.start.slice(0, 11)}${e.target.value}`)}
402
+ />
403
+ )}
404
+ <span className="text-text-muted">to</span>
405
+ {!values.allDay && (
406
+ <input
407
+ type="time"
408
+ aria-label="Event end time"
409
+ className={`${SELECT_CLASS} min-w-0`}
410
+ value={values.end.slice(11, 16)}
411
+ onChange={(e) => c.setEnd(`${values.end.slice(0, 11)}${e.target.value}`)}
412
+ />
413
+ )}
414
+ <input
415
+ type="date"
416
+ aria-label="Event end date"
417
+ className={`${SELECT_CLASS} min-w-0`}
418
+ value={values.end.slice(0, 10)}
419
+ onChange={(e) => c.setEnd(`${e.target.value}${values.end.slice(10)}`)}
420
+ />
421
+ </div>
422
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm">
423
+ <label className="flex items-center gap-2">
424
+ <input type="checkbox" checked={values.allDay} onChange={(e) => c.onAllDayChange(e.target.checked)} />
425
+ All day
426
+ </label>
427
+ <button
428
+ type="button"
429
+ aria-expanded={zoneOpen}
430
+ className="text-primary-dark hover:underline"
431
+ onClick={() => setZoneOpen((open) => !open)}
432
+ >
433
+ Time zone
434
+ </button>
435
+ {!c.editingSingleOccurrence && (
436
+ <RecurrenceSelect value={values.recurrenceRule} onChange={(rule) => c.update({ recurrenceRule: rule })} allDay={values.allDay} startWeekday={c.startWeekday} />
437
+ )}
438
+ </div>
439
+ {zoneOpen && (
440
+ <select
441
+ aria-label="Time zone"
442
+ className={`${SELECT_CLASS} self-start max-w-full`}
443
+ value={values.formZone}
444
+ onChange={(e) => c.onTimeZoneChange(e.target.value)}
445
+ >
446
+ {c.timeZones.map((zone) => (
447
+ <option key={zone} value={zone}>
448
+ {zone}
449
+ </option>
450
+ ))}
451
+ </select>
452
+ )}
453
+ </div>
454
+ );
455
+ }
456
+
457
+ /** The calendar this event is in: its colour and name, a choice between calendars (and mailboxes) when an event is being created. */
458
+ export function CalendarField({ c }: { c: EventFormController }) {
459
+ const { values } = c;
460
+ const mailboxChoices = !c.occurrence && c.mailboxOptions && c.mailboxOptions.length > 1 ? c.mailboxOptions : undefined;
461
+ const calendarChoices = !c.occurrence && c.calendarChoices && c.calendarChoices.length > 1 ? c.calendarChoices : undefined;
462
+ return (
463
+ <div className="flex flex-col gap-2">
464
+ {mailboxChoices && (
465
+ <select
466
+ aria-label="Mailbox"
467
+ className={`${SELECT_CLASS} self-start max-w-full`}
468
+ value={values.targetMailboxUid}
469
+ onChange={(e) => c.onMailboxChange(e.target.value)}
470
+ >
471
+ {mailboxChoices.map(({ mailbox }) => (
472
+ <option key={mailbox.uid} value={mailbox.uid}>
473
+ {mailbox.displayName}
474
+ {mailbox.ownerUserUid ? "" : " (shared)"}
475
+ </option>
476
+ ))}
477
+ </select>
478
+ )}
479
+ <div className="flex items-center gap-2 text-sm">
480
+ <span aria-hidden="true" className="w-3 h-3 rounded-full shrink-0" style={{ backgroundColor: c.calendarColor }} />
481
+ {calendarChoices ? (
482
+ <select
483
+ aria-label="Calendar"
484
+ className={`${SELECT_CLASS} min-w-0 max-w-full`}
485
+ value={values.targetFolderUid}
486
+ onChange={(e) => c.update({ targetFolderUid: e.target.value })}
487
+ >
488
+ {calendarChoices.map((cal) => (
489
+ <option key={cal.uid} value={cal.uid}>
490
+ {cal.name}
491
+ </option>
492
+ ))}
493
+ </select>
494
+ ) : (
495
+ <span className="truncate">{c.calendarName}</span>
496
+ )}
497
+ </div>
498
+ </div>
499
+ );
500
+ }
501
+
502
+ /**
503
+ * The description row: the rich-text box (`DescriptionEditor`). In the full card it is always there; in the quick-create popover (`collapsible`) it is an
504
+ * "Add description" row that opens the same box in place, as Google Calendar does - and stays open once there is text in it.
505
+ */
506
+ export function DescriptionRow({ c, collapsible }: { c: EventFormController; collapsible: boolean }) {
507
+ const [opened, setOpened] = useState(false);
508
+ if (collapsible && !opened && !hasDescriptionText(c.values.descriptionHtml)) {
509
+ return (
510
+ <IconRow icon={<HiOutlineBars3BottomLeft size={20} />}>
511
+ <button type="button" className="text-sm text-text-muted hover:text-text py-1.5" onClick={() => setOpened(true)}>
512
+ Add description
513
+ </button>
514
+ </IconRow>
515
+ );
516
+ }
517
+ return (
518
+ <IconRow icon={<HiOutlineBars3BottomLeft size={20} />}>
519
+ <LazyDescriptionEditor value={c.values.descriptionHtml} onChange={(html) => c.update({ descriptionHtml: html })} autoFocus={collapsible && opened} />
520
+ </IconRow>
521
+ );
522
+ }
523
+
524
+ /** Who may see the event's details, with what each choice means underneath. */
525
+ export function VisibilitySelect({ c }: { c: EventFormController }) {
526
+ const { visibility } = c.values;
527
+ return (
528
+ <div className="flex flex-col gap-1">
529
+ <select
530
+ aria-label="Visibility"
531
+ aria-describedby="event-visibility-help"
532
+ className={`${SELECT_CLASS} !w-auto self-start`}
533
+ value={visibility}
534
+ onChange={(e) => c.update({ visibility: e.target.value as EventVisibility })}
535
+ >
536
+ {VISIBILITIES.map((option) => (
537
+ <option key={option} value={option}>
538
+ {VISIBILITY_LABEL[option]}
539
+ </option>
540
+ ))}
541
+ </select>
542
+ <p id="event-visibility-help" className="text-xs text-text-muted">
543
+ {VISIBILITY_HELP[visibility]}
544
+ </p>
545
+ </div>
546
+ );
547
+ }
548
+
549
+ const GUEST_PERMISSIONS = [
550
+ {
551
+ key: "guestsCanModify",
552
+ label: "Modify event",
553
+ help: "Guests can ask you to change the title, location, description or time. What they ask for is sent to you and applied automatically when this is on.",
554
+ },
555
+ {
556
+ key: "guestsCanInviteOthers",
557
+ label: "Invite others",
558
+ help: "Guests can ask you to add more guests; those are added automatically when this is on.",
559
+ },
560
+ {
561
+ key: "guestsCanSeeGuestList",
562
+ label: "See guest list",
563
+ help: "When this is off, each guest is sent an invitation that names only themselves and you.",
564
+ },
565
+ ] as const;
566
+
567
+ /** What guests may do with the event: ask for it to change, ask for guests to be added, see who else is invited. Only the organizer sets these. */
568
+ export function GuestPermissions({ c }: { c: EventFormController }) {
569
+ return (
570
+ <fieldset className="flex flex-col gap-2 min-w-0">
571
+ <legend className="text-sm font-semibold mb-1">Guest permissions</legend>
572
+ {GUEST_PERMISSIONS.map(({ key, label, help }) => (
573
+ <div key={key}>
574
+ <label className="flex items-center gap-2 text-sm">
575
+ <input
576
+ type="checkbox"
577
+ aria-describedby={`event-${key}-help`}
578
+ checked={c.values[key]}
579
+ onChange={(e) => c.update({ [key]: e.target.checked })}
580
+ />
581
+ {label}
582
+ </label>
583
+ <p id={`event-${key}-help`} className="text-xs text-text-muted pl-6">
584
+ {help}
585
+ </p>
586
+ </div>
587
+ ))}
588
+ </fieldset>
589
+ );
590
+ }
591
+