@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
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useMemo, useState } from "react";
|
|
7
|
-
import { endOfWeek, isAfter, isBefore, isToday, startOfDay } from "date-fns";
|
|
7
|
+
import { endOfWeek, isAfter, isBefore, isToday, parseISO, startOfDay } from "date-fns";
|
|
8
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
9
9
|
import { createTask, deleteTask, listTasks, setTaskCompleted, setTaskMyDay, } from "@rapidmx/react-shared/tasks/tasksApi.js";
|
|
10
10
|
import { listFlaggedMessages } from "@rapidmx/react-shared/mail/flaggedMessages.js";
|
|
@@ -13,18 +13,29 @@ import TasksSidebar from "../../shared/components/tasks/TasksSidebar.js";
|
|
|
13
13
|
import TasksToolbar from "../../shared/components/tasks/TasksToolbar.js";
|
|
14
14
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
15
15
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
16
|
+
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
16
17
|
import { findWellKnownFolderUid } from "../../shared/mail/findWellKnownFolderUid.js";
|
|
18
|
+
import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
|
|
19
|
+
import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/listAllPages.js";
|
|
17
20
|
const INPUT_CLASS = "text-sm py-1.5 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
18
21
|
export default function TasksPage(props) {
|
|
19
22
|
return (_jsx(TasksShell, { ...props, children: _jsx(TasksContent, {}) }));
|
|
20
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
|
|
26
|
+
* date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
|
|
27
|
+
* timestamp parses identically either way.
|
|
28
|
+
*/
|
|
29
|
+
function parseDueDate(value) {
|
|
30
|
+
return parseISO(value);
|
|
31
|
+
}
|
|
21
32
|
const BUCKET_ORDER = ["Overdue", "Today", "This Week", "Later", "No due date"];
|
|
22
33
|
/** Buckets a task by its due date relative to `now`, matching the Outlook/To-Do convention this list mirrors. */
|
|
23
34
|
function bucketFor(task, now) {
|
|
24
35
|
if (!task.dueDate) {
|
|
25
36
|
return "No due date";
|
|
26
37
|
}
|
|
27
|
-
const due =
|
|
38
|
+
const due = parseDueDate(task.dueDate);
|
|
28
39
|
if (isBefore(due, startOfDay(now))) {
|
|
29
40
|
return "Overdue";
|
|
30
41
|
}
|
|
@@ -44,6 +55,8 @@ const PRIORITY_CLASS = {
|
|
|
44
55
|
};
|
|
45
56
|
function TasksContent() {
|
|
46
57
|
const { folderUid, mailboxUid, userUid, mailboxes } = useTasksShell();
|
|
58
|
+
// The quick-add form's Mailbox choices - view-only shares are left out (see writableMailboxes.ts).
|
|
59
|
+
const writableMailboxes = useWritableMailboxes(mailboxes, userUid, mailboxUid);
|
|
47
60
|
// The quick-add form's chosen mailbox - `undefined` means "the mailbox being viewed".
|
|
48
61
|
const [targetMailboxUid, setTargetMailboxUid] = useState(undefined);
|
|
49
62
|
// Set after a task is added to a different mailbox than the one this list shows.
|
|
@@ -51,6 +64,8 @@ function TasksContent() {
|
|
|
51
64
|
const [tasks, setTasks] = useState([]);
|
|
52
65
|
const [loading, setLoading] = useState(true);
|
|
53
66
|
const [error, setError] = useState(null);
|
|
67
|
+
// Set when `listAllPages()` stopped at its page cap - the list shown isn't every task.
|
|
68
|
+
const [truncated, setTruncated] = useState(false);
|
|
54
69
|
const [title, setTitle] = useState("");
|
|
55
70
|
const [dueDate, setDueDate] = useState("");
|
|
56
71
|
const [priority, setPriority] = useState("normal");
|
|
@@ -62,16 +77,23 @@ function TasksContent() {
|
|
|
62
77
|
const [flaggedMessages, setFlaggedMessages] = useState([]);
|
|
63
78
|
const [flaggedLoading, setFlaggedLoading] = useState(false);
|
|
64
79
|
const [flaggedError, setFlaggedError] = useState(null);
|
|
80
|
+
// What the delete confirmation dialog is asking about - one task (a row's delete button), or every
|
|
81
|
+
// checked task (the toolbar's Delete).
|
|
82
|
+
const [pendingDelete, setPendingDelete] = useState(null);
|
|
65
83
|
function reload() {
|
|
66
84
|
if (!folderUid) {
|
|
67
85
|
setTasks([]);
|
|
86
|
+
setTruncated(false);
|
|
68
87
|
setLoading(false);
|
|
69
88
|
return Promise.resolve();
|
|
70
89
|
}
|
|
71
90
|
setLoading(true);
|
|
72
91
|
setError(null);
|
|
73
|
-
return listTasks(folderUid, { limit:
|
|
74
|
-
.then(
|
|
92
|
+
return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }))
|
|
93
|
+
.then((result) => {
|
|
94
|
+
setTasks(result.items);
|
|
95
|
+
setTruncated(result.truncated);
|
|
96
|
+
})
|
|
75
97
|
.catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load tasks."))
|
|
76
98
|
.finally(() => setLoading(false));
|
|
77
99
|
}
|
|
@@ -115,7 +137,8 @@ function TasksContent() {
|
|
|
115
137
|
mailboxUid: target,
|
|
116
138
|
folderUid: targetFolderUid,
|
|
117
139
|
title: title.trim(),
|
|
118
|
-
|
|
140
|
+
// The date input's "yyyy-MM-dd" is the user's *local* day - see `parseDueDate()`.
|
|
141
|
+
dueDate: dueDate ? parseDueDate(dueDate).toISOString() : undefined,
|
|
119
142
|
priority,
|
|
120
143
|
});
|
|
121
144
|
if (target === mailboxUid) {
|
|
@@ -237,6 +260,19 @@ function TasksContent() {
|
|
|
237
260
|
await reload();
|
|
238
261
|
setError(bulkError);
|
|
239
262
|
}
|
|
263
|
+
function requestDelete(task) {
|
|
264
|
+
setPendingDelete({ type: "single", task });
|
|
265
|
+
}
|
|
266
|
+
function handleConfirmDelete() {
|
|
267
|
+
const pending = pendingDelete;
|
|
268
|
+
setPendingDelete(null);
|
|
269
|
+
if (pending.type === "single") {
|
|
270
|
+
void handleDelete(pending.task);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
void handleBulkDelete();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
240
276
|
const grouped = useMemo(() => {
|
|
241
277
|
const now = new Date();
|
|
242
278
|
const map = new Map(BUCKET_ORDER.map((b) => [b, []]));
|
|
@@ -249,13 +285,17 @@ function TasksContent() {
|
|
|
249
285
|
return map;
|
|
250
286
|
}, [viewFiltered]);
|
|
251
287
|
const completedTasks = viewFiltered.filter((t) => t.completed);
|
|
252
|
-
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx(TasksSidebar, { mailboxUid: mailboxUid, tasks: tasks, userUid: userUid, active: view, onSelect: handleSelectView }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col overflow-y-auto", children: [_jsx(TasksToolbar, { viewMode: viewMode, onViewModeChange: setViewMode, selectedCount: checkedTasks.length, onComplete: handleBulkComplete, onDelete:
|
|
288
|
+
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx(TasksSidebar, { mailboxUid: mailboxUid, tasks: tasks, userUid: userUid, active: view, onSelect: handleSelectView }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col overflow-y-auto", children: [_jsx(TasksToolbar, { viewMode: viewMode, onViewModeChange: setViewMode, selectedCount: checkedTasks.length, onComplete: handleBulkComplete, onDelete: () => setPendingDelete({ type: "bulk" }), onAddToMyDay: handleBulkAddToMyDay }), _jsxs("div", { role: "region", "aria-label": "Tasks list", className: "max-w-2xl mx-auto w-full flex flex-col gap-6 p-6", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide", children: "Tasks" }), error && _jsx(Alert, { children: error }), truncated && view.type !== "flagged" && (_jsxs(Alert, { children: ["This folder has more tasks than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] })), view.type === "flagged" ? (_jsx(FlaggedEmailList, { loading: flaggedLoading, error: flaggedError, messages: flaggedMessages })) : (_jsxs(_Fragment, { children: [_jsxs("form", { onSubmit: handleCreate, className: "flex items-center gap-2 bg-surface border border-border rounded-md p-2", children: [_jsx("input", { type: "text", "aria-label": "Add a task", className: `${INPUT_CLASS} flex-1`, value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Add a task" }), _jsx("input", { type: "date", "aria-label": "Due date", className: INPUT_CLASS, value: dueDate, onChange: (e) => setDueDate(e.target.value) }), _jsxs("select", { "aria-label": "Priority", className: INPUT_CLASS, value: priority, onChange: (e) => setPriority(e.target.value), children: [_jsx("option", { value: "low", children: "Low" }), _jsx("option", { value: "normal", children: "Normal" }), _jsx("option", { value: "high", children: "High" })] }), writableMailboxes.length > 1 && (_jsx("select", { "aria-label": "Task mailbox", className: `${INPUT_CLASS} max-w-40`,
|
|
289
|
+
// `writableMailboxes` only has entries once `mailboxUid` has resolved.
|
|
290
|
+
value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
|
|
253
291
|
const items = grouped.get(bucket);
|
|
254
292
|
if (items.length === 0) {
|
|
255
293
|
return null;
|
|
256
294
|
}
|
|
257
|
-
return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete:
|
|
258
|
-
}), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete:
|
|
295
|
+
return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }, bucket));
|
|
296
|
+
}), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }))] }))] }))] })] }), _jsxs(Modal, { open: pendingDelete !== null, onClose: () => setPendingDelete(null), title: "Delete tasks", children: [_jsx("p", { className: "text-sm mb-5", children: pendingDelete?.type === "single"
|
|
297
|
+
? `Delete "${pendingDelete.task.title}"?`
|
|
298
|
+
: `Delete ${checkedTasks.length} selected ${checkedTasks.length === 1 ? "task" : "tasks"}?` }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setPendingDelete(null), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleConfirmDelete, children: "Delete" })] })] })] }));
|
|
259
299
|
}
|
|
260
300
|
function FlaggedEmailList({ loading, error, messages }) {
|
|
261
301
|
if (error) {
|
|
@@ -278,7 +318,7 @@ function CompletionToggle({ task, onToggle }) {
|
|
|
278
318
|
].join(" "), children: "\u2713" }));
|
|
279
319
|
}
|
|
280
320
|
function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
|
|
281
|
-
return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children:
|
|
321
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children: parseDueDate(task.dueDate).toLocaleDateString() })), _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" })] }, task.uid))) })] }));
|
|
282
322
|
}
|
|
283
323
|
function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
|
|
284
324
|
const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
|
|
@@ -298,5 +338,5 @@ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete })
|
|
|
298
338
|
}
|
|
299
339
|
}
|
|
300
340
|
}
|
|
301
|
-
return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ?
|
|
341
|
+
return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : "" }), _jsx("td", { className: ["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" }) })] }, task.uid))) })] }) }));
|
|
302
342
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rapidmx/web-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "RapidMX's webmail and admin console React UI for web-based clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/rapidmx/web-client",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"./shared/styles/app.css": "./apps/shared/styles/app.css"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "rimraf dist && tsc -p tsconfig.json && shx mkdir -p dist/apps/shared/styles && shx cp apps/shared/styles/app.css dist/apps/shared/styles/app.css",
|
|
20
|
+
"build": "rimraf dist && tsc -p tsconfig.json && shx mkdir -p dist/apps/shared/styles && shx cp apps/shared/styles/app.css dist/apps/shared/styles/app.css && node scripts/checkDistReferences.mjs",
|
|
21
21
|
"lint": "eslint ./apps ./test",
|
|
22
22
|
"test": "vitest run --coverage",
|
|
23
23
|
"test:watch": "vitest --watch --coverage",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@dnd-kit/core": "^6.3.1",
|
|
40
40
|
"@journeyapps/wa-sqlite": "^2.0.4",
|
|
41
41
|
"@peculiar/x509": "^2.1.0",
|
|
42
|
-
"@rapidmx/react-shared": "^0.
|
|
42
|
+
"@rapidmx/react-shared": "^0.6.0",
|
|
43
43
|
"@tiptap/core": "^3.31.3",
|
|
44
44
|
"@tiptap/extension-highlight": "^3.31.3",
|
|
45
45
|
"@tiptap/extension-image": "^3.31.3",
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, { useState } from "react";
|
|
6
|
-
import { BookingAvailabilityWindow } from "@rapidmx/react-shared/booking/bookingApi.js";
|
|
7
|
-
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
8
|
-
|
|
9
|
-
const DAY_LABELS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
10
|
-
const INPUT_CLASS =
|
|
11
|
-
"text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
12
|
-
|
|
13
|
-
function minutesToTimeValue(minutes: number): string {
|
|
14
|
-
const h = Math.floor(minutes / 60)
|
|
15
|
-
.toString()
|
|
16
|
-
.padStart(2, "0");
|
|
17
|
-
const m = (minutes % 60).toString().padStart(2, "0");
|
|
18
|
-
return `${h}:${m}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function timeValueToMinutes(value: string): number {
|
|
22
|
-
const [h, m] = value.split(":").map(Number);
|
|
23
|
-
return h * 60 + m;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AvailabilityEditorProps {
|
|
27
|
-
value: BookingAvailabilityWindow[];
|
|
28
|
-
onChange: (value: BookingAvailabilityWindow[]) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Adds/removes `BookingType.availability` weekly windows — deliberately does not cover `dateOverrides`
|
|
33
|
-
* (per-date blackouts/exceptions), a separate, less commonly needed escape hatch left for a future pass;
|
|
34
|
-
* `BookingUtils.generateCandidateSlots()` already handles an empty `dateOverrides` array gracefully.
|
|
35
|
-
*/
|
|
36
|
-
export default function AvailabilityEditor({ value, onChange }: AvailabilityEditorProps) {
|
|
37
|
-
const [dayOfWeek, setDayOfWeek] = useState(1);
|
|
38
|
-
const [startTime, setStartTime] = useState("09:00");
|
|
39
|
-
const [endTime, setEndTime] = useState("17:00");
|
|
40
|
-
const [error, setError] = useState<string | null>(null);
|
|
41
|
-
|
|
42
|
-
function handleAdd() {
|
|
43
|
-
const startMinute = timeValueToMinutes(startTime);
|
|
44
|
-
const endMinute = timeValueToMinutes(endTime);
|
|
45
|
-
if (startMinute >= endMinute) {
|
|
46
|
-
setError("Start time must be before end time.");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
setError(null);
|
|
50
|
-
onChange([...value, { dayOfWeek, startMinute, endMinute }]);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function handleRemove(index: number) {
|
|
54
|
-
onChange(value.filter((_, i) => i !== index));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div className="flex flex-col gap-3">
|
|
59
|
-
{error && <p className="text-sm text-danger">{error}</p>}
|
|
60
|
-
{value.length === 0 ? (
|
|
61
|
-
<p className="text-sm text-text-muted">No availability windows yet — add one below.</p>
|
|
62
|
-
) : (
|
|
63
|
-
<ul className="flex flex-col gap-2">
|
|
64
|
-
{value.map((window, index) => (
|
|
65
|
-
<li
|
|
66
|
-
key={index}
|
|
67
|
-
className="flex items-center justify-between gap-3 py-2 px-3 border border-border rounded-sm text-sm"
|
|
68
|
-
>
|
|
69
|
-
<span>
|
|
70
|
-
{DAY_LABELS[window.dayOfWeek]} {minutesToTimeValue(window.startMinute)}–
|
|
71
|
-
{minutesToTimeValue(window.endMinute)}
|
|
72
|
-
</span>
|
|
73
|
-
<button
|
|
74
|
-
type="button"
|
|
75
|
-
onClick={() => handleRemove(index)}
|
|
76
|
-
className="text-sm text-danger hover:underline"
|
|
77
|
-
>
|
|
78
|
-
Remove
|
|
79
|
-
</button>
|
|
80
|
-
</li>
|
|
81
|
-
))}
|
|
82
|
-
</ul>
|
|
83
|
-
)}
|
|
84
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
85
|
-
<select
|
|
86
|
-
aria-label="Day of week"
|
|
87
|
-
value={dayOfWeek}
|
|
88
|
-
onChange={(e) => setDayOfWeek(Number(e.target.value))}
|
|
89
|
-
className={INPUT_CLASS}
|
|
90
|
-
>
|
|
91
|
-
{DAY_LABELS.map((label, i) => (
|
|
92
|
-
<option key={label} value={i}>
|
|
93
|
-
{label}
|
|
94
|
-
</option>
|
|
95
|
-
))}
|
|
96
|
-
</select>
|
|
97
|
-
<input
|
|
98
|
-
type="time"
|
|
99
|
-
aria-label="Start time"
|
|
100
|
-
value={startTime}
|
|
101
|
-
onChange={(e) => setStartTime(e.target.value)}
|
|
102
|
-
className={INPUT_CLASS}
|
|
103
|
-
/>
|
|
104
|
-
<span className="text-sm text-text-muted">to</span>
|
|
105
|
-
<input
|
|
106
|
-
type="time"
|
|
107
|
-
aria-label="End time"
|
|
108
|
-
value={endTime}
|
|
109
|
-
onChange={(e) => setEndTime(e.target.value)}
|
|
110
|
-
className={INPUT_CLASS}
|
|
111
|
-
/>
|
|
112
|
-
<Button type="button" variant="secondary" className="!w-auto" onClick={handleAdd}>
|
|
113
|
-
Add window
|
|
114
|
-
</Button>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, { FormEvent, useEffect, useState } from "react";
|
|
6
|
-
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
7
|
-
import {
|
|
8
|
-
BookingAvailabilityWindow,
|
|
9
|
-
BookingType,
|
|
10
|
-
deleteBookingType,
|
|
11
|
-
getBookingType,
|
|
12
|
-
updateBookingType,
|
|
13
|
-
} from "@rapidmx/react-shared/booking/bookingApi.js";
|
|
14
|
-
import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
15
|
-
import AvailabilityEditor from "../../../shared/components/booking/AvailabilityEditor.js";
|
|
16
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
17
|
-
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
18
|
-
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
19
|
-
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
20
|
-
|
|
21
|
-
const INPUT_CLASS =
|
|
22
|
-
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
23
|
-
|
|
24
|
-
export type BookingTypeDetailPageProps = Omit<SettingsShellProps, "active"> & { params: { uid: string } };
|
|
25
|
-
|
|
26
|
-
export default function BookingTypeDetailPage(props: BookingTypeDetailPageProps) {
|
|
27
|
-
return (
|
|
28
|
-
<SettingsShell {...props} active="booking-types">
|
|
29
|
-
<BookingTypeDetailContent uid={props.params.uid} />
|
|
30
|
-
</SettingsShell>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function BookingTypeDetailContent({ uid }: { uid: string }) {
|
|
35
|
-
const { mailboxUid } = useSettingsShell();
|
|
36
|
-
const [bookingType, setBookingType] = useState<BookingType | null>(null);
|
|
37
|
-
const [name, setName] = useState("");
|
|
38
|
-
const [description, setDescription] = useState("");
|
|
39
|
-
const [hostDisplayName, setHostDisplayName] = useState("");
|
|
40
|
-
const [durationMinutes, setDurationMinutes] = useState(30);
|
|
41
|
-
const [timezone, setTimezone] = useState("");
|
|
42
|
-
const [availability, setAvailability] = useState<BookingAvailabilityWindow[]>([]);
|
|
43
|
-
const [minimumNoticeMinutes, setMinimumNoticeMinutes] = useState(60);
|
|
44
|
-
const [bookingWindowDays, setBookingWindowDays] = useState(30);
|
|
45
|
-
const [requiresApproval, setRequiresApproval] = useState(false);
|
|
46
|
-
const [enabled, setEnabled] = useState(true);
|
|
47
|
-
const [loading, setLoading] = useState(true);
|
|
48
|
-
const [error, setError] = useState<string | null>(null);
|
|
49
|
-
const [saving, setSaving] = useState(false);
|
|
50
|
-
const [saved, setSaved] = useState(false);
|
|
51
|
-
const [copied, setCopied] = useState(false);
|
|
52
|
-
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
53
|
-
const [deleting, setDeleting] = useState(false);
|
|
54
|
-
const [deleteError, setDeleteError] = useState<string | null>(null);
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
setLoading(true);
|
|
58
|
-
setError(null);
|
|
59
|
-
getBookingType(uid)
|
|
60
|
-
.then((bt) => {
|
|
61
|
-
setBookingType(bt);
|
|
62
|
-
setName(bt.name);
|
|
63
|
-
setDescription(bt.description ?? "");
|
|
64
|
-
setHostDisplayName(bt.hostDisplayName);
|
|
65
|
-
setDurationMinutes(bt.durationMinutes);
|
|
66
|
-
setTimezone(bt.timezone);
|
|
67
|
-
setAvailability(bt.availability);
|
|
68
|
-
setMinimumNoticeMinutes(bt.minimumNoticeMinutes);
|
|
69
|
-
setBookingWindowDays(bt.bookingWindowDays);
|
|
70
|
-
setRequiresApproval(bt.requiresApproval);
|
|
71
|
-
setEnabled(bt.enabled);
|
|
72
|
-
})
|
|
73
|
-
.catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this booking link."))
|
|
74
|
-
.finally(() => setLoading(false));
|
|
75
|
-
}, [uid]);
|
|
76
|
-
|
|
77
|
-
async function handleSubmit(e: FormEvent) {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
setError(null);
|
|
80
|
-
setSaved(false);
|
|
81
|
-
setSaving(true);
|
|
82
|
-
try {
|
|
83
|
-
const updated = await updateBookingType({
|
|
84
|
-
uid: bookingType!.uid,
|
|
85
|
-
version: bookingType!.version,
|
|
86
|
-
name,
|
|
87
|
-
description: description.trim() || undefined,
|
|
88
|
-
hostDisplayName,
|
|
89
|
-
durationMinutes,
|
|
90
|
-
timezone,
|
|
91
|
-
availability,
|
|
92
|
-
minimumNoticeMinutes,
|
|
93
|
-
bookingWindowDays,
|
|
94
|
-
requiresApproval,
|
|
95
|
-
enabled,
|
|
96
|
-
});
|
|
97
|
-
setBookingType(updated);
|
|
98
|
-
setSaved(true);
|
|
99
|
-
} catch (err) {
|
|
100
|
-
setError(err instanceof ApiRequestError ? err.message : "Could not save this booking link.");
|
|
101
|
-
} finally {
|
|
102
|
-
setSaving(false);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function handleCopy(value: string) {
|
|
107
|
-
try {
|
|
108
|
-
await navigator.clipboard.writeText(value);
|
|
109
|
-
setCopied(true);
|
|
110
|
-
setTimeout(() => setCopied(false), 2000);
|
|
111
|
-
} catch {
|
|
112
|
-
// Clipboard access can be denied by the browser — the value is still selectable/copyable by hand.
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function handleDelete() {
|
|
117
|
-
setDeleting(true);
|
|
118
|
-
setDeleteError(null);
|
|
119
|
-
try {
|
|
120
|
-
await deleteBookingType(bookingType!.uid, bookingType!.version);
|
|
121
|
-
window.location.href = `/settings/booking-types?mailboxUid=${encodeURIComponent(mailboxUid!)}`;
|
|
122
|
-
} catch (err) {
|
|
123
|
-
setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this booking link.");
|
|
124
|
-
setDeleting(false);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (loading) {
|
|
129
|
-
return <p className="p-6 text-sm text-text-muted">Loading…</p>;
|
|
130
|
-
}
|
|
131
|
-
if (error || !bookingType) {
|
|
132
|
-
// `error` is always set whenever `bookingType` is falsy here: the load effect above destructures
|
|
133
|
-
// every field off the resolved value directly (`bt.name`, `bt.hostDisplayName`, ...), so a
|
|
134
|
-
// successful-but-empty response throws into `.catch()` — which sets `error` — before this
|
|
135
|
-
// component ever renders past the `loading` guard above. `!bookingType` alone (with `error` still
|
|
136
|
-
// `null`) is therefore unreachable, unlike e.g. `messages/[uid].tsx`'s identical-looking check,
|
|
137
|
-
// which stores its fetched value whole rather than destructuring it.
|
|
138
|
-
return (
|
|
139
|
-
<div className="p-6">
|
|
140
|
-
<Alert>{error!}</Alert>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// `bookingType` only ever becomes non-null via the client-only fetch above, so by the time this line
|
|
146
|
-
// runs `window` is always defined — the `!bookingType` early return just above covers every real SSR
|
|
147
|
-
// pass, where `bookingType` is still `null`.
|
|
148
|
-
const publicUrl = `${window.location.origin}/book/${encodeURIComponent(bookingType.slug)}`;
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<div className="flex-1 min-w-0 overflow-y-auto p-6">
|
|
152
|
-
<div className="max-w-2xl">
|
|
153
|
-
<div className="flex items-start justify-between gap-4 mb-1">
|
|
154
|
-
<div>
|
|
155
|
-
<a
|
|
156
|
-
href={`/settings/booking-types?mailboxUid=${encodeURIComponent(mailboxUid!)}`}
|
|
157
|
-
className="text-sm text-primary-dark hover:underline"
|
|
158
|
-
>
|
|
159
|
-
← All booking links
|
|
160
|
-
</a>
|
|
161
|
-
<h1 className="text-xl font-bold tracking-tight mt-1">{bookingType.name}</h1>
|
|
162
|
-
</div>
|
|
163
|
-
<Button
|
|
164
|
-
type="button"
|
|
165
|
-
variant="secondary"
|
|
166
|
-
className="!w-auto shrink-0 !border-danger !text-danger hover:!border-danger hover:!text-danger"
|
|
167
|
-
onClick={() => setConfirmingDelete(true)}
|
|
168
|
-
>
|
|
169
|
-
Delete
|
|
170
|
-
</Button>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div className="flex items-center gap-2 mb-5">
|
|
174
|
-
<code className="flex-1 text-xs bg-surface-alt border border-border rounded-sm py-2 px-3 overflow-x-auto whitespace-nowrap">
|
|
175
|
-
{publicUrl}
|
|
176
|
-
</code>
|
|
177
|
-
<Button type="button" variant="secondary" className="!w-auto shrink-0" onClick={() => handleCopy(publicUrl)}>
|
|
178
|
-
{copied ? "Copied" : "Copy"}
|
|
179
|
-
</Button>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
{saved && !error && <div className="mb-4 text-sm text-success font-medium">Saved.</div>}
|
|
183
|
-
|
|
184
|
-
<form onSubmit={handleSubmit} className="flex flex-col gap-1">
|
|
185
|
-
<label className="flex items-center gap-2 text-sm mb-3">
|
|
186
|
-
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
|
|
187
|
-
Enabled (publicly bookable)
|
|
188
|
-
</label>
|
|
189
|
-
<FormField label="Name" htmlFor="name">
|
|
190
|
-
<input id="name" type="text" className={INPUT_CLASS} value={name} onChange={(e) => setName(e.target.value)} />
|
|
191
|
-
</FormField>
|
|
192
|
-
<FormField label="Description (optional)" htmlFor="description">
|
|
193
|
-
<textarea
|
|
194
|
-
id="description"
|
|
195
|
-
className={INPUT_CLASS}
|
|
196
|
-
rows={2}
|
|
197
|
-
value={description}
|
|
198
|
-
onChange={(e) => setDescription(e.target.value)}
|
|
199
|
-
/>
|
|
200
|
-
</FormField>
|
|
201
|
-
<FormField label="Host name shown to visitors" htmlFor="hostDisplayName">
|
|
202
|
-
<input
|
|
203
|
-
id="hostDisplayName"
|
|
204
|
-
type="text"
|
|
205
|
-
className={INPUT_CLASS}
|
|
206
|
-
value={hostDisplayName}
|
|
207
|
-
onChange={(e) => setHostDisplayName(e.target.value)}
|
|
208
|
-
/>
|
|
209
|
-
</FormField>
|
|
210
|
-
<div className="grid grid-cols-2 gap-3">
|
|
211
|
-
<FormField label="Duration (minutes)" htmlFor="durationMinutes">
|
|
212
|
-
<input
|
|
213
|
-
id="durationMinutes"
|
|
214
|
-
type="number"
|
|
215
|
-
min={1}
|
|
216
|
-
className={INPUT_CLASS}
|
|
217
|
-
value={durationMinutes}
|
|
218
|
-
onChange={(e) => setDurationMinutes(Number(e.target.value))}
|
|
219
|
-
/>
|
|
220
|
-
</FormField>
|
|
221
|
-
<FormField label="Timezone" htmlFor="timezone">
|
|
222
|
-
<input
|
|
223
|
-
id="timezone"
|
|
224
|
-
type="text"
|
|
225
|
-
className={INPUT_CLASS}
|
|
226
|
-
value={timezone}
|
|
227
|
-
onChange={(e) => setTimezone(e.target.value)}
|
|
228
|
-
/>
|
|
229
|
-
</FormField>
|
|
230
|
-
</div>
|
|
231
|
-
<div className="grid grid-cols-2 gap-3">
|
|
232
|
-
<FormField label="Minimum notice (minutes)" htmlFor="minimumNoticeMinutes">
|
|
233
|
-
<input
|
|
234
|
-
id="minimumNoticeMinutes"
|
|
235
|
-
type="number"
|
|
236
|
-
min={0}
|
|
237
|
-
className={INPUT_CLASS}
|
|
238
|
-
value={minimumNoticeMinutes}
|
|
239
|
-
onChange={(e) => setMinimumNoticeMinutes(Number(e.target.value))}
|
|
240
|
-
/>
|
|
241
|
-
</FormField>
|
|
242
|
-
<FormField label="Booking window (days ahead)" htmlFor="bookingWindowDays">
|
|
243
|
-
<input
|
|
244
|
-
id="bookingWindowDays"
|
|
245
|
-
type="number"
|
|
246
|
-
min={1}
|
|
247
|
-
className={INPUT_CLASS}
|
|
248
|
-
value={bookingWindowDays}
|
|
249
|
-
onChange={(e) => setBookingWindowDays(Number(e.target.value))}
|
|
250
|
-
/>
|
|
251
|
-
</FormField>
|
|
252
|
-
</div>
|
|
253
|
-
<label className="flex items-center gap-2 text-sm my-3">
|
|
254
|
-
<input type="checkbox" checked={requiresApproval} onChange={(e) => setRequiresApproval(e.target.checked)} />
|
|
255
|
-
Require my approval before confirming a booking
|
|
256
|
-
</label>
|
|
257
|
-
|
|
258
|
-
<div className="mb-4">
|
|
259
|
-
<span className="block text-sm font-semibold mb-1.5 text-text">Weekly availability</span>
|
|
260
|
-
<AvailabilityEditor value={availability} onChange={setAvailability} />
|
|
261
|
-
</div>
|
|
262
|
-
|
|
263
|
-
<div>
|
|
264
|
-
<Button type="submit" loading={saving} disabled={saving} className="!w-auto">
|
|
265
|
-
Save
|
|
266
|
-
</Button>
|
|
267
|
-
</div>
|
|
268
|
-
</form>
|
|
269
|
-
|
|
270
|
-
<Modal open={confirmingDelete} onClose={() => setConfirmingDelete(false)} title="Delete booking link">
|
|
271
|
-
<p className="text-sm mb-5">
|
|
272
|
-
Are you sure you want to delete <strong>{bookingType.name}</strong>? Its public link will stop
|
|
273
|
-
working immediately. This cannot be undone.
|
|
274
|
-
</p>
|
|
275
|
-
{deleteError && <Alert>{deleteError}</Alert>}
|
|
276
|
-
<div className="flex gap-3 justify-end mt-5">
|
|
277
|
-
<Button
|
|
278
|
-
type="button"
|
|
279
|
-
variant="secondary"
|
|
280
|
-
className="!w-auto"
|
|
281
|
-
disabled={deleting}
|
|
282
|
-
onClick={() => setConfirmingDelete(false)}
|
|
283
|
-
>
|
|
284
|
-
Cancel
|
|
285
|
-
</Button>
|
|
286
|
-
<Button
|
|
287
|
-
type="button"
|
|
288
|
-
className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger"
|
|
289
|
-
loading={deleting}
|
|
290
|
-
disabled={deleting}
|
|
291
|
-
onClick={handleDelete}
|
|
292
|
-
>
|
|
293
|
-
Delete
|
|
294
|
-
</Button>
|
|
295
|
-
</div>
|
|
296
|
-
</Modal>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
);
|
|
300
|
-
}
|