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