@rapidmx/web-client 0.14.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +373 -373
- package/apps/admin/branding/index.tsx +39 -39
- package/apps/admin/data-requests/index.tsx +482 -480
- package/apps/admin/domains/[uid].tsx +164 -163
- package/apps/admin/escrow-scopes/[uid].tsx +348 -348
- package/apps/admin/index.tsx +127 -124
- package/apps/admin/mailboxes/[uid].tsx +269 -224
- package/apps/admin/mailboxes/new/index.tsx +28 -28
- package/apps/admin/plugins/index.tsx +15 -15
- package/apps/admin/retention-policy/index.tsx +39 -39
- package/apps/admin/signing-certificates/index.tsx +343 -343
- package/apps/escrow/audit-log/index.tsx +196 -196
- package/apps/escrow/matters/[uid].tsx +621 -621
- package/apps/shared/appearance/AppearanceForm.tsx +9 -1
- package/apps/shared/auth/adminAccess.ts +99 -99
- package/apps/shared/calendar/bookingPlugin.ts +100 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +378 -346
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
- package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +53 -4
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
- package/apps/shared/components/admin/settings/PluginsManager.tsx +64 -10
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
- package/apps/shared/components/admin/setup/SetupWizard.tsx +444 -444
- package/apps/shared/components/admin/usePagedList.tsx +129 -129
- package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
- package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
- package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
- package/apps/shared/components/calendar/EventDetails.tsx +401 -0
- package/apps/shared/components/calendar/EventEditor.tsx +635 -0
- package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
- package/apps/shared/components/calendar/EventFormParts.tsx +591 -0
- package/apps/shared/components/calendar/EventModal.tsx +206 -946
- package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
- package/apps/shared/components/calendar/EventShell.tsx +202 -0
- package/apps/shared/components/calendar/FindATime.tsx +379 -0
- package/apps/shared/components/calendar/GuestInput.tsx +100 -0
- package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
- package/apps/shared/components/calendar/MonthView.tsx +185 -173
- package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
- package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
- package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -222
- package/apps/shared/components/calendar/RequestChangeForm.tsx +240 -0
- package/apps/shared/components/calendar/SplitDayView.tsx +144 -143
- package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
- package/apps/shared/components/calendar/TimeGridView.tsx +246 -244
- package/apps/shared/components/calendar/allDay.ts +124 -124
- package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
- package/apps/shared/components/calendar/eventForm.ts +108 -0
- package/apps/shared/components/calendar/eventFormat.ts +210 -0
- package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
- package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
- package/apps/shared/components/contacts/ContactForm.tsx +383 -383
- package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
- package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +217 -216
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
- package/apps/shared/components/layout/AppShell.tsx +480 -480
- package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
- package/apps/shared/components/layout/UserMenu.tsx +439 -372
- package/apps/shared/components/mail/ConversationList.tsx +323 -271
- package/apps/shared/components/mail/ConversationThreadPane.tsx +130 -5
- package/apps/shared/components/mail/InviteCard.tsx +387 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
- package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1537
- package/apps/shared/components/mail/SwipeRow.tsx +120 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1673
- package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
- package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
- package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
- package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
- package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
- package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
- package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
- package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +546 -490
- package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
- package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -125
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
- package/apps/shared/components/settings/layout/SettingsShell.tsx +255 -253
- package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
- package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
- package/apps/shared/gestures/useEnterSlide.ts +54 -0
- package/apps/shared/gestures/useSwipe.ts +114 -0
- package/apps/shared/gestures/useSwipeSlide.ts +113 -0
- package/apps/shared/gestures/useWheelPaging.ts +107 -0
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
- package/apps/shared/keyboard/dispatch.ts +124 -124
- package/apps/shared/keyboard/format.ts +89 -89
- package/apps/shared/keyboard/keymap.ts +114 -114
- package/apps/shared/keyboard/registry.ts +65 -65
- package/apps/shared/keyboard/targets.ts +79 -79
- package/apps/shared/mail/folderCounts.ts +17 -0
- package/apps/shared/mail/folderTree.ts +143 -143
- package/apps/shared/mail/listAllPages.ts +39 -39
- package/apps/shared/mail/newMailNotifications.ts +3 -15
- package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
- package/apps/shared/mail/outbox/sendJob.ts +19 -1
- package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
- package/apps/shared/mail/primaryMailbox.ts +71 -0
- package/apps/shared/mail/useCollapsedSections.ts +146 -0
- package/apps/shared/mail/useMailConnection.ts +205 -203
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
- package/apps/shared/mail/useMarkMessageRead.ts +47 -47
- package/apps/shared/mail/useNewMailNotifications.ts +2 -2
- package/apps/shared/navigation/AppRouter.tsx +300 -300
- package/apps/shared/navigation/routerContext.tsx +83 -83
- package/apps/shared/notifications/preferences.ts +36 -0
- package/apps/shared/notifications/store.ts +9 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
- package/apps/shared/search/crossMailboxSearch.ts +169 -0
- package/apps/shared/search/localIndexBuilder.ts +481 -481
- package/apps/shared/signing/enrollmentStorage.ts +33 -33
- package/apps/shared/signing/enrollmentTracker.ts +385 -385
- package/apps/shared/signing/enrollmentView.ts +251 -251
- package/apps/shared/signing/useNow.ts +19 -19
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
- package/apps/shared/styles/app.css +396 -386
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +578 -469
- package/apps/www/contacts/[uid].tsx +109 -109
- package/apps/www/contacts/index.tsx +6 -0
- package/apps/www/index.tsx +2847 -2332
- package/apps/www/messages/[uid].tsx +135 -108
- package/apps/www/settings/auto-reply/index.tsx +133 -133
- package/apps/www/settings/encryption/index.tsx +1287 -1287
- package/apps/www/settings/filters/[uid].tsx +176 -176
- package/apps/www/settings/filters/index.tsx +102 -102
- package/apps/www/settings/filters/new/index.tsx +140 -140
- package/apps/www/settings/labels/index.tsx +204 -204
- package/apps/www/settings/privacy/index.tsx +492 -492
- package/apps/www/settings/profile/index.tsx +248 -0
- package/apps/www/settings/read-receipts/index.tsx +147 -147
- package/apps/www/settings/sharing/index.tsx +256 -256
- package/apps/www/settings/signatures/[uid].tsx +172 -172
- package/apps/www/settings/signatures/index.tsx +88 -88
- package/apps/www/settings/signatures/new/index.tsx +135 -135
- package/apps/www/tasks/index.tsx +649 -649
- package/dist/apps/admin/data-requests/index.js +1 -1
- package/dist/apps/admin/domains/[uid].js +4 -2
- package/dist/apps/admin/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +12 -2
- package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
- package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
- package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +28 -4
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
- package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
- package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
- package/dist/apps/shared/components/calendar/EventEditor.js +544 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +70 -0
- package/dist/apps/shared/components/calendar/EventFormParts.js +191 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
- package/dist/apps/shared/components/calendar/EventModal.js +57 -459
- package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
- package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
- package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
- package/dist/apps/shared/components/calendar/EventShell.js +134 -0
- package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
- package/dist/apps/shared/components/calendar/FindATime.js +190 -0
- package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
- package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
- package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
- package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
- package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
- package/dist/apps/shared/components/calendar/MonthView.js +11 -5
- package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
- package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +127 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
- package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
- package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
- package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
- package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
- package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
- package/dist/apps/shared/components/calendar/eventForm.js +1 -0
- package/dist/apps/shared/components/calendar/eventFormat.d.ts +80 -0
- package/dist/apps/shared/components/calendar/eventFormat.js +190 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
- package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
- package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -3
- package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
- package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +14 -9
- package/dist/apps/shared/components/layout/UserMenu.js +74 -26
- package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
- package/dist/apps/shared/components/mail/ConversationList.js +21 -4
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
- package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
- package/dist/apps/shared/components/mail/InviteCard.js +150 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
- package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
- package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
- package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +16 -7
- package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
- package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
- package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
- package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
- package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +10 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +76 -46
- package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
- package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
- package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
- package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +7 -5
- package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
- package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
- package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
- package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
- package/dist/apps/shared/gestures/useSwipe.js +90 -0
- package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
- package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
- package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
- package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
- package/dist/apps/shared/mail/folderCounts.js +12 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
- package/dist/apps/shared/mail/newMailNotifications.js +3 -12
- package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
- package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
- package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
- package/dist/apps/shared/mail/primaryMailbox.js +61 -0
- package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
- package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
- package/dist/apps/shared/mail/useMailConnection.js +3 -1
- package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
- package/dist/apps/shared/notifications/preferences.d.ts +13 -0
- package/dist/apps/shared/notifications/preferences.js +36 -0
- package/dist/apps/shared/notifications/store.d.ts +1 -0
- package/dist/apps/shared/notifications/store.js +7 -23
- package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
- package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
- package/dist/apps/shared/styles/app.css +396 -386
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +78 -7
- package/dist/apps/www/contacts/index.js +3 -1
- package/dist/apps/www/index.js +548 -210
- package/dist/apps/www/messages/[uid].js +13 -5
- package/dist/apps/www/settings/profile/index.d.ts +4 -0
- package/dist/apps/www/settings/profile/index.js +124 -0
- package/package.json +2 -2
|
@@ -75,7 +75,15 @@ export default function AppearanceForm() {
|
|
|
75
75
|
|
|
76
76
|
// The kind the radio shows: chosen before it can take effect ("Image" is chosen before there is one).
|
|
77
77
|
const [kind, setKind] = useState<BackgroundKind>(background?.kind ?? "none");
|
|
78
|
-
|
|
78
|
+
// Follows a change of the stored kind (the server's answer, another tab, a reset) - not the first render, which `useState()` above
|
|
79
|
+
// already started from: that effect can run after a choice made right after the page appeared, and would put the old kind back.
|
|
80
|
+
const shownKind = useRef(background?.kind);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (shownKind.current !== background?.kind) {
|
|
83
|
+
shownKind.current = background?.kind;
|
|
84
|
+
setKind(background?.kind ?? "none");
|
|
85
|
+
}
|
|
86
|
+
}, [background?.kind]);
|
|
79
87
|
const [fileError, setFileError] = useState<string | null>(null);
|
|
80
88
|
const [dragging, setDragging] = useState(false);
|
|
81
89
|
const [uploading, setUploading] = useState<string | null>(null);
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import { authApiFetch } from "@rapidmx/react-shared/util/api.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Whether the signed-in user holds an administrator role, learned without elevating.
|
|
9
|
-
*
|
|
10
|
-
* The JWT the browser carries can't say: auth-server strips every trusted role from a token that isn't elevated
|
|
11
|
-
* (`TokenUtils.resolveTokenUser()`), so `trusted` (from the JWT) is false for a real administrator until they confirm
|
|
12
|
-
* their identity on auth-server's elevation page. The role itself lives on the caller's own `User` record, which they may
|
|
13
|
-
* always read - `GET {authServerUrl}/api/users/me` (the same call auth-server's own UI makes "to check for admin access") -
|
|
14
|
-
* so the answer is read from there, with the same cross-origin credentials the profile lookups use.
|
|
15
|
-
*
|
|
16
|
-
* This only decides whether a menu item is shown. The `/admin` pages and API stay authoritative: they check the role and
|
|
17
|
-
* the elevation themselves, and the link only navigates (`/admin` then runs the elevation redirect), so a wrong answer
|
|
18
|
-
* - a stale cache, a tampered `sessionStorage` - can show a link that leads to "no administrator access", never more.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** The roles the server treats as trusted when it isn't configured otherwise (`trusted_roles`, default `["admin"]`). */
|
|
22
|
-
export const DEFAULT_TRUSTED_ROLES: readonly string[] = ["admin"];
|
|
23
|
-
|
|
24
|
-
/** `sessionStorage` key prefix of a remembered answer; the user's uid follows, so another account signing in in the same
|
|
25
|
-
* tab never inherits it. */
|
|
26
|
-
export const ADMIN_ACCESS_KEY_PREFIX = "rapidmx-admin-access:";
|
|
27
|
-
|
|
28
|
-
/** How long a remembered answer is trusted - long enough that navigating between the apps costs no request, short enough
|
|
29
|
-
* that a role granted or removed shows up within the sitting. */
|
|
30
|
-
export const ADMIN_ACCESS_TTL_MS = 30 * 60 * 1000;
|
|
31
|
-
|
|
32
|
-
/** Lookups in flight, by key, so two components asking at once make one request. */
|
|
33
|
-
const lookups = new Map<string, Promise<boolean | undefined>>();
|
|
34
|
-
|
|
35
|
-
/** Forgets the lookups in flight (tests). */
|
|
36
|
-
export function resetAdminAccessLookups(): void {
|
|
37
|
-
lookups.clear();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function readRemembered(key: string, now: number): boolean | undefined {
|
|
41
|
-
try {
|
|
42
|
-
const stored = JSON.parse(sessionStorage.getItem(key) ?? "null") as { admin?: unknown; at?: unknown } | null;
|
|
43
|
-
if (typeof stored?.admin === "boolean" && typeof stored.at === "number" && now - stored.at >= 0 && now - stored.at < ADMIN_ACCESS_TTL_MS) {
|
|
44
|
-
return stored.admin;
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Unreadable, or storage unavailable - ask again.
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function remember(key: string, admin: boolean, now: number): void {
|
|
53
|
-
try {
|
|
54
|
-
sessionStorage.setItem(key, JSON.stringify({ admin, at: now }));
|
|
55
|
-
} catch {
|
|
56
|
-
// Storage unavailable - the next page load asks again.
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Resolves `true` when `userUid`'s own `User` record on auth-server carries one of `trustedRoles`, `false` when it
|
|
62
|
-
* doesn't, and `undefined` when that couldn't be established (unreachable, blocked by CORS, not signed in, an unexpected
|
|
63
|
-
* body, or a record that isn't this user's). Only a real answer is remembered; a failure is asked again on the next page.
|
|
64
|
-
* Never rejects.
|
|
65
|
-
*/
|
|
66
|
-
export function lookUpAdminAccess(
|
|
67
|
-
authServerUrl: string,
|
|
68
|
-
userUid: string,
|
|
69
|
-
trustedRoles: readonly string[] = DEFAULT_TRUSTED_ROLES,
|
|
70
|
-
now: number = Date.now(),
|
|
71
|
-
): Promise<boolean | undefined> {
|
|
72
|
-
const key = `${ADMIN_ACCESS_KEY_PREFIX}${userUid}`;
|
|
73
|
-
const remembered = readRemembered(key, now);
|
|
74
|
-
if (remembered !== undefined) {
|
|
75
|
-
return Promise.resolve(remembered);
|
|
76
|
-
}
|
|
77
|
-
const inFlight = lookups.get(key);
|
|
78
|
-
if (inFlight) {
|
|
79
|
-
return inFlight;
|
|
80
|
-
}
|
|
81
|
-
const lookup = (async (): Promise<boolean | undefined> => {
|
|
82
|
-
try {
|
|
83
|
-
const user = await authApiFetch<{ uid?: unknown; roles?: unknown } | null>(authServerUrl, "/users/me");
|
|
84
|
-
if (!user || user.uid !== userUid || !Array.isArray(user.roles)) {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
const admin = user.roles.some((role) => typeof role === "string" && trustedRoles.includes(role));
|
|
88
|
-
remember(key, admin, now);
|
|
89
|
-
return admin;
|
|
90
|
-
} catch {
|
|
91
|
-
return undefined;
|
|
92
|
-
} finally {
|
|
93
|
-
// Answered: what was learned is in storage; a failure may be asked again.
|
|
94
|
-
lookups.delete(key);
|
|
95
|
-
}
|
|
96
|
-
})();
|
|
97
|
-
lookups.set(key, lookup);
|
|
98
|
-
return lookup;
|
|
99
|
-
}
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { authApiFetch } from "@rapidmx/react-shared/util/api.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Whether the signed-in user holds an administrator role, learned without elevating.
|
|
9
|
+
*
|
|
10
|
+
* The JWT the browser carries can't say: auth-server strips every trusted role from a token that isn't elevated
|
|
11
|
+
* (`TokenUtils.resolveTokenUser()`), so `trusted` (from the JWT) is false for a real administrator until they confirm
|
|
12
|
+
* their identity on auth-server's elevation page. The role itself lives on the caller's own `User` record, which they may
|
|
13
|
+
* always read - `GET {authServerUrl}/api/users/me` (the same call auth-server's own UI makes "to check for admin access") -
|
|
14
|
+
* so the answer is read from there, with the same cross-origin credentials the profile lookups use.
|
|
15
|
+
*
|
|
16
|
+
* This only decides whether a menu item is shown. The `/admin` pages and API stay authoritative: they check the role and
|
|
17
|
+
* the elevation themselves, and the link only navigates (`/admin` then runs the elevation redirect), so a wrong answer
|
|
18
|
+
* - a stale cache, a tampered `sessionStorage` - can show a link that leads to "no administrator access", never more.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The roles the server treats as trusted when it isn't configured otherwise (`trusted_roles`, default `["admin"]`). */
|
|
22
|
+
export const DEFAULT_TRUSTED_ROLES: readonly string[] = ["admin"];
|
|
23
|
+
|
|
24
|
+
/** `sessionStorage` key prefix of a remembered answer; the user's uid follows, so another account signing in in the same
|
|
25
|
+
* tab never inherits it. */
|
|
26
|
+
export const ADMIN_ACCESS_KEY_PREFIX = "rapidmx-admin-access:";
|
|
27
|
+
|
|
28
|
+
/** How long a remembered answer is trusted - long enough that navigating between the apps costs no request, short enough
|
|
29
|
+
* that a role granted or removed shows up within the sitting. */
|
|
30
|
+
export const ADMIN_ACCESS_TTL_MS = 30 * 60 * 1000;
|
|
31
|
+
|
|
32
|
+
/** Lookups in flight, by key, so two components asking at once make one request. */
|
|
33
|
+
const lookups = new Map<string, Promise<boolean | undefined>>();
|
|
34
|
+
|
|
35
|
+
/** Forgets the lookups in flight (tests). */
|
|
36
|
+
export function resetAdminAccessLookups(): void {
|
|
37
|
+
lookups.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readRemembered(key: string, now: number): boolean | undefined {
|
|
41
|
+
try {
|
|
42
|
+
const stored = JSON.parse(sessionStorage.getItem(key) ?? "null") as { admin?: unknown; at?: unknown } | null;
|
|
43
|
+
if (typeof stored?.admin === "boolean" && typeof stored.at === "number" && now - stored.at >= 0 && now - stored.at < ADMIN_ACCESS_TTL_MS) {
|
|
44
|
+
return stored.admin;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Unreadable, or storage unavailable - ask again.
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function remember(key: string, admin: boolean, now: number): void {
|
|
53
|
+
try {
|
|
54
|
+
sessionStorage.setItem(key, JSON.stringify({ admin, at: now }));
|
|
55
|
+
} catch {
|
|
56
|
+
// Storage unavailable - the next page load asks again.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolves `true` when `userUid`'s own `User` record on auth-server carries one of `trustedRoles`, `false` when it
|
|
62
|
+
* doesn't, and `undefined` when that couldn't be established (unreachable, blocked by CORS, not signed in, an unexpected
|
|
63
|
+
* body, or a record that isn't this user's). Only a real answer is remembered; a failure is asked again on the next page.
|
|
64
|
+
* Never rejects.
|
|
65
|
+
*/
|
|
66
|
+
export function lookUpAdminAccess(
|
|
67
|
+
authServerUrl: string,
|
|
68
|
+
userUid: string,
|
|
69
|
+
trustedRoles: readonly string[] = DEFAULT_TRUSTED_ROLES,
|
|
70
|
+
now: number = Date.now(),
|
|
71
|
+
): Promise<boolean | undefined> {
|
|
72
|
+
const key = `${ADMIN_ACCESS_KEY_PREFIX}${userUid}`;
|
|
73
|
+
const remembered = readRemembered(key, now);
|
|
74
|
+
if (remembered !== undefined) {
|
|
75
|
+
return Promise.resolve(remembered);
|
|
76
|
+
}
|
|
77
|
+
const inFlight = lookups.get(key);
|
|
78
|
+
if (inFlight) {
|
|
79
|
+
return inFlight;
|
|
80
|
+
}
|
|
81
|
+
const lookup = (async (): Promise<boolean | undefined> => {
|
|
82
|
+
try {
|
|
83
|
+
const user = await authApiFetch<{ uid?: unknown; roles?: unknown } | null>(authServerUrl, "/users/me");
|
|
84
|
+
if (!user || user.uid !== userUid || !Array.isArray(user.roles)) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const admin = user.roles.some((role) => typeof role === "string" && trustedRoles.includes(role));
|
|
88
|
+
remember(key, admin, now);
|
|
89
|
+
return admin;
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
} finally {
|
|
93
|
+
// Answered: what was learned is in storage; a failure may be asked again.
|
|
94
|
+
lookups.delete(key);
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
lookups.set(key, lookup);
|
|
98
|
+
return lookup;
|
|
99
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* The calendar's side of `@rapidmx/booking-plugin`: whether the plugin is running, and the one call the quick-create popover's
|
|
7
|
+
* "Appointment schedule" tab makes on it - creating a booking type (`POST /api/mail/booking-types`).
|
|
8
|
+
*
|
|
9
|
+
* The plugin ships its own typed client, but inside its own UI (`apps/shared/bookingApi.ts` of the plugin), which this package cannot import,
|
|
10
|
+
* so the few fields this popover writes are mirrored here. The plugin's `BaseBookingTypeRoute` is the authority: it normalizes the slug,
|
|
11
|
+
* assigns the uids, refuses a taken slug with a 409 and validates the availability, durations and calendar folder.
|
|
12
|
+
*/
|
|
13
|
+
import { apiFetch } from "@rapidmx/react-shared/util/api.js";
|
|
14
|
+
import { PluginNav, isSafePluginHref } from "../plugins/pluginNav.js";
|
|
15
|
+
|
|
16
|
+
/** The id of the Settings section the booking plugin's manifest contributes (`ui.settingsSections`) - what tells the client it is running. */
|
|
17
|
+
export const BOOKING_SETTINGS_SECTION_ID = "booking-types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Where the booking plugin's Settings pages are mounted (`/settings/booking-types`), or `undefined` when the plugin is not running.
|
|
21
|
+
*
|
|
22
|
+
* A server only puts a plugin's navigation into `pluginNav` while the plugin is enabled and its pages built, so its section being there is the
|
|
23
|
+
* client's one way of knowing (the plugin list itself is admin-only).
|
|
24
|
+
*/
|
|
25
|
+
export function bookingSettingsHref(pluginNav: PluginNav | undefined): string | undefined {
|
|
26
|
+
const section = pluginNav?.settingsSections?.find((item) => item.id === BOOKING_SETTINGS_SECTION_ID);
|
|
27
|
+
return section && isSafePluginHref(section.href) ? section.href.replace(/\/+$/, "") : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The plugin's "new booking link" page for `mailboxUid` - the full form behind the popover's More options. */
|
|
31
|
+
export function newBookingLinkHref(settingsHref: string, mailboxUid: string): string {
|
|
32
|
+
return `${settingsHref}/new?mailboxUid=${encodeURIComponent(mailboxUid)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BookingAvailabilityWindow {
|
|
36
|
+
/** 0 (Sunday) to 6 (Saturday). */
|
|
37
|
+
dayOfWeek: number;
|
|
38
|
+
startMinute: number;
|
|
39
|
+
endMinute: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type BookingLocationType = "phone" | "video" | "other";
|
|
43
|
+
|
|
44
|
+
export interface BookingMeetingType {
|
|
45
|
+
name: string;
|
|
46
|
+
durationMinutes: number;
|
|
47
|
+
locationOptions: { type: BookingLocationType }[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CreateBookingTypeInput {
|
|
51
|
+
mailboxUid: string;
|
|
52
|
+
calendarFolderUid: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
name: string;
|
|
55
|
+
hostDisplayName: string;
|
|
56
|
+
meetingTypes: BookingMeetingType[];
|
|
57
|
+
timezone: string;
|
|
58
|
+
availability: BookingAvailabilityWindow[];
|
|
59
|
+
bufferAfterMinutes: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What comes back from a create: the fields the popover needs to show the link. */
|
|
63
|
+
export interface CreatedBookingType {
|
|
64
|
+
uid: string;
|
|
65
|
+
mailboxUid: string;
|
|
66
|
+
slug: string;
|
|
67
|
+
name: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Creates a booking type with the plugin's own defaults for everything the popover leaves out (an hour's notice, 30 days ahead, no approval). */
|
|
71
|
+
export function createBookingType(input: CreateBookingTypeInput): Promise<CreatedBookingType> {
|
|
72
|
+
return apiFetch("/mail/booking-types", {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
dateOverrides: [],
|
|
76
|
+
bufferBeforeMinutes: 0,
|
|
77
|
+
minimumNoticeMinutes: 60,
|
|
78
|
+
bookingWindowDays: 30,
|
|
79
|
+
requiresApproval: false,
|
|
80
|
+
enabled: true,
|
|
81
|
+
...input,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A URL-safe slug for a schedule's name, the way the plugin's own `normalizeSlug()` reads it; `"appointments"` when nothing usable is left. */
|
|
87
|
+
export function slugFor(name: string): string {
|
|
88
|
+
return (
|
|
89
|
+
name
|
|
90
|
+
.trim()
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
93
|
+
.replace(/^-+|-+$/g, "") || "appointments"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The public page of a booking type on this site's own origin (`/book/<mailbox>/<slug>`, the `@` of a mailbox uid left as it is). Browser only. */
|
|
98
|
+
export function bookingPublicUrl(mailboxUid: string, slug: string): string {
|
|
99
|
+
return `${window.location.origin}/book/${encodeURIComponent(mailboxUid).replace(/%40/g, "@")}/${encodeURIComponent(slug)}`;
|
|
100
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/** Pure helper behind the calendar page's live updates: which push events say "an event changed, and the payload is only a busy block". */
|
|
6
|
+
|
|
7
|
+
import type { PushEvent } from "@rapidmx/react-shared/mail/pushClient.js";
|
|
8
|
+
|
|
9
|
+
/** The published model name of a calendar event on either database (`CalendarEventMongo`, `CalendarEventSQL`) - and not the reminder's `"CalendarEvent"`,
|
|
10
|
+
* which has its own action. */
|
|
11
|
+
const EVENT_MODEL = /^CalendarEvent(Mongo|SQL)$/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The uid of the event a push event announces as a redacted busy block, or `undefined` for anything else the shared push connection delivers.
|
|
15
|
+
*
|
|
16
|
+
* When somebody creates or changes a private or confidential event, restapi publishes it on the calendar's channel once, for every subscriber - and that one
|
|
17
|
+
* payload has to be safe for a read-only reader of a shared calendar, so it is the busy block (`redacted: true`, title "Busy", no details). It must not be
|
|
18
|
+
* stored as the event by the calendar's owner (or anybody else who may see it in full): they fetch the event again by its uid, which answers with as
|
|
19
|
+
* much as they may see.
|
|
20
|
+
*/
|
|
21
|
+
export function redactedEventUidOf(event: PushEvent): string | undefined {
|
|
22
|
+
if (!EVENT_MODEL.test(event.type) || (event.action !== "create" && event.action !== "update")) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const data = event.data as { uid?: unknown; redacted?: unknown } | null | undefined;
|
|
26
|
+
return data?.redacted === true && typeof data.uid === "string" ? data.uid : undefined;
|
|
27
|
+
}
|