@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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 2 (`specs/search.md` §6) - the local encrypted index's public entry point, mirroring
|
|
3
|
+
* `@rapidmx/react-shared`'s `searchTier3.ts#searchEncryptedCandidates()` in shape: given a parsed query
|
|
4
|
+
* and this session's unlocked keys, return already-scored, already-sourced results plus how much of the
|
|
5
|
+
* mailbox the local index actually covers right now.
|
|
6
|
+
*
|
|
7
|
+
* Returns `{ results: [], coverage: undefined }` - never throws, never rejects - both when `unlocked` is
|
|
8
|
+
* absent (nothing is indexed without a master key to derive the index key from, the same "silently
|
|
9
|
+
* contributes nothing" degradation `searchTier3.ts`'s own doc comment describes for its identical case)
|
|
10
|
+
* and when anything about the local index itself fails (Worker/WASM/OPFS unsupported or unavailable in
|
|
11
|
+
* this browser, a corrupted index mid-rebuild, a query timeout). This tier's own storage engine is
|
|
12
|
+
* strictly best-effort infrastructure Tier 1 doesn't depend on - a caller `Promise.all()`-ing this
|
|
13
|
+
* alongside Tier 1/Tier 3 (`apps/www/index.tsx`'s own search orchestration) must never see the *whole
|
|
14
|
+
* search* fail just because this one, most novel piece had a bad moment.
|
|
15
|
+
*/
|
|
16
|
+
import type { UnlockedKeys } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
17
|
+
import type { ParsedSearchQuery } from "@rapidmx/react-shared/search/queryGrammar.js";
|
|
18
|
+
import type { SearchResult } from "@rapidmx/react-shared/search/searchApi.js";
|
|
19
|
+
import type { Coverage } from "./localIndexWorker.js";
|
|
20
|
+
export interface Tier2SearchOutcome {
|
|
21
|
+
results: SearchResult[];
|
|
22
|
+
/** `undefined` when `unlocked` was absent - there's no local index to report coverage for at all in
|
|
23
|
+
* that case, distinct from a real, empty (just-built) index. */
|
|
24
|
+
coverage?: Coverage;
|
|
25
|
+
/** `true` when at least one more match exists beyond `results` - §8's composite pagination cursor's
|
|
26
|
+
* own per-tier continuation signal. Always `false` alongside the `unlocked`-absent/error
|
|
27
|
+
* degradations below - there is nothing more to page through either way. */
|
|
28
|
+
hasMore: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function searchLocalIndex(mailboxUid: string, parsed: ParsedSearchQuery, unlocked: UnlockedKeys | undefined, limit?: number, offset?: number): Promise<Tier2SearchOutcome>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from "react";
|
|
2
|
+
import { Branding } from "@rapidmx/react-shared/branding/brandingApi.js";
|
|
3
|
+
export interface LayoutProps {
|
|
4
|
+
/**
|
|
5
|
+
* Supplied by `WwwRoute`'s `fetchProps()` override (`src/mongo/routes/wwwRoute.ts`/
|
|
6
|
+
* `src/sql/routes/wwwRoute.ts`) - `@rapidrest/react` spreads the same merged page props onto this
|
|
7
|
+
* layout as onto the page it wraps, so it renders correctly server-side on the very first byte of the
|
|
8
|
+
* response: no client-side flash from the stock defaults below, and a crawler reading the raw HTML
|
|
9
|
+
* sees the real branding too. `undefined` only on the framework's unwrapped `_500` fallback path,
|
|
10
|
+
* which doesn't receive props at all - falling back to the stock defaults there is acceptable.
|
|
11
|
+
*/
|
|
12
|
+
branding?: Branding;
|
|
13
|
+
}
|
|
14
|
+
export default function Layout({ children, branding }: PropsWithChildren<LayoutProps>): React.JSX.Element;
|
|
@@ -3,10 +3,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
-
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { DndContext, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
8
8
|
import { HiOutlineBars3 } from "react-icons/hi2";
|
|
9
|
-
import { addDays, addMonths, addWeeks, eachDayOfInterval, endOfDay, endOfMonth, endOfWeek, format, startOfDay, startOfMonth, startOfWeek, } from "date-fns";
|
|
9
|
+
import { addDays, addMonths, addWeeks, eachDayOfInterval, endOfDay, endOfMonth, endOfWeek, format, parseISO, startOfDay, startOfMonth, startOfWeek, } from "date-fns";
|
|
10
10
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
11
11
|
import { listCalendarEvents } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
12
12
|
import { moveOccurrence, resizeOccurrenceEnd } from "@rapidmx/react-shared/calendar/calendarMutations.js";
|
|
@@ -16,6 +16,7 @@ import { expandAllOccurrences } from "@rapidmx/react-shared/calendar/recurrence.
|
|
|
16
16
|
import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
|
|
17
17
|
import CalendarShell, { useCalendarShell } from "../../shared/components/calendar/layout/CalendarShell.js";
|
|
18
18
|
import CalendarListSidebar from "../../shared/components/calendar/CalendarListSidebar.js";
|
|
19
|
+
import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
|
|
19
20
|
import EventModal from "../../shared/components/calendar/EventModal.js";
|
|
20
21
|
import MiniDatePicker from "@rapidmx/react-shared/components/pickers/MiniDatePicker.js";
|
|
21
22
|
import MonthView from "../../shared/components/calendar/MonthView.js";
|
|
@@ -24,11 +25,11 @@ import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
|
|
|
24
25
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
25
26
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
26
27
|
export default function CalendarPage(props) {
|
|
27
|
-
return (_jsx(CalendarShell, { ...props, children: _jsx(CalendarContent, {}) }));
|
|
28
|
+
return (_jsx(CalendarShell, { ...props, children: _jsx(CalendarContent, { userUid: props.userUid }) }));
|
|
28
29
|
}
|
|
29
30
|
const VIEW_TYPES = ["month", "week", "workWeek", "day", "split"];
|
|
30
31
|
const VIEW_LABELS = { month: "Month", week: "Week", workWeek: "Work Week", day: "Day", split: "Split" };
|
|
31
|
-
function CalendarContent() {
|
|
32
|
+
function CalendarContent({ userUid }) {
|
|
32
33
|
const { mailboxUid, folderUid, calendarFolders, mailboxCalendars, mailboxes, reloadFolders, colorFor } = useCalendarShell();
|
|
33
34
|
// `PointerSensor` alone activates a drag on the very first touch-move, indistinguishable from a
|
|
34
35
|
// scroll gesture on a touch device. `MouseSensor` (a small `distance` — desktop drags still start
|
|
@@ -55,7 +56,9 @@ function CalendarContent() {
|
|
|
55
56
|
setView(requestedView);
|
|
56
57
|
}
|
|
57
58
|
const requestedDate = params.get("date");
|
|
58
|
-
|
|
59
|
+
// `parseISO()`, not `new Date()` - `?date=2026-06-16` is a local calendar day, but `new Date()` reads a
|
|
60
|
+
// date-only string as UTC midnight, i.e. the previous day anywhere west of UTC.
|
|
61
|
+
const parsedDate = requestedDate ? parseISO(requestedDate) : null;
|
|
59
62
|
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
|
60
63
|
setViewDate(startOfDay(parsedDate));
|
|
61
64
|
}
|
|
@@ -100,7 +103,13 @@ function CalendarContent() {
|
|
|
100
103
|
const end = endOfDay(viewDate);
|
|
101
104
|
return { rangeStart: start, rangeEnd: end, days: [start] };
|
|
102
105
|
}, [view, viewDate]);
|
|
106
|
+
// Only the most recent reload() may apply its results - toggling calendars (or saving/dragging an event)
|
|
107
|
+
// while an earlier, slower fetch is still in flight must not let that stale response overwrite newer
|
|
108
|
+
// events. (No date range is passed: `listCalendarEvents()` deliberately fetches the whole calendar and
|
|
109
|
+
// filters client-side - see its own doc comment on the server-side range query it works around.)
|
|
110
|
+
const reloadSeqRef = useRef(0);
|
|
103
111
|
function reload() {
|
|
112
|
+
const seq = ++reloadSeqRef.current;
|
|
104
113
|
const targets = calendarFolders.filter((f) => checkedFolderUids.has(f.uid));
|
|
105
114
|
if (targets.length === 0) {
|
|
106
115
|
setEvents([]);
|
|
@@ -111,6 +120,9 @@ function CalendarContent() {
|
|
|
111
120
|
setError(null);
|
|
112
121
|
void Promise.allSettled(targets.map((f) => listCalendarEvents(f.uid)))
|
|
113
122
|
.then((results) => {
|
|
123
|
+
if (seq !== reloadSeqRef.current) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
114
126
|
const loaded = [];
|
|
115
127
|
const failures = [];
|
|
116
128
|
results.forEach((result, i) => {
|
|
@@ -137,7 +149,11 @@ function CalendarContent() {
|
|
|
137
149
|
setError(`Could not load events for: ${failures.map((f) => f.name).join(", ")}.`);
|
|
138
150
|
}
|
|
139
151
|
})
|
|
140
|
-
.finally(() =>
|
|
152
|
+
.finally(() => {
|
|
153
|
+
if (seq === reloadSeqRef.current) {
|
|
154
|
+
setLoading(false);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
141
157
|
}
|
|
142
158
|
// Keyed on stable string summaries (not the array/set objects themselves, which are new references
|
|
143
159
|
// every render) so this only re-fires when the actual set of folders to fetch changes.
|
|
@@ -215,10 +231,12 @@ function CalendarContent() {
|
|
|
215
231
|
const modalFolderUid = modal ? (modal.targetFolderUid ?? modal.occurrence?.folderUid ?? folderUid) : undefined;
|
|
216
232
|
const modalMailboxUid = calendarFolders.find((f) => f.uid === modalFolderUid)?.mailboxUid ?? mailboxUid;
|
|
217
233
|
const modalMailbox = mailboxes.find((mb) => mb.uid === modalMailboxUid);
|
|
218
|
-
// Mailboxes a new event can be created in - only those with at least one calendar
|
|
234
|
+
// Mailboxes a new event can be created in - only those with at least one calendar that the caller can
|
|
235
|
+
// write to (a view-only share is left out; see writableMailboxes.ts).
|
|
236
|
+
const writableMailboxes = useWritableMailboxes(mailboxes, userUid, modalMailboxUid);
|
|
219
237
|
const mailboxOptions = useMemo(() => mailboxCalendars
|
|
220
|
-
.filter((mc) => mc.calendarFolders.length > 0)
|
|
221
|
-
.map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })), [mailboxCalendars]);
|
|
238
|
+
.filter((mc) => mc.calendarFolders.length > 0 && writableMailboxes.some((mb) => mb.uid === mc.mailbox.uid))
|
|
239
|
+
.map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })), [mailboxCalendars, writableMailboxes]);
|
|
222
240
|
const sidebarContent = (_jsxs(_Fragment, { children: [_jsx(MiniDatePicker, { selected: viewDate, onSelect: (date) => setViewDate(startOfDay(date)) }), _jsx(CalendarListSidebar, { mailboxCalendars: mailboxCalendars, checkedFolderUids: checkedFolderUids, onToggle: toggleCalendar, onAddCalendar: handleAddCalendar, colorFor: colorFor })] }));
|
|
223
241
|
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx("div", { className: "hidden md:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto", children: sidebarContent }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Calendars", children: _jsx("div", { className: "flex flex-col", children: sidebarContent }) }), _jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [_jsxs("div", { className: "flex items-center gap-3 p-3 border-b border-border shrink-0", children: [_jsx("button", { type: "button", className: "md:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0", "aria-label": "Open calendars", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx(Button, { type: "button", onClick: () => openNewEvent(), className: "!w-auto shrink-0", children: "+ New event" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => shiftView(-1), "aria-label": "Previous", className: "w-7 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "\u2039" }), _jsx("button", { type: "button", onClick: goToday, className: "px-2 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "Today" }), _jsx("button", { type: "button", onClick: () => shiftView(1), "aria-label": "Next", className: "w-7 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "\u203A" })] }), _jsx("h1", { className: "hidden md:block text-lg font-bold tracking-tight", children: title }), _jsx("div", { className: "ml-auto flex gap-1 overflow-x-auto", children: VIEW_TYPES.map((v) => (_jsx("button", { type: "button", onClick: () => setView(v), className: [
|
|
224
242
|
"px-3 h-7 text-sm rounded-sm whitespace-nowrap",
|
|
@@ -226,5 +244,5 @@ function CalendarContent() {
|
|
|
226
244
|
(v === "workWeek" || v === "split") && "hidden md:inline-block",
|
|
227
245
|
]
|
|
228
246
|
.filter(Boolean)
|
|
229
|
-
.join(" "), children: VIEW_LABELS[v] }, v))) })] }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), actionError && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: actionError }) })), loading ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : (_jsx(DndContext, { sensors: sensors, onDragEnd: handleDragEnd, children: view === "month" ? (_jsx(MonthView, { viewDate: viewDate, occurrences: occurrences, folderColors: folderColors, onSelectDay: handleSelectDay, onSelectEvent: openEvent })) : view === "split" ? (_jsx(SplitDayView, { day: viewDate, columns: splitColumns, occurrences: occurrences, onSelectEvent: openEvent, onSelectSlot: (start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid) })) : (_jsx(TimeGridView, { days: days, occurrences: occurrences, folderColors: folderColors, onSelectEvent: openEvent, onSelectSlot: openNewEvent })) })), modal && modalMailbox && (_jsx(EventModal, { open: true, onClose: closeModal, mailboxUid: modalMailbox.uid, folderUid: modalFolderUid, calendars: calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name })), mailboxOptions: mailboxOptions, organizerAddress: modalMailbox.primarySmtpAddress, occurrence: modal.occurrence, initialStart: modal.initialStart, initialEnd: modal.initialEnd, onSaved: handleSaved, onDeleted: handleDeleted }))] })] }));
|
|
247
|
+
.join(" "), children: VIEW_LABELS[v] }, v))) })] }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), actionError && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: actionError }) })), loading ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : (_jsx(DndContext, { sensors: sensors, onDragEnd: handleDragEnd, children: view === "month" ? (_jsx(MonthView, { viewDate: viewDate, occurrences: occurrences, folderColors: folderColors, onSelectDay: handleSelectDay, onSelectEvent: openEvent })) : view === "split" ? (_jsx(SplitDayView, { day: viewDate, columns: splitColumns, occurrences: occurrences, onSelectEvent: openEvent, onSelectSlot: (start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid) })) : (_jsx(TimeGridView, { days: days, occurrences: occurrences, folderColors: folderColors, onSelectEvent: openEvent, onSelectSlot: openNewEvent })) })), modal && modalMailbox && (_jsx(EventModal, { open: true, onClose: closeModal, mailboxUid: modalMailbox.uid, folderUid: modalFolderUid, calendars: calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name })), mailboxOptions: mailboxOptions, organizerAddress: modalMailbox.primarySmtpAddress, organizerAliases: modalMailbox.aliasAddresses, occurrence: modal.occurrence, initialStart: modal.initialStart, initialEnd: modal.initialEnd, onSaved: handleSaved, onDeleted: handleDeleted }))] })] }));
|
|
230
248
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
|
|
3
|
+
/**
|
|
4
|
+
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
|
|
5
|
+
* existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
|
|
6
|
+
* equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
|
|
7
|
+
* in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
|
|
8
|
+
*/
|
|
9
|
+
export default function ContactDetailPage(props: ContactsShellProps & {
|
|
10
|
+
params: {
|
|
11
|
+
uid: string;
|
|
12
|
+
};
|
|
13
|
+
}): React.JSX.Element;
|
|
@@ -10,6 +10,7 @@ import ContactsShell from "../../shared/components/contacts/layout/ContactsShell
|
|
|
10
10
|
import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
|
|
11
11
|
import ContactForm from "../../shared/components/contacts/ContactForm.js";
|
|
12
12
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
13
|
+
import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
|
|
13
14
|
/**
|
|
14
15
|
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
|
|
15
16
|
* existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
|
|
@@ -23,6 +24,9 @@ function ContactDetailContent({ uid }) {
|
|
|
23
24
|
const [contact, setContact] = useState(null);
|
|
24
25
|
const [loading, setLoading] = useState(true);
|
|
25
26
|
const [error, setError] = useState(null);
|
|
27
|
+
// Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
|
|
28
|
+
// reason) rather than replacing the whole page with an error as if it had never loaded.
|
|
29
|
+
const [deleteError, setDeleteError] = useState(null);
|
|
26
30
|
const [mode, setMode] = useState("view");
|
|
27
31
|
useEffect(() => {
|
|
28
32
|
setLoading(true);
|
|
@@ -37,14 +41,21 @@ function ContactDetailContent({ uid }) {
|
|
|
37
41
|
// rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
|
|
38
42
|
// never runs with a stale/absent contact — no redundant null check needed here.
|
|
39
43
|
async function handleDelete(contact) {
|
|
44
|
+
setDeleteError(null);
|
|
40
45
|
try {
|
|
41
46
|
await deleteContact(contact.uid, contact.version);
|
|
47
|
+
// A deleted contact's pinned signing keys must stop vouching for signatures.
|
|
48
|
+
clearPinnedSignerCache();
|
|
42
49
|
window.location.href = "/contacts";
|
|
43
50
|
}
|
|
44
51
|
catch (err) {
|
|
45
|
-
|
|
52
|
+
setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this contact.");
|
|
46
53
|
}
|
|
47
54
|
}
|
|
55
|
+
// After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
|
|
56
|
+
function handleKeysChanged() {
|
|
57
|
+
getContact(uid).then(setContact, () => undefined);
|
|
58
|
+
}
|
|
48
59
|
if (loading) {
|
|
49
60
|
return _jsx("p", { className: "p-8 text-sm text-text-muted", children: "Loading\u2026" });
|
|
50
61
|
}
|
|
@@ -58,5 +69,5 @@ function ContactDetailContent({ uid }) {
|
|
|
58
69
|
setMode("view");
|
|
59
70
|
}, onCancel: () => setMode("view") })] }));
|
|
60
71
|
}
|
|
61
|
-
return (
|
|
72
|
+
return (_jsxs("div", { className: "p-6", children: [deleteError && _jsx(Alert, { children: deleteError }), _jsx(ContactDetailPane, { contact: contact, onEdit: () => setMode("edit"), onDelete: () => handleDelete(contact), backHref: backHref, onKeysChanged: handleKeysChanged })] }));
|
|
62
73
|
}
|
|
@@ -15,25 +15,40 @@ import ContactsToolbar from "../../shared/components/contacts/ContactsToolbar.js
|
|
|
15
15
|
import ContactAvatar from "@rapidmx/react-shared/components/avatar/ContactAvatar.js";
|
|
16
16
|
import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
|
|
17
17
|
import ContactForm from "../../shared/components/contacts/ContactForm.js";
|
|
18
|
+
import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
|
|
19
|
+
import { getMyMailboxAccess } from "@rapidmx/react-shared/mail/mailboxAccessApi.js";
|
|
18
20
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
21
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
22
|
+
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
23
|
+
import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/listAllPages.js";
|
|
24
|
+
import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
|
|
19
25
|
const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
20
26
|
export default function ContactsPage(props) {
|
|
21
|
-
return (_jsx(ContactsShell, { ...props, children: _jsx(ContactsContent, {}) }));
|
|
27
|
+
return (_jsx(ContactsShell, { ...props, children: _jsx(ContactsContent, { userUid: props.userUid }) }));
|
|
22
28
|
}
|
|
29
|
+
/** Shown in the Deleted view when the server answered `?deleted=true` with live contacts (see `canViewDeleted`). */
|
|
30
|
+
const DELETED_NOT_PERMITTED_MESSAGE = "You don't have permission to view deleted contacts in this folder.";
|
|
23
31
|
function primaryInfo(contact) {
|
|
24
32
|
return contact.emails[0]?.address ?? contact.phones[0]?.phoneNumber ?? "";
|
|
25
33
|
}
|
|
34
|
+
/** How long an export's object URL is kept alive after the click - some browsers are still reading the
|
|
35
|
+
* blob after `click()` returns, so revoking it synchronously can cancel the download. */
|
|
36
|
+
const VCARD_DOWNLOAD_URL_LIFETIME_MS = 60000;
|
|
26
37
|
function downloadTextFile(filename, content) {
|
|
27
38
|
const blob = new Blob([content], { type: "text/vcard" });
|
|
28
39
|
const url = URL.createObjectURL(blob);
|
|
29
40
|
const a = document.createElement("a");
|
|
30
41
|
a.href = url;
|
|
31
42
|
a.download = filename;
|
|
43
|
+
document.body.appendChild(a);
|
|
32
44
|
a.click();
|
|
33
|
-
|
|
45
|
+
a.remove();
|
|
46
|
+
setTimeout(() => URL.revokeObjectURL(url), VCARD_DOWNLOAD_URL_LIFETIME_MS);
|
|
34
47
|
}
|
|
35
|
-
function ContactsContent() {
|
|
48
|
+
function ContactsContent({ userUid }) {
|
|
36
49
|
const { folderUid, mailboxUid, mailboxes } = useContactsShell();
|
|
50
|
+
// The new-contact form's Mailbox choices - view-only shares are left out (see writableMailboxes.ts).
|
|
51
|
+
const writableMailboxes = useWritableMailboxes(mailboxes, userUid, mailboxUid);
|
|
37
52
|
const { openCompose } = useCompose();
|
|
38
53
|
const isMobile = useIsMobile();
|
|
39
54
|
const [contacts, setContacts] = useState([]);
|
|
@@ -42,6 +57,9 @@ function ContactsContent() {
|
|
|
42
57
|
const [deletedError, setDeletedError] = useState(null);
|
|
43
58
|
const [loading, setLoading] = useState(true);
|
|
44
59
|
const [error, setError] = useState(null);
|
|
60
|
+
// Set when `listAllPages()` stopped at its page cap - the list shown isn't every contact.
|
|
61
|
+
const [truncated, setTruncated] = useState(false);
|
|
62
|
+
const [deletedTruncated, setDeletedTruncated] = useState(false);
|
|
45
63
|
const [query, setQuery] = useState("");
|
|
46
64
|
const [view, setView] = useState({ type: "all" });
|
|
47
65
|
const [sortColumn, setSortColumn] = useState("name");
|
|
@@ -51,6 +69,7 @@ function ContactsContent() {
|
|
|
51
69
|
const [mode, setMode] = useState("view");
|
|
52
70
|
// Set after a contact is created in a different mailbox than the one this list shows.
|
|
53
71
|
const [savedElsewhere, setSavedElsewhere] = useState(null);
|
|
72
|
+
const [confirmingBulkDelete, setConfirmingBulkDelete] = useState(false);
|
|
54
73
|
/** Returns a promise so bulk actions (below) can wait for the refreshed list before re-asserting their
|
|
55
74
|
* own error message — this always clears `error` first (a legitimate reset for a fresh fetch attempt),
|
|
56
75
|
* which would otherwise silently wipe out a bulk action's own just-set failure message before the user
|
|
@@ -58,27 +77,69 @@ function ContactsContent() {
|
|
|
58
77
|
function reload() {
|
|
59
78
|
if (!folderUid) {
|
|
60
79
|
setContacts([]);
|
|
80
|
+
setTruncated(false);
|
|
61
81
|
setLoading(false);
|
|
62
82
|
return Promise.resolve();
|
|
63
83
|
}
|
|
64
84
|
setLoading(true);
|
|
65
85
|
setError(null);
|
|
66
|
-
return listContacts(folderUid, { limit:
|
|
67
|
-
.then(
|
|
86
|
+
return listAllPages((page) => listContacts(folderUid, { limit: LIST_PAGE_SIZE, page }))
|
|
87
|
+
.then((result) => {
|
|
88
|
+
setContacts(result.items);
|
|
89
|
+
setTruncated(result.truncated);
|
|
90
|
+
})
|
|
68
91
|
.catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load contacts."))
|
|
69
92
|
.finally(() => setLoading(false));
|
|
70
93
|
}
|
|
71
94
|
useEffect(() => {
|
|
72
95
|
void reload();
|
|
73
96
|
}, [folderUid]);
|
|
97
|
+
// restapi only honors `?deleted=true` for a caller with both delete and update rights on the contacts *folder*
|
|
98
|
+
// (its ACL, which inherits the mailbox's but can carry its own records) - anyone else silently gets the *live*
|
|
99
|
+
// contacts back, which the Deleted view would then present as deleted. There is no client API for the caller's
|
|
100
|
+
// access to a folder, so the mailbox-level check below is only a first gate (an owner always passes; anyone else
|
|
101
|
+
// asks the server; unknown - still loading, or the check failed - hides the view), and the Deleted view's own
|
|
102
|
+
// load below detects the server ignoring the filter. `ContactsShell` only renders this with a resolved mailbox,
|
|
103
|
+
// and switching mailboxes is a full page load, so `mailboxUid` never changes under this component.
|
|
104
|
+
const ownsMailbox = mailboxes.some((mb) => mb.uid === mailboxUid && mb.ownerUserUid !== undefined && mb.ownerUserUid === userUid);
|
|
105
|
+
const [delegateCanViewDeleted, setDelegateCanViewDeleted] = useState(false);
|
|
106
|
+
// Resolving a contact's key change needs update rights on the mailbox - `undefined` while unknown.
|
|
107
|
+
const [delegateCanUpdate, setDelegateCanUpdate] = useState(undefined);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (ownsMailbox) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let cancelled = false;
|
|
113
|
+
getMyMailboxAccess(mailboxUid).then((access) => {
|
|
114
|
+
if (!cancelled) {
|
|
115
|
+
setDelegateCanViewDeleted(access.canDelete && access.canUpdate);
|
|
116
|
+
setDelegateCanUpdate(access.canUpdate);
|
|
117
|
+
}
|
|
118
|
+
}, () => undefined);
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
};
|
|
122
|
+
}, [ownsMailbox]);
|
|
123
|
+
const canViewDeleted = ownsMailbox || delegateCanViewDeleted;
|
|
74
124
|
useEffect(() => {
|
|
75
125
|
if (view.type !== "deleted" || !folderUid) {
|
|
76
126
|
return;
|
|
77
127
|
}
|
|
78
128
|
setDeletedLoading(true);
|
|
79
129
|
setDeletedError(null);
|
|
80
|
-
listDeletedContacts(folderUid, { limit:
|
|
81
|
-
.then(
|
|
130
|
+
listAllPages((page) => listDeletedContacts(folderUid, { limit: LIST_PAGE_SIZE, page }))
|
|
131
|
+
.then((result) => {
|
|
132
|
+
// A server that dropped the `deleted` filter (no delete+update right on this folder) answers with live
|
|
133
|
+
// contacts, which must never be listed as deleted.
|
|
134
|
+
if (result.items.some((c) => c.deleted !== true)) {
|
|
135
|
+
setDeletedContacts([]);
|
|
136
|
+
setDeletedTruncated(false);
|
|
137
|
+
setDeletedError(DELETED_NOT_PERMITTED_MESSAGE);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setDeletedContacts(result.items);
|
|
141
|
+
setDeletedTruncated(result.truncated);
|
|
142
|
+
})
|
|
82
143
|
.catch((err) => setDeletedError(err instanceof ApiRequestError ? err.message : "Could not load deleted contacts."))
|
|
83
144
|
.finally(() => setDeletedLoading(false));
|
|
84
145
|
// Re-fetches every time the Deleted view is (re-)selected, matching this page's own `reload()`
|
|
@@ -175,6 +236,8 @@ function ContactsContent() {
|
|
|
175
236
|
async function handleDelete(contact) {
|
|
176
237
|
try {
|
|
177
238
|
await deleteContact(contact.uid, contact.version);
|
|
239
|
+
// Trusted signer pins come from contacts - a deleted contact's keys must stop vouching for signatures.
|
|
240
|
+
clearPinnedSignerCache();
|
|
178
241
|
setSelectedUid(null);
|
|
179
242
|
setMode("view");
|
|
180
243
|
void reload();
|
|
@@ -184,6 +247,7 @@ function ContactsContent() {
|
|
|
184
247
|
}
|
|
185
248
|
}
|
|
186
249
|
async function handleBulkDelete() {
|
|
250
|
+
setConfirmingBulkDelete(false);
|
|
187
251
|
let bulkError = null;
|
|
188
252
|
for (const contact of checkedContacts) {
|
|
189
253
|
try {
|
|
@@ -193,6 +257,7 @@ function ContactsContent() {
|
|
|
193
257
|
bulkError = err instanceof ApiRequestError ? err.message : "Could not delete one or more contacts.";
|
|
194
258
|
}
|
|
195
259
|
}
|
|
260
|
+
clearPinnedSignerCache();
|
|
196
261
|
setCheckedUids(new Set());
|
|
197
262
|
await reload();
|
|
198
263
|
setError(bulkError);
|
|
@@ -222,6 +287,7 @@ function ContactsContent() {
|
|
|
222
287
|
bulkError = err instanceof ApiRequestError ? err.message : "Could not update one or more contacts.";
|
|
223
288
|
}
|
|
224
289
|
}
|
|
290
|
+
clearPinnedSignerCache();
|
|
225
291
|
await reload();
|
|
226
292
|
setError(bulkError);
|
|
227
293
|
}
|
|
@@ -234,12 +300,15 @@ function ContactsContent() {
|
|
|
234
300
|
for (const contact of checkedContacts) {
|
|
235
301
|
const categories = Array.from(new Set([...(contact.categories ?? []), category.trim()]));
|
|
236
302
|
try {
|
|
237
|
-
|
|
303
|
+
// Only the changed field - sending the whole fetched contact back includes server-managed
|
|
304
|
+
// fields restapi rejects, and would overwrite any concurrent edit to the other fields.
|
|
305
|
+
await updateContact({ uid: contact.uid, version: contact.version, categories });
|
|
238
306
|
}
|
|
239
307
|
catch (err) {
|
|
240
308
|
bulkError = err instanceof ApiRequestError ? err.message : "Could not update one or more contacts.";
|
|
241
309
|
}
|
|
242
310
|
}
|
|
311
|
+
clearPinnedSignerCache();
|
|
243
312
|
await reload();
|
|
244
313
|
setError(bulkError);
|
|
245
314
|
}
|
|
@@ -261,11 +330,12 @@ function ContactsContent() {
|
|
|
261
330
|
bulkError = err instanceof ApiRequestError ? err.message : "Could not import one or more contacts.";
|
|
262
331
|
}
|
|
263
332
|
}
|
|
333
|
+
clearPinnedSignerCache();
|
|
264
334
|
await reload();
|
|
265
335
|
setError(bulkError);
|
|
266
336
|
}
|
|
267
|
-
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx(ContactsSidebar, { mailboxUid: mailboxUid, contacts: contacts, active: view, onSelect: handleSelectView }), _jsxs("div", { className: [
|
|
337
|
+
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx(ContactsSidebar, { mailboxUid: mailboxUid, contacts: contacts, active: view, onSelect: handleSelectView, showDeleted: canViewDeleted }), _jsxs("div", { className: [
|
|
268
338
|
"w-full md:w-96 shrink-0 md:border-r border-border md:flex flex-col min-h-0",
|
|
269
339
|
mode === "new" ? "hidden md:flex" : "flex",
|
|
270
|
-
].join(" "), children: [_jsx(ContactsToolbar, { selectedCount: checkedContacts.length, allSelectedFavorited: checkedContacts.length > 0 && checkedContacts.every((c) => c.favorite), onNewContact: handleNew, onEdit: handleToolbarEdit, onDelete:
|
|
340
|
+
].join(" "), children: [_jsx(ContactsToolbar, { selectedCount: checkedContacts.length, allSelectedFavorited: checkedContacts.length > 0 && checkedContacts.every((c) => c.favorite), onNewContact: handleNew, onEdit: handleToolbarEdit, onDelete: () => setConfirmingBulkDelete(true), onEmail: handleEmail, onToggleFavorite: handleToggleFavorite, onAddCategory: handleAddCategory, onExportVCard: handleExportVCard, onImportFile: handleImportFile }), _jsx("div", { className: "p-3 border-b border-border", children: _jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search contacts", "aria-label": "Search contacts", className: INPUT_CLASS }) }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), (isDeletedView ? deletedTruncated : truncated) && (_jsx("div", { className: "p-3", children: _jsxs(Alert, { children: ["This folder has more contacts than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] }) })), deletedError && isDeletedView && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: deletedError }) })), (isDeletedView ? deletedLoading : loading) ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : sorted.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "No contacts found." })) : (_jsx("div", { className: "flex-1 overflow-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [!isDeletedView && (_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all contacts", checked: allChecked, onChange: toggleCheckAll }) })), _jsx("th", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("name"), className: "text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Name", sortColumn === "name" ? (sortDesc ? " ▾" : " ▴") : ""] }) }), _jsx("th", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("info"), className: "text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Contact info", sortColumn === "info" ? (sortDesc ? " ▾" : " ▴") : ""] }) })] }) }), _jsx("tbody", { children: sorted.map((contact) => (_jsxs("tr", { className: ["border-b border-border", contact.uid === selectedUid ? "bg-primary/10" : "hover:bg-surface-alt"].join(" "), children: [!isDeletedView && (_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${contact.displayName}`, checked: checkedUids.has(contact.uid), onChange: () => toggleChecked(contact.uid) }) })), _jsx("td", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSelectRow(contact), className: "flex items-center gap-2 text-left", children: [_jsx(ContactAvatar, { displayName: contact.displayName, size: 28 }), _jsxs("span", { className: "truncate font-medium", children: [contact.displayName, contact.favorite && _jsx("span", { "aria-label": "Favorite", children: " \u2605" })] })] }) }), _jsx("td", { className: "px-3 py-2 text-text-muted truncate", children: primaryInfo(contact) })] }, contact.uid))) })] }) }))] }), _jsxs("div", { className: ["flex-1 min-w-0 overflow-y-auto p-6 md:block", mode === "new" ? "block" : "hidden"].join(" "), children: [savedElsewhere && mode !== "new" && (_jsxs("p", { role: "status", className: "mb-4 text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: [savedElsewhere.displayName, " was added to", " ", mailboxes.find((mb) => mb.uid === savedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/contacts?mailboxUid=${encodeURIComponent(savedElsewhere.mailboxUid)}`, className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s contacts" })] })), mode === "new" && mailboxUid && folderUid ? (_jsx(ContactForm, { mailboxUid: mailboxUid, folderUid: folderUid, mailboxes: writableMailboxes, onSaved: handleSaved, onCancel: handleCancel })) : mode === "edit" && selected ? (_jsx(ContactForm, { contact: selected, onSaved: handleSaved, onCancel: handleCancel })) : selected ? (_jsx(ContactDetailPane, { contact: selected, onEdit: () => setMode("edit"), onDelete: () => handleDelete(selected), onKeysChanged: () => void reload(), canResolveKeys: ownsMailbox || delegateCanUpdate })) : (_jsx("p", { className: "text-sm text-text-muted", children: "Select a contact, or create a new one." }))] }), _jsxs(Modal, { open: confirmingBulkDelete, onClose: () => setConfirmingBulkDelete(false), title: "Delete contacts", children: [_jsxs("p", { className: "text-sm mb-5", children: ["Delete ", checkedContacts.length, " selected ", checkedContacts.length === 1 ? "contact" : "contacts", "? They move to Deleted contacts."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setConfirmingBulkDelete(false), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleBulkDelete, children: "Delete" })] })] })] }));
|
|
271
341
|
}
|