@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,99 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { useRef, useState } from "react";
|
|
7
|
+
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
9
|
+
/** Page size for the async-request lists (the server returns them newest first and caps `limit` at 500). */
|
|
10
|
+
export const REQUEST_LIST_PAGE_SIZE = 50;
|
|
11
|
+
/**
|
|
12
|
+
* A newest-first list fetched a page at a time with "Load more". Only the most recently started `reload()`'s
|
|
13
|
+
* responses are applied, so a slow earlier fetch (e.g. the mount fetch racing a reload after a create) can't
|
|
14
|
+
* overwrite a newer list, and a "Load more" that was in flight during a reload is dropped.
|
|
15
|
+
*
|
|
16
|
+
* Paging is by offset, so "Load more" re-reads the previous page alongside the next one and continues after the last
|
|
17
|
+
* row already shown: rows created since shift older rows onto later pages (repeats are skipped), and rows deleted
|
|
18
|
+
* since shift them onto earlier pages (which a plain next-page fetch would silently skip). If that row can't be found
|
|
19
|
+
* in the two pages at all (it was deleted, or more than a page of rows came or went), the list reloads from the start.
|
|
20
|
+
*/
|
|
21
|
+
export function usePagedList(fetchPage, errorMessage) {
|
|
22
|
+
const pageSize = REQUEST_LIST_PAGE_SIZE;
|
|
23
|
+
const [items, setItems] = useState([]);
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [loadError, setLoadError] = useState(null);
|
|
26
|
+
const [hasMore, setHasMore] = useState(false);
|
|
27
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
28
|
+
const generation = useRef(0);
|
|
29
|
+
const nextPage = useRef(0);
|
|
30
|
+
const loaded = useRef(items);
|
|
31
|
+
loaded.current = items;
|
|
32
|
+
function describe(err) {
|
|
33
|
+
return err instanceof ApiRequestError ? err.message : errorMessage;
|
|
34
|
+
}
|
|
35
|
+
async function reload() {
|
|
36
|
+
const current = ++generation.current;
|
|
37
|
+
try {
|
|
38
|
+
const data = await fetchPage({ limit: pageSize, page: 0 });
|
|
39
|
+
if (current !== generation.current)
|
|
40
|
+
return;
|
|
41
|
+
setItems(data);
|
|
42
|
+
setHasMore(data.length === pageSize);
|
|
43
|
+
setLoadError(null);
|
|
44
|
+
nextPage.current = 1;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (current === generation.current)
|
|
48
|
+
setLoadError(describe(err));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (current === generation.current)
|
|
52
|
+
setLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function loadMore() {
|
|
56
|
+
const current = generation.current;
|
|
57
|
+
const page = nextPage.current;
|
|
58
|
+
setLoadingMore(true);
|
|
59
|
+
try {
|
|
60
|
+
const [previous, data] = await Promise.all([
|
|
61
|
+
fetchPage({ limit: pageSize, page: page - 1 }),
|
|
62
|
+
fetchPage({ limit: pageSize, page }),
|
|
63
|
+
]);
|
|
64
|
+
if (current !== generation.current)
|
|
65
|
+
return;
|
|
66
|
+
const window = [...previous, ...data];
|
|
67
|
+
// "Load more" is only offered after a full page, so at least one row is shown.
|
|
68
|
+
const lastShown = loaded.current[loaded.current.length - 1].uid;
|
|
69
|
+
const boundary = window.map((item) => item.uid).lastIndexOf(lastShown);
|
|
70
|
+
if (boundary === -1) {
|
|
71
|
+
await reload();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const after = window.slice(boundary + 1);
|
|
75
|
+
setItems((prev) => [...prev, ...after.filter((item) => !prev.some((existing) => existing.uid === item.uid))]);
|
|
76
|
+
setHasMore(data.length === pageSize);
|
|
77
|
+
setLoadError(null);
|
|
78
|
+
nextPage.current = page + 1;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (current === generation.current)
|
|
82
|
+
setLoadError(describe(err));
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setLoadingMore(false);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function replaceItem(item) {
|
|
89
|
+
setItems((prev) => prev.map((existing) => (existing.uid === item.uid ? item : existing)));
|
|
90
|
+
}
|
|
91
|
+
return { items, loading, loadError, hasMore, loadingMore, reload, loadMore, replaceItem };
|
|
92
|
+
}
|
|
93
|
+
/** The "Load more" button for a `usePagedList()` list - renders nothing once there's nothing more to load. */
|
|
94
|
+
export function LoadMoreButton({ list, label }) {
|
|
95
|
+
if (!list.hasMore) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return (_jsx("div", { className: "mt-3", children: _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", loading: list.loadingMore, disabled: list.loadingMore, onClick: () => void list.loadMore(), children: label }) }));
|
|
99
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Folder, Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
|
+
export interface CalendarListSidebarMailbox {
|
|
4
|
+
mailbox: Mailbox;
|
|
5
|
+
calendarFolders: Folder[];
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CalendarListSidebarProps {
|
|
9
|
+
/** Every accessible mailbox's `calendar`-type folders, grouped per mailbox (see `CalendarShell`). */
|
|
10
|
+
mailboxCalendars: CalendarListSidebarMailbox[];
|
|
11
|
+
checkedFolderUids: Set<string>;
|
|
12
|
+
onToggle: (folderUid: string) => void;
|
|
13
|
+
onAddCalendar: (mailboxUid: string, name: string, color: string) => Promise<void>;
|
|
14
|
+
/** A calendar's display color - defaults to its own `color` (see `CalendarShell`'s `colorFor`). */
|
|
15
|
+
colorFor?: (folder: Folder) => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Calendar's left-sidebar checklist of calendars — one row per `calendar`-type folder, a color swatch,
|
|
19
|
+
* and a checkbox controlling whether that calendar's events are currently shown (the checked set is owned
|
|
20
|
+
* by `apps/www/calendar/index.tsx`, since it drives which folders get fanned out to on every reload).
|
|
21
|
+
* With one mailbox this is a single "My calendars" list, as before; with several, each mailbox gets its
|
|
22
|
+
* own section (a shared mailbox labeled as such, with a color-dot accent) and its own "+ Add calendar",
|
|
23
|
+
* which creates the calendar in that mailbox. "+ Add calendar" is a real `createFolder({type: "calendar",
|
|
24
|
+
* ...})` call — folder creation has no type restriction server-side, so this needed no new backend route.
|
|
25
|
+
*/
|
|
26
|
+
export default function CalendarListSidebar({ mailboxCalendars, checkedFolderUids, onToggle, onAddCalendar, colorFor, }: CalendarListSidebarProps): React.JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
|
+
import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
4
|
+
export interface EventModalProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
mailboxUid: string;
|
|
8
|
+
/** The calendar folder a new event is created into (ignored when editing — an existing event keeps
|
|
9
|
+
* its own `folderUid`). When `calendars` names more than one option, a "Calendar" selector lets the
|
|
10
|
+
* user override this default before saving. */
|
|
11
|
+
folderUid: string;
|
|
12
|
+
/** Every calendar the caller could create this event into. Omitted, or a single entry, means "only
|
|
13
|
+
* one calendar exists" — no selector is shown and `folderUid` is used as-is, matching this
|
|
14
|
+
* component's original single-calendar behavior exactly. */
|
|
15
|
+
calendars?: {
|
|
16
|
+
uid: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}[];
|
|
19
|
+
/** Every mailbox a new event could be created in, each with its own calendars. With more than one, a
|
|
20
|
+
* "Mailbox" selector (create mode only) chooses the mailbox, which in turn drives the Calendar selector
|
|
21
|
+
* and the organizer address; `mailboxUid`/`folderUid` are the initial selection. */
|
|
22
|
+
mailboxOptions?: {
|
|
23
|
+
mailbox: Mailbox;
|
|
24
|
+
calendars: {
|
|
25
|
+
uid: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}[];
|
|
28
|
+
}[];
|
|
29
|
+
organizerAddress: string;
|
|
30
|
+
/** The viewing mailbox's alias addresses - an event it organizes (or an invitation to it) may name any of
|
|
31
|
+
* them instead of `organizerAddress`. Defaults to that mailbox's `aliasAddresses` from `mailboxOptions`. */
|
|
32
|
+
organizerAliases?: string[];
|
|
33
|
+
/** `null` when creating a new event. */
|
|
34
|
+
occurrence: CalendarOccurrence | null;
|
|
35
|
+
/** Prefilled start/end for create mode (e.g. the day/slot the user clicked). */
|
|
36
|
+
initialStart?: Date;
|
|
37
|
+
initialEnd?: Date;
|
|
38
|
+
onSaved: () => void;
|
|
39
|
+
onDeleted: () => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create/view/edit/delete for a single calendar event. Editing or deleting a recurring event's
|
|
43
|
+
* occurrence offers a choice between "this event" and "the entire series" (see
|
|
44
|
+
* `calendarMutations.ts`) — Outlook's own convention for the same ambiguity.
|
|
45
|
+
*/
|
|
46
|
+
export default function EventModal({ open, onClose, mailboxUid, folderUid, calendars, mailboxOptions, organizerAddress, organizerAliases, occurrence, initialStart, initialEnd, onSaved, onDeleted, }: EventModalProps): React.JSX.Element;
|
|
@@ -5,14 +5,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useRef, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
-
import { createCalendarEvent, respondToEvent, updateCalendarEvent, } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
8
|
+
import { createCalendarEvent, getCalendarEvent, respondToEvent, updateCalendarEvent, } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
9
9
|
import { deleteEventOccurrence, deleteEventSeries, detachOccurrence, saveEventSeries } from "@rapidmx/react-shared/calendar/calendarMutations.js";
|
|
10
10
|
import { toDatetimeLocal } from "@rapidmx/react-shared/util/dateInput.js";
|
|
11
|
+
import { fromEventWallClock, toEventWallClock } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
11
12
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
12
13
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
13
14
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
14
15
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
15
16
|
import RecurrenceEditor from "./RecurrenceEditor.js";
|
|
17
|
+
import { addDaysToKey, allDayDateKey, allDayInstant, recurrenceUntilDateKey, recurrenceUntilInstant, startWeekdayCode } from "./allDay.js";
|
|
16
18
|
import ResourcePicker from "./ResourcePicker.js";
|
|
17
19
|
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";
|
|
18
20
|
const SELECT_CLASS = "text-sm py-2 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
@@ -25,18 +27,83 @@ const RESPONSE_STATUS_LABEL = {
|
|
|
25
27
|
declined: "Declined",
|
|
26
28
|
tentative: "Tentative",
|
|
27
29
|
};
|
|
30
|
+
const MS_PER_MINUTE = 60000;
|
|
31
|
+
const MS_PER_DAY = 86400000;
|
|
32
|
+
/** Whether two ISO instants fall in the same minute - a stored `...T15:00:00Z` and the form's re-serialized
|
|
33
|
+
* `...T15:00:00.000Z` name the same time without being equal strings. */
|
|
34
|
+
function sameMinute(a, b) {
|
|
35
|
+
return Math.round(new Date(a).getTime() / MS_PER_MINUTE) === Math.round(new Date(b).getTime() / MS_PER_MINUTE);
|
|
36
|
+
}
|
|
37
|
+
/** Milliseconds since midnight of an instant's wall clock in `timezone` (a timed event's own zone). */
|
|
38
|
+
function wallTimeOfDayMs(instantMs, timezone) {
|
|
39
|
+
const wall = toEventWallClock(instantMs, timezone, false);
|
|
40
|
+
return ((wall % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Rewrites an edited occurrence's `startDate`/`endDate` for a save to the entire series. The modal only
|
|
44
|
+
* ever shows (and edits) *this occurrence's* dates, but the series is stored as its master record starting
|
|
45
|
+
* at the first occurrence - sending the occurrence's dates as-is would move the whole series' start to
|
|
46
|
+
* this occurrence. So only the change in time-of-day (and the new duration) is applied to the master's own
|
|
47
|
+
* start; the series keeps its original first date. Unchanged times aren't sent at all.
|
|
48
|
+
*
|
|
49
|
+
* Times of day are read on the event's own timezone wall clock (the series expands there), not the
|
|
50
|
+
* browser's, and the change is the smallest signed difference modulo 24h - moving 23:00 to 01:00 is two
|
|
51
|
+
* hours later (into the next day), never 22 hours earlier. The master's new start is converted back from
|
|
52
|
+
* that wall clock, so a master on the other side of a DST change keeps the chosen local time.
|
|
53
|
+
*/
|
|
54
|
+
async function toSeriesFields(occurrence, fields) {
|
|
55
|
+
const { startDate, endDate, ...rest } = fields;
|
|
56
|
+
if (sameMinute(startDate, occurrence.startDate) && sameMinute(endDate, occurrence.endDate)) {
|
|
57
|
+
return rest;
|
|
58
|
+
}
|
|
59
|
+
const master = await getCalendarEvent(occurrence.uid);
|
|
60
|
+
const timezone = fields.timezone;
|
|
61
|
+
const newStart = new Date(startDate).getTime();
|
|
62
|
+
const durationMs = new Date(endDate).getTime() - newStart;
|
|
63
|
+
let masterStart;
|
|
64
|
+
if (fields.allDay) {
|
|
65
|
+
// Timed -> all-day keeps the master's own date on its wall clock.
|
|
66
|
+
const masterDateKey = master.allDay
|
|
67
|
+
? allDayDateKey(master.startDate)
|
|
68
|
+
: new Date(toEventWallClock(new Date(master.startDate).getTime(), timezone, false)).toISOString().slice(0, 10);
|
|
69
|
+
masterStart = new Date(allDayInstant(masterDateKey)).getTime();
|
|
70
|
+
}
|
|
71
|
+
else if (master.allDay) {
|
|
72
|
+
// All-day -> timed: the master's own date at the newly chosen wall-clock time.
|
|
73
|
+
const masterDateWall = new Date(allDayInstant(allDayDateKey(master.startDate))).getTime();
|
|
74
|
+
masterStart = fromEventWallClock(masterDateWall + wallTimeOfDayMs(newStart, timezone), timezone, false);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const rawDelta = wallTimeOfDayMs(newStart, timezone) - wallTimeOfDayMs(new Date(occurrence.startDate).getTime(), timezone);
|
|
78
|
+
const delta = ((((rawDelta + MS_PER_DAY / 2) % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY) - MS_PER_DAY / 2;
|
|
79
|
+
const masterWall = toEventWallClock(new Date(master.startDate).getTime(), timezone, false);
|
|
80
|
+
masterStart = fromEventWallClock(masterWall + delta, timezone, false);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...rest,
|
|
84
|
+
startDate: new Date(masterStart).toISOString(),
|
|
85
|
+
endDate: new Date(masterStart + durationMs).toISOString(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
28
88
|
/**
|
|
29
89
|
* Create/view/edit/delete for a single calendar event. Editing or deleting a recurring event's
|
|
30
90
|
* occurrence offers a choice between "this event" and "the entire series" (see
|
|
31
91
|
* `calendarMutations.ts`) — Outlook's own convention for the same ambiguity.
|
|
32
92
|
*/
|
|
33
|
-
export default function EventModal({ open, onClose, mailboxUid, folderUid, calendars, mailboxOptions, organizerAddress, occurrence, initialStart, initialEnd, onSaved, onDeleted, }) {
|
|
93
|
+
export default function EventModal({ open, onClose, mailboxUid, folderUid, calendars, mailboxOptions, organizerAddress, organizerAliases, occurrence, initialStart, initialEnd, onSaved, onDeleted, }) {
|
|
34
94
|
const [targetFolderUid, setTargetFolderUid] = useState(folderUid);
|
|
35
95
|
const [targetMailboxUid, setTargetMailboxUid] = useState(mailboxUid);
|
|
36
96
|
const targetMailboxOption = mailboxOptions?.find((option) => option.mailbox.uid === targetMailboxUid);
|
|
37
97
|
// Create mode follows the chosen mailbox; editing an existing event always keeps the passed-in values.
|
|
38
98
|
const calendarChoices = !occurrence && targetMailboxOption ? targetMailboxOption.calendars : calendars;
|
|
39
99
|
const effectiveOrganizerAddress = !occurrence && targetMailboxOption ? targetMailboxOption.mailbox.primarySmtpAddress : organizerAddress;
|
|
100
|
+
// Every address the viewing mailbox receives at - its primary address and its aliases, case-insensitively.
|
|
101
|
+
const ownAddresses = new Set([organizerAddress, ...(organizerAliases ?? mailboxOptions?.find((option) => option.mailbox.uid === mailboxUid)?.mailbox.aliasAddresses ?? [])].map((address) => address.toLowerCase()));
|
|
102
|
+
const isOwnAddress = (address) => ownAddresses.has(address.toLowerCase());
|
|
103
|
+
// An existing event organized by someone else is the viewing mailbox's copy of an invitation: only the
|
|
104
|
+
// organizer can change it (their next update would overwrite local edits anyway), so it's read-only
|
|
105
|
+
// apart from the RSVP controls.
|
|
106
|
+
const isInvited = !!occurrence && !isOwnAddress(occurrence.organizer.address) && !occurrence.attendees.some((a) => a.isOrganizer && isOwnAddress(a.address));
|
|
40
107
|
function handleMailboxChange(nextMailboxUid) {
|
|
41
108
|
setTargetMailboxUid(nextMailboxUid);
|
|
42
109
|
const firstCalendar = mailboxOptions?.find((option) => option.mailbox.uid === nextMailboxUid)?.calendars[0];
|
|
@@ -46,8 +113,19 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
46
113
|
}
|
|
47
114
|
const [title, setTitle] = useState(occurrence?.title ?? "");
|
|
48
115
|
const [location, setLocation] = useState(occurrence?.location ?? "");
|
|
49
|
-
|
|
50
|
-
|
|
116
|
+
// An all-day event is stored date-only with an exclusive end (see `allDay.ts`); the form shows its
|
|
117
|
+
// inclusive last day instead.
|
|
118
|
+
const [start, setStart] = useState(() => occurrence?.allDay
|
|
119
|
+
? `${allDayDateKey(occurrence.startDate)}T00:00`
|
|
120
|
+
: toDatetimeLocal(occurrence?.startDate ?? initialStart?.toISOString() ?? new Date().toISOString()));
|
|
121
|
+
const [end, setEnd] = useState(() => {
|
|
122
|
+
if (occurrence?.allDay) {
|
|
123
|
+
const startKey = allDayDateKey(occurrence.startDate);
|
|
124
|
+
const lastDayKey = addDaysToKey(allDayDateKey(occurrence.endDate), -1);
|
|
125
|
+
return `${lastDayKey < startKey ? startKey : lastDayKey}T00:00`;
|
|
126
|
+
}
|
|
127
|
+
return toDatetimeLocal(occurrence?.endDate ?? initialEnd?.toISOString() ?? new Date(Date.now() + 30 * 60000).toISOString());
|
|
128
|
+
});
|
|
51
129
|
const [allDay, setAllDay] = useState(occurrence?.allDay ?? false);
|
|
52
130
|
const [timezone] = useState(occurrence?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
53
131
|
const [attendees, setAttendees] = useState(occurrence?.attendees ?? []);
|
|
@@ -60,17 +138,21 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
60
138
|
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
61
139
|
const [error, setError] = useState(null);
|
|
62
140
|
const [saving, setSaving] = useState(false);
|
|
141
|
+
// A series save that moved the series but couldn't move every separately edited occurrence with it.
|
|
142
|
+
const [syncWarning, setSyncWarning] = useState(false);
|
|
63
143
|
const [responding, setResponding] = useState(false);
|
|
64
144
|
const [resourcePickerOpen, setResourcePickerOpen] = useState(false);
|
|
65
145
|
const addResourceButtonRef = useRef(null);
|
|
66
146
|
// The viewing mailbox can respond to an *existing* event it's invited to but doesn't organize —
|
|
67
|
-
// `organizerAddress`
|
|
68
|
-
// finding
|
|
147
|
+
// `organizerAddress` (plus the aliases) are this mailbox's own addresses (see `EventModalProps`), so
|
|
148
|
+
// finding one among `attendees` (and not as that attendee's own organizer flag) identifies exactly
|
|
69
149
|
// that case. `@rapidmx/restapi`'s `respond()` route has no occurrence-vs-series scope of its own
|
|
70
150
|
// (see `calendarApi.ts`'s `respondToEvent` doc comment) — it always acts on `occurrence.uid` as a
|
|
71
151
|
// single event document, so this deliberately does not consult `editScope`.
|
|
72
|
-
const myAttendeeIndex = occurrence ? attendees.findIndex((a) => a.address
|
|
152
|
+
const myAttendeeIndex = occurrence ? attendees.findIndex((a) => isOwnAddress(a.address)) : -1;
|
|
73
153
|
const canRespond = myAttendeeIndex !== -1 && !attendees[myAttendeeIndex].isOrganizer;
|
|
154
|
+
// "This event only" detaches a standalone, non-repeating copy - the series' rule doesn't apply to it.
|
|
155
|
+
const editingSingleOccurrence = !!occurrence?.isRecurringOccurrence && editScope === "occurrence";
|
|
74
156
|
function updateAttendee(index, patch) {
|
|
75
157
|
setAttendees((prev) => prev.map((a, i) => (i === index ? { ...a, ...patch } : a)));
|
|
76
158
|
}
|
|
@@ -91,35 +173,65 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
91
173
|
setError("A title is required.");
|
|
92
174
|
return;
|
|
93
175
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
176
|
+
let startDate;
|
|
177
|
+
let endDate;
|
|
178
|
+
if (allDay) {
|
|
179
|
+
const startKey = start.slice(0, 10);
|
|
180
|
+
const lastDayKey = end.slice(0, 10);
|
|
181
|
+
if (lastDayKey < startKey) {
|
|
182
|
+
setError("The end date can't be before the start date.");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
startDate = allDayInstant(startKey);
|
|
186
|
+
endDate = allDayInstant(addDaysToKey(lastDayKey, 1));
|
|
97
187
|
}
|
|
188
|
+
else {
|
|
189
|
+
if (new Date(end) <= new Date(start)) {
|
|
190
|
+
setError("The end time must be after the start time.");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
startDate = new Date(start).toISOString();
|
|
194
|
+
endDate = new Date(end).toISOString();
|
|
195
|
+
}
|
|
196
|
+
// Only a stored event is updated in place, where a cleared field must be sent as `null`; creating
|
|
197
|
+
// (a new event, or the detached copy of one occurrence) just omits it.
|
|
198
|
+
const updatingInPlace = !!occurrence && !editingSingleOccurrence;
|
|
199
|
+
const cleared = updatingInPlace ? null : undefined;
|
|
98
200
|
const fields = {
|
|
99
201
|
title: title.trim(),
|
|
100
|
-
location: location.trim() ||
|
|
101
|
-
startDate
|
|
102
|
-
endDate
|
|
202
|
+
location: location.trim() || cleared,
|
|
203
|
+
startDate,
|
|
204
|
+
endDate,
|
|
103
205
|
allDay,
|
|
104
206
|
timezone,
|
|
105
|
-
organizer: { address: effectiveOrganizerAddress, type: "to" },
|
|
106
207
|
attendees,
|
|
107
|
-
recurrenceRule: recurrenceRule ??
|
|
108
|
-
reminderMinutesBeforeStart: reminderMinutes.trim() ? Number(reminderMinutes) :
|
|
208
|
+
recurrenceRule: editingSingleOccurrence ? undefined : (recurrenceRule ?? cleared),
|
|
209
|
+
reminderMinutesBeforeStart: reminderMinutes.trim() ? Number(reminderMinutes) : cleared,
|
|
109
210
|
busyStatus,
|
|
110
211
|
autoReplyEnabled,
|
|
111
|
-
autoReplyMessage: autoReplyEnabled ? autoReplyMessage :
|
|
212
|
+
autoReplyMessage: autoReplyEnabled ? autoReplyMessage : cleared,
|
|
112
213
|
};
|
|
113
214
|
setSaving(true);
|
|
114
215
|
try {
|
|
115
216
|
if (!occurrence) {
|
|
116
|
-
|
|
217
|
+
// The organizer is only ever set on create - an edit keeps the event's own organizer.
|
|
218
|
+
await createCalendarEvent({
|
|
219
|
+
mailboxUid: targetMailboxUid,
|
|
220
|
+
folderUid: targetFolderUid,
|
|
221
|
+
...fields,
|
|
222
|
+
organizer: { address: effectiveOrganizerAddress, type: "to" },
|
|
223
|
+
});
|
|
117
224
|
}
|
|
118
|
-
else if (
|
|
225
|
+
else if (editingSingleOccurrence) {
|
|
119
226
|
await detachOccurrence(occurrence, fields);
|
|
120
227
|
}
|
|
121
228
|
else if (occurrence.isRecurringOccurrence) {
|
|
122
|
-
await saveEventSeries(occurrence, fields);
|
|
229
|
+
const saved = await saveEventSeries(occurrence, (await toSeriesFields(occurrence, fields)));
|
|
230
|
+
if (saved.detachedOccurrenceSyncFailed) {
|
|
231
|
+
// The series itself was saved - say so, and leave closing to the user once they've read it.
|
|
232
|
+
setSyncWarning(true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
123
235
|
}
|
|
124
236
|
else {
|
|
125
237
|
await updateCalendarEvent({ uid: occurrence.uid, version: occurrence.version, ...fields });
|
|
@@ -174,6 +286,15 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
174
286
|
setResponding(false);
|
|
175
287
|
}
|
|
176
288
|
}
|
|
177
|
-
|
|
289
|
+
// The recurrence end date is stored in the frame the series expands in (UTC for all-day, local otherwise),
|
|
290
|
+
// so toggling All day re-stores the same chosen date in the new frame.
|
|
291
|
+
function handleAllDayChange(nextAllDay) {
|
|
292
|
+
setAllDay(nextAllDay);
|
|
293
|
+
setRecurrenceRule((rule) => rule?.until ? { ...rule, until: recurrenceUntilInstant(recurrenceUntilDateKey(rule.until, allDay), nextAllDay) } : rule);
|
|
294
|
+
}
|
|
295
|
+
if (syncWarning) {
|
|
296
|
+
return (_jsxs(Modal, { open: open, onClose: onSaved, title: "Series saved", children: [_jsx("p", { role: "alert", className: "text-sm mb-4", children: "The series was saved, but some occurrences you had changed individually couldn\u2019t be moved with it. They may now appear twice - check the calendar and delete any duplicate." }), _jsx(Button, { type: "button", className: "!w-auto", onClick: onSaved, children: "OK" })] }));
|
|
297
|
+
}
|
|
298
|
+
return (_jsx(Modal, { open: open, onClose: onClose, title: occurrence ? "Edit event" : "New event", children: _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-1", children: [error && _jsx(Alert, { children: error }), canRespond && (_jsxs("div", { className: "flex flex-col gap-2 mb-3 p-3 rounded-sm bg-surface-alt", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium", children: "Your response" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: responding, onClick: () => handleRespond("accepted"), children: "Accept" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: responding, onClick: () => handleRespond("tentative"), children: "Tentative" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: responding, onClick: () => handleRespond("declined"), children: "Decline" })] })] }), _jsx("p", { className: "text-xs text-text-muted", children: "Other attendees\u2019 responses may take a few minutes to update." })] })), isInvited && (_jsx("p", { className: "text-xs text-text-muted mb-3", children: "You were invited to this event. Only the organizer can change its details." })), _jsxs("fieldset", { disabled: isInvited, className: "flex flex-col gap-1 min-w-0", children: [!occurrence && mailboxOptions && mailboxOptions.length > 1 && (_jsx(FormField, { label: "Mailbox", htmlFor: "event-mailbox", children: _jsx("select", { id: "event-mailbox", className: INPUT_CLASS, value: targetMailboxUid, onChange: (e) => handleMailboxChange(e.target.value), children: mailboxOptions.map(({ mailbox }) => (_jsxs("option", { value: mailbox.uid, children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }, mailbox.uid))) }) })), !occurrence && calendarChoices && calendarChoices.length > 1 && (_jsx(FormField, { label: "Calendar", htmlFor: "event-calendar", children: _jsx("select", { id: "event-calendar", className: INPUT_CLASS, value: targetFolderUid, onChange: (e) => setTargetFolderUid(e.target.value), children: calendarChoices.map((cal) => (_jsx("option", { value: cal.uid, children: cal.name }, cal.uid))) }) })), _jsx(FormField, { label: "Title", htmlFor: "event-title", children: _jsx("input", { id: "event-title", type: "text", className: INPUT_CLASS, value: title, onChange: (e) => setTitle(e.target.value) }) }), _jsx(FormField, { label: "Location", htmlFor: "event-location", children: _jsx("input", { id: "event-location", type: "text", className: INPUT_CLASS, value: location, onChange: (e) => setLocation(e.target.value) }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm font-medium mb-3", children: [_jsx("input", { type: "checkbox", checked: allDay, onChange: (e) => handleAllDayChange(e.target.checked) }), "All day"] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx(FormField, { label: "Start", htmlFor: "event-start", children: _jsx("input", { id: "event-start", type: allDay ? "date" : "datetime-local", className: INPUT_CLASS, value: allDay ? start.slice(0, 10) : start, onChange: (e) => setStart(allDay ? `${e.target.value}T00:00` : e.target.value) }) }), _jsx(FormField, { label: "End", htmlFor: "event-end", children: _jsx("input", { id: "event-end", type: allDay ? "date" : "datetime-local", className: INPUT_CLASS, value: allDay ? end.slice(0, 10) : end, onChange: (e) => setEnd(allDay ? `${e.target.value}T00:00` : e.target.value) }) })] }), _jsx(FormField, { label: "Attendees", htmlFor: "event-attendees", children: _jsxs("div", { id: "event-attendees", className: "flex flex-col gap-2", children: [attendees.map((attendee, i) => (_jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "email", className: `${INPUT_CLASS} flex-1`, value: attendee.address, onChange: (e) => updateAttendee(i, { address: e.target.value }), "aria-label": `Attendee email ${i + 1}` }), _jsx("select", { className: SELECT_CLASS, value: attendee.role, onChange: (e) => updateAttendee(i, { role: e.target.value }), "aria-label": `Attendee role ${i + 1}`, children: ATTENDEE_ROLES.map((role) => (_jsx("option", { value: role, children: role }, role))) }), _jsx("span", { className: "text-xs font-medium text-text-muted shrink-0 py-1 px-2.5 rounded-pill bg-surface-alt self-center", children: RESPONSE_STATUS_LABEL[attendee.responseStatus] }), _jsx("button", { type: "button", onClick: () => removeAttendee(i), className: "text-sm text-danger px-2", "aria-label": `Remove attendee ${i + 1}`, children: "\u00D7" })] }, i))), _jsxs("div", { className: "flex gap-3 items-center", children: [_jsx("button", { type: "button", onClick: () => setAttendees((prev) => [...prev, { address: "", role: "required", responseStatus: "needsAction", isOrganizer: false }]), className: "self-start text-xs font-medium text-primary-dark hover:underline", children: "+ Add attendee" }), _jsx("button", { ref: addResourceButtonRef, type: "button", onClick: () => setResourcePickerOpen(true), className: "self-start text-xs font-medium text-primary-dark hover:underline", children: "+ Add room/equipment" })] }), resourcePickerOpen && (_jsx(ResourcePicker, { anchorRef: addResourceButtonRef, onClose: () => setResourcePickerOpen(false), onSelect: addResource, excludeAddresses: attendees.map((a) => a.address.toLowerCase()) }))] }) }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx(FormField, { label: "Busy status", htmlFor: "event-busyStatus", children: _jsx("select", { id: "event-busyStatus", className: INPUT_CLASS, value: busyStatus, onChange: (e) => setBusyStatus(e.target.value), children: BUSY_STATUSES.map((status) => (_jsx("option", { value: status, children: BUSY_STATUS_LABEL[status] }, status))) }) }), _jsx(FormField, { label: "Reminder (minutes before)", htmlFor: "event-reminder", children: _jsx("input", { id: "event-reminder", type: "number", min: 0, className: INPUT_CLASS, value: reminderMinutes, onChange: (e) => setReminderMinutes(e.target.value), placeholder: "None" }) })] }), _jsxs("div", { className: "mb-3", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx("input", { type: "checkbox", checked: autoReplyEnabled, onChange: (e) => setAutoReplyEnabled(e.target.checked) }), "Send an automatic reply while this event is happening"] }), _jsx("p", { className: "text-xs text-text-muted mt-1", children: "Applies in addition to your mailbox\u2019s own Automatic Replies setting (see Settings) \u2014 this event\u2019s message takes over for its own start/end window." }), autoReplyEnabled && (_jsx("textarea", { "aria-label": "Automatic reply message", className: `${INPUT_CLASS} mt-2`, rows: 3, value: autoReplyMessage, onChange: (e) => setAutoReplyMessage(e.target.value), placeholder: "I'm out of office and back on..." }))] }), !editingSingleOccurrence && (_jsx(FormField, { label: "Recurrence", htmlFor: "event-recurrence", children: _jsx(RecurrenceEditor, { value: recurrenceRule, onChange: setRecurrenceRule, allDay: allDay, startWeekday: startWeekdayCode(start, allDay, timezone) }) })), occurrence?.isRecurringOccurrence && !isInvited && (_jsxs("fieldset", { className: "flex flex-col gap-1.5 text-sm mb-3", children: [_jsx("legend", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1", children: "Apply changes to" }), _jsxs("label", { className: "flex items-center gap-2", children: [_jsx("input", { type: "radio", name: "event-edit-scope", checked: editScope === "occurrence", onChange: () => setEditScope("occurrence") }), "This event only"] }), _jsxs("label", { className: "flex items-center gap-2", children: [_jsx("input", { type: "radio", name: "event-edit-scope", checked: editScope === "series", onChange: () => setEditScope("series") }), "The entire series"] })] }))] }), _jsxs("div", { className: "flex gap-3 mt-2", children: [!isInvited && (_jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: onClose, children: "Cancel" }), occurrence &&
|
|
178
299
|
(!occurrence.isRecurringOccurrence ? (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto ml-auto text-danger", disabled: saving, onClick: () => handleDelete("series"), children: "Delete" })) : !confirmingDelete ? (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto ml-auto text-danger", disabled: saving, onClick: () => setConfirmingDelete(true), children: "Delete" })) : (_jsxs("div", { className: "flex gap-2 ml-auto", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: saving, onClick: () => handleDelete("occurrence"), children: "Delete this event" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: saving, onClick: () => handleDelete("series"), children: "Delete series" })] })))] })] }) }));
|
|
179
300
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
3
|
+
export interface MonthViewProps {
|
|
4
|
+
/** Any date within the month to display. */
|
|
5
|
+
viewDate: Date;
|
|
6
|
+
/** Already expanded for the visible grid range — see `recurrence.ts`'s `expandAllOccurrences`. */
|
|
7
|
+
occurrences: CalendarOccurrence[];
|
|
8
|
+
/** Each occurrence's own calendar color, keyed by `folderUid` — see `calendarColors.ts`. */
|
|
9
|
+
folderColors: Record<string, string>;
|
|
10
|
+
onSelectDay: (date: Date) => void;
|
|
11
|
+
onSelectEvent: (occurrence: CalendarOccurrence) => void;
|
|
12
|
+
}
|
|
13
|
+
/** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day and all-day
|
|
14
|
+
* events are shown on every day they cover (see `allDay.ts`'s `occursOnDay`). */
|
|
15
|
+
export default function MonthView({ viewDate, occurrences, folderColors, onSelectDay, onSelectEvent }: MonthViewProps): React.JSX.Element;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
3
|
-
import { eachDayOfInterval, endOfMonth, endOfWeek, format,
|
|
3
|
+
import { eachDayOfInterval, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek } from "date-fns";
|
|
4
4
|
import { dayDropId, eventDragId } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
|
|
5
|
+
import { occursOnDay, startsOnDay } from "./allDay.js";
|
|
5
6
|
const MAX_CHIPS_PER_DAY = 3;
|
|
6
|
-
/** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day
|
|
7
|
-
* events are shown
|
|
7
|
+
/** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day and all-day
|
|
8
|
+
* events are shown on every day they cover (see `allDay.ts`'s `occursOnDay`). */
|
|
8
9
|
export default function MonthView({ viewDate, occurrences, folderColors, onSelectDay, onSelectEvent }) {
|
|
9
10
|
const gridStart = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
|
|
10
11
|
const gridEnd = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
|
|
11
12
|
const days = eachDayOfInterval({ start: gridStart, end: gridEnd });
|
|
12
|
-
return (_jsx("div", { className: "flex-1 grid grid-cols-7 grid-rows-6 min-h-0", role: "grid", "aria-label": "Month", children: days.map((day) => (_jsx(DayCell, { day: day, inCurrentMonth: isSameMonth(day, viewDate), occurrences: occurrences.filter((occ) =>
|
|
13
|
+
return (_jsx("div", { className: "flex-1 grid grid-cols-7 grid-rows-6 min-h-0", role: "grid", "aria-label": "Month", children: days.map((day) => (_jsx(DayCell, { day: day, inCurrentMonth: isSameMonth(day, viewDate), occurrences: occurrences.filter((occ) => occursOnDay(occ, day)), folderColors: folderColors, onSelectDay: onSelectDay, onSelectEvent: onSelectEvent }, day.toISOString()))) }));
|
|
13
14
|
}
|
|
14
15
|
function DayCell({ day, inCurrentMonth, occurrences, folderColors, onSelectDay, onSelectEvent }) {
|
|
15
16
|
const { setNodeRef, isOver } = useDroppable({ id: dayDropId(day) });
|
|
@@ -22,7 +23,7 @@ function DayCell({ day, inCurrentMonth, occurrences, folderColors, onSelectDay,
|
|
|
22
23
|
].join(" "), children: [_jsx("button", { type: "button", onClick: () => onSelectDay(day), className: [
|
|
23
24
|
"self-start text-xs font-semibold w-6 h-6 rounded-full flex items-center justify-center shrink-0",
|
|
24
25
|
isToday(day) ? "bg-primary text-white" : inCurrentMonth ? "text-text" : "text-text-muted",
|
|
25
|
-
].join(" "), children: format(day, "d") }), _jsxs("div", { className: "flex-1 flex flex-col gap-0.5 min-h-0 overflow-hidden", children: [visible.map((occurrence) => (_jsx(EventChip, { occurrence: occurrence, color: folderColors[occurrence.folderUid], onSelect: onSelectEvent }, occurrence.occurrenceKey))), overflowCount > 0 && (_jsxs("button", { type: "button", onClick: () => onSelectDay(day), className: "text-xs text-text-muted text-left hover:underline shrink-0", children: ["+", overflowCount, " more"] }))] })] }));
|
|
26
|
+
].join(" "), children: format(day, "d") }), _jsxs("div", { className: "flex-1 flex flex-col gap-0.5 min-h-0 overflow-hidden", children: [visible.map((occurrence) => startsOnDay(occurrence, day) ? (_jsx(EventChip, { occurrence: occurrence, color: folderColors[occurrence.folderUid], onSelect: onSelectEvent }, occurrence.occurrenceKey)) : (_jsx(ContinuationChip, { occurrence: occurrence, color: folderColors[occurrence.folderUid], onSelect: onSelectEvent }, occurrence.occurrenceKey))), overflowCount > 0 && (_jsxs("button", { type: "button", onClick: () => onSelectDay(day), className: "text-xs text-text-muted text-left hover:underline shrink-0", children: ["+", overflowCount, " more"] }))] })] }));
|
|
26
27
|
}
|
|
27
28
|
function EventChip({ occurrence, color, onSelect, }) {
|
|
28
29
|
const { setNodeRef, listeners, attributes, transform, isDragging } = useDraggable({ id: eventDragId(occurrence) });
|
|
@@ -36,3 +37,9 @@ function EventChip({ occurrence, color, onSelect, }) {
|
|
|
36
37
|
isDragging ? "opacity-50" : "",
|
|
37
38
|
].join(" "), ...listeners, ...attributes, children: [occurrence.allDay ? "" : `${format(new Date(occurrence.startDate), "h:mma")} `, occurrence.title] }));
|
|
38
39
|
}
|
|
40
|
+
/** A multi-day event's chip on a day after its first: clickable, but not draggable (its drag id is
|
|
41
|
+
* already taken by the start day's chip) and without a start-time prefix. */
|
|
42
|
+
function ContinuationChip({ occurrence, color, onSelect, }) {
|
|
43
|
+
const isFree = occurrence.busyStatus === "free";
|
|
44
|
+
return (_jsx("button", { type: "button", onClick: () => onSelect(occurrence), style: isFree ? undefined : { backgroundColor: color, color: "#fff" }, className: ["text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0", isFree ? "bg-surface-alt text-text-muted" : ""].join(" "), children: occurrence.title }));
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { RecurrenceRule, WeekdayCode } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
3
|
+
export interface RecurrenceEditorProps {
|
|
4
|
+
/** `null` means "does not repeat". */
|
|
5
|
+
value: RecurrenceRule | null;
|
|
6
|
+
onChange: (value: RecurrenceRule | null) => void;
|
|
7
|
+
/** Whether the event is all-day - its series expands in UTC, so the inclusive "Ends on" date is stored as
|
|
8
|
+
* the end of that UTC day rather than the local one (see `allDay.ts`'s `recurrenceUntilInstant()`). */
|
|
9
|
+
allDay?: boolean;
|
|
10
|
+
/** The weekday the event starts on (in the frame its series expands in - see `allDay.ts`'s
|
|
11
|
+
* `startWeekdayCode()`): the day a new weekly rule, or one switched to Weekly, repeats on. Defaults to Monday. */
|
|
12
|
+
startWeekday?: WeekdayCode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A recurring-event editor: frequency + interval, a weekday picker (weekly only), and an end
|
|
16
|
+
* condition (never / after N occurrences / on a date) — everything `RecurrenceRule` can express. The
|
|
17
|
+
* live "every ... until/for ..." summary comes from `describeRecurrence()` (built on `rrule`'s own
|
|
18
|
+
* `.toText()`), so it never drifts out of sync with what will actually be submitted.
|
|
19
|
+
*/
|
|
20
|
+
export default function RecurrenceEditor({ value, onChange, allDay, startWeekday }: RecurrenceEditorProps): React.JSX.Element;
|