@rapidmx/web-client 0.10.0 → 0.12.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 +183 -2
- package/apps/admin/_layout.tsx +8 -2
- package/apps/admin/index.tsx +7 -1
- package/apps/admin/ingest-queue/index.tsx +2 -1
- package/apps/admin/mailboxes/[uid].tsx +23 -10
- package/apps/admin/mailboxes/new/index.tsx +3 -3
- package/apps/admin/quarantine/index.tsx +2 -1
- package/apps/admin/signing-certificates/index.tsx +343 -0
- package/apps/escrow/_layout.tsx +8 -2
- package/apps/shared/appearance/AppearanceForm.tsx +392 -0
- package/apps/shared/appearance/AppearanceHead.tsx +49 -0
- package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
- package/apps/shared/appearance/ColorField.tsx +107 -0
- package/apps/shared/appearance/appearanceCache.ts +106 -0
- package/apps/shared/appearance/appearanceContext.ts +77 -0
- package/apps/shared/appearance/bootScript.ts +35 -0
- package/apps/shared/appearance/color.ts +169 -0
- package/apps/shared/appearance/photo.ts +88 -0
- package/apps/shared/appearance/resolvedTheme.ts +67 -0
- package/apps/shared/appearance/theme.ts +355 -0
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +4 -4
- package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
- package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +60 -14
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +65 -16
- package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
- package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
- package/apps/shared/components/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +98 -32
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/RailIcon.tsx +100 -0
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
- package/apps/shared/components/layout/UserMenu.tsx +75 -6
- package/apps/shared/components/mail/ConversationList.tsx +4 -2
- package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
- package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
- package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
- package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
- package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
- package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
- package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
- package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
- package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
- package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
- package/apps/shared/components/mail/reading/color.ts +245 -0
- package/apps/shared/components/mail/reading/frameControl.ts +142 -0
- package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
- package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
- package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
- package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
- package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/components/sharing/PrincipalListField.tsx +82 -0
- package/apps/shared/components/sharing/PrincipalPicker.tsx +76 -0
- package/apps/shared/components/sharing/PrincipalResolver.tsx +164 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
- package/apps/shared/mail/folderCounts.ts +9 -3
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
- package/apps/shared/mail/outbox/composeBridge.ts +69 -0
- package/apps/shared/mail/outbox/outboxState.ts +88 -0
- package/apps/shared/mail/outbox/pendingSends.ts +146 -0
- package/apps/shared/mail/outbox/sendDecision.ts +157 -0
- package/apps/shared/mail/outbox/sendJob.ts +427 -0
- package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
- package/apps/shared/mail/outbox/sendState.ts +35 -0
- package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
- package/apps/shared/mail/useMailConnection.ts +81 -28
- package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
- package/apps/shared/mail/useNewMailNotifications.ts +47 -32
- package/apps/shared/navigation/AppRouter.tsx +2 -2
- package/apps/shared/notifications/NotificationCenter.tsx +215 -0
- package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
- package/apps/shared/notifications/apiErrors.ts +98 -0
- package/apps/shared/notifications/headerOffset.ts +35 -0
- package/apps/shared/notifications/pushStatus.ts +56 -0
- package/apps/shared/notifications/store.ts +549 -0
- package/apps/shared/notifications/systemErrors.ts +63 -0
- package/apps/shared/notifications/useNotifications.ts +41 -0
- package/apps/shared/signing/enrollmentStorage.ts +33 -0
- package/apps/shared/signing/enrollmentTracker.ts +385 -0
- package/apps/shared/signing/enrollmentView.ts +251 -0
- package/apps/shared/signing/signingInfo.ts +58 -0
- package/apps/shared/signing/useNow.ts +19 -0
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
- package/apps/shared/styles/app.css +80 -0
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +9 -14
- package/apps/www/contacts/[uid].tsx +5 -8
- package/apps/www/contacts/index.tsx +587 -584
- package/apps/www/index.tsx +38 -27
- package/apps/www/messages/[uid].tsx +3 -1
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +3 -7
- package/apps/www/settings/encryption/index.tsx +151 -116
- package/apps/www/settings/filters/[uid].tsx +3 -1
- package/apps/www/settings/filters/new/index.tsx +3 -1
- package/apps/www/settings/privacy/index.tsx +3 -8
- package/apps/www/settings/read-receipts/index.tsx +3 -7
- package/apps/www/settings/sharing/index.tsx +44 -65
- package/apps/www/settings/signatures/[uid].tsx +3 -1
- package/apps/www/settings/signatures/new/index.tsx +3 -2
- package/apps/www/tasks/index.tsx +11 -16
- package/dist/apps/admin/_layout.d.ts +5 -1
- package/dist/apps/admin/_layout.js +3 -2
- package/dist/apps/admin/index.js +3 -2
- package/dist/apps/admin/ingest-queue/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +9 -6
- package/dist/apps/admin/mailboxes/new/index.js +1 -1
- package/dist/apps/admin/quarantine/index.js +2 -1
- package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
- package/dist/apps/admin/signing-certificates/index.js +161 -0
- package/dist/apps/escrow/_layout.d.ts +5 -1
- package/dist/apps/escrow/_layout.js +3 -2
- package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
- package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
- package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
- package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
- package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
- package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
- package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
- package/dist/apps/shared/appearance/ColorField.js +40 -0
- package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceCache.js +80 -0
- package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceContext.js +29 -0
- package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
- package/dist/apps/shared/appearance/bootScript.js +33 -0
- package/dist/apps/shared/appearance/color.d.ts +65 -0
- package/dist/apps/shared/appearance/color.js +132 -0
- package/dist/apps/shared/appearance/photo.d.ts +17 -0
- package/dist/apps/shared/appearance/photo.js +79 -0
- package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
- package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
- package/dist/apps/shared/appearance/theme.d.ts +111 -0
- package/dist/apps/shared/appearance/theme.js +251 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +25 -5
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +18 -7
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
- package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
- package/dist/apps/shared/components/layout/AppShell.js +59 -13
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
- package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
- package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
- package/dist/apps/shared/components/layout/RailIcon.js +85 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
- package/dist/apps/shared/components/layout/UserMenu.js +41 -7
- package/dist/apps/shared/components/mail/ConversationList.js +3 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
- package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
- package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
- package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
- package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
- package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
- package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
- package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
- package/dist/apps/shared/components/mail/reading/color.js +201 -0
- package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
- package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +24 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.js +23 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +28 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +24 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +44 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.js +92 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
- package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
- package/dist/apps/shared/mail/folderCounts.js +9 -3
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
- package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
- package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
- package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
- package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
- package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
- package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
- package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
- package/dist/apps/shared/mail/outbox/sendState.js +8 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
- package/dist/apps/shared/mail/useMailConnection.js +64 -22
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
- package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
- package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
- package/dist/apps/shared/navigation/AppRouter.js +2 -2
- package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
- package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
- package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
- package/dist/apps/shared/notifications/apiErrors.js +86 -0
- package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
- package/dist/apps/shared/notifications/headerOffset.js +33 -0
- package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
- package/dist/apps/shared/notifications/pushStatus.js +54 -0
- package/dist/apps/shared/notifications/store.d.ts +136 -0
- package/dist/apps/shared/notifications/store.js +416 -0
- package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
- package/dist/apps/shared/notifications/systemErrors.js +58 -0
- package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
- package/dist/apps/shared/notifications/useNotifications.js +18 -0
- package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
- package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
- package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
- package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
- package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
- package/dist/apps/shared/signing/enrollmentView.js +214 -0
- package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
- package/dist/apps/shared/signing/signingInfo.js +48 -0
- package/dist/apps/shared/signing/useNow.d.ts +2 -0
- package/dist/apps/shared/signing/useNow.js +18 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
- package/dist/apps/shared/styles/app.css +80 -0
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +3 -4
- package/dist/apps/www/contacts/[uid].js +5 -7
- package/dist/apps/www/contacts/index.js +14 -21
- package/dist/apps/www/index.js +37 -19
- package/dist/apps/www/messages/[uid].js +3 -1
- package/dist/apps/www/settings/appearance/index.d.ts +4 -0
- package/dist/apps/www/settings/appearance/index.js +13 -0
- package/dist/apps/www/settings/auto-reply/index.js +3 -6
- package/dist/apps/www/settings/encryption/index.js +103 -95
- package/dist/apps/www/settings/filters/[uid].js +3 -1
- package/dist/apps/www/settings/filters/new/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +5 -8
- package/dist/apps/www/settings/read-receipts/index.js +3 -6
- package/dist/apps/www/settings/sharing/index.js +19 -33
- package/dist/apps/www/settings/signatures/[uid].js +3 -1
- package/dist/apps/www/settings/signatures/new/index.js +3 -2
- package/dist/apps/www/tasks/index.js +8 -14
- package/package.json +2 -2
- package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
- package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -6,15 +6,14 @@ import { routedPage } from "../../_routedPage.js";
|
|
|
6
6
|
import React, { FormEvent, useEffect, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
8
|
import {
|
|
9
|
-
EnrollmentResult,
|
|
10
9
|
KeyVault,
|
|
11
10
|
MasterKeyWrap,
|
|
12
11
|
PublicKey,
|
|
13
12
|
WrappedPrivateKey,
|
|
14
13
|
addMasterKeyWrap,
|
|
15
14
|
cancelSignEnrollment,
|
|
16
|
-
checkSignEnrollmentStatus,
|
|
17
15
|
findActivePublicKey,
|
|
16
|
+
getCurrentSignEnrollment,
|
|
18
17
|
getEscrowInfo,
|
|
19
18
|
getKeyVault,
|
|
20
19
|
rekey,
|
|
@@ -45,9 +44,22 @@ import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../sh
|
|
|
45
44
|
import KeyEnrollmentGate from "../../../shared/components/layout/KeyEnrollmentGate.js";
|
|
46
45
|
import { useUnlockPrompt } from "../../../shared/components/layout/UnlockPromptProvider.js";
|
|
47
46
|
import { destroyLocalIndex } from "../../../shared/search/localIndexRpcClient.js";
|
|
47
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
48
48
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
49
49
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
50
50
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
51
|
+
import SigningCertificateCard, { SigningCertificateMode } from "../../../shared/components/settings/SigningCertificateCard.js";
|
|
52
|
+
import { readStoredSignEnrollment, storeSignEnrollment } from "../../../shared/signing/enrollmentStorage.js";
|
|
53
|
+
import {
|
|
54
|
+
checkEnrollmentNow,
|
|
55
|
+
forgetEnrollment,
|
|
56
|
+
recordEnrollmentResult,
|
|
57
|
+
useEnrollmentSnapshot,
|
|
58
|
+
watchCurrentEnrollment,
|
|
59
|
+
watchEnrollment,
|
|
60
|
+
} from "../../../shared/signing/enrollmentTracker.js";
|
|
61
|
+
import { STALE_REQUEST_TEXT, beforeRequestText, healthWarning, isInstalling, timeOf } from "../../../shared/signing/enrollmentView.js";
|
|
62
|
+
import { useSigningEnrollmentInfo } from "../../../shared/signing/signingInfo.js";
|
|
51
63
|
|
|
52
64
|
const MIN_PASSWORD_LENGTH = 8;
|
|
53
65
|
|
|
@@ -55,6 +67,10 @@ const MIN_PASSWORD_LENGTH = 8;
|
|
|
55
67
|
* already holding this many wraps (escrow included). */
|
|
56
68
|
export const MAX_MASTER_KEY_WRAPS = 20;
|
|
57
69
|
|
|
70
|
+
/** How often, and how many times, the page asks for the mailbox's keys while a certificate is issued but not installed yet. */
|
|
71
|
+
const INSTALL_WATCH_INTERVAL_MS = 15_000;
|
|
72
|
+
const INSTALL_WATCH_ATTEMPTS = 40;
|
|
73
|
+
|
|
58
74
|
const KEYS_LOCKED_MESSAGE = "Your encryption keys were locked before this could finish. Unlock them and try again.";
|
|
59
75
|
|
|
60
76
|
/** Thrown inside `handleRotateKeys()` when the escrow wrap of the new master key can't be built - the rotation is
|
|
@@ -160,39 +176,6 @@ async function rewrapVaultPrivateKeys(
|
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
// RFC 8823 ACME issuance is a real email round-trip with a public CA - "likely minutes," not seconds -
|
|
164
|
-
// so this polls infrequently rather than hammering the endpoint.
|
|
165
|
-
const SIGNING_ENROLLMENT_POLL_INTERVAL_MS = 15_000;
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* A started signing enrollment's id, per mailbox. Kept only so a reload can ask the server
|
|
169
|
-
* (`checkSignEnrollmentStatus()`) whether that enrollment is still pending: a rotation while it is pending
|
|
170
|
-
* would strand the enrollment's already-submitted private key under a master key that no longer exists. The
|
|
171
|
-
* stored id is never trusted on its own - the server's answer decides the state, and restapi's own `rekey()`
|
|
172
|
-
* refuses (409) a rotation during an enrollment this browser never saw (e.g. one started on another device).
|
|
173
|
-
*/
|
|
174
|
-
const SIGN_ENROLLMENT_STORAGE_PREFIX = "rapidmx.signEnrollment.";
|
|
175
|
-
|
|
176
|
-
function readStoredSignEnrollment(mailboxUid: string): string | null {
|
|
177
|
-
try {
|
|
178
|
-
return localStorage.getItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid);
|
|
179
|
-
} catch {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function storeSignEnrollment(mailboxUid: string, enrollmentId: string | null): void {
|
|
185
|
-
try {
|
|
186
|
-
if (enrollmentId) {
|
|
187
|
-
localStorage.setItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid, enrollmentId);
|
|
188
|
-
} else {
|
|
189
|
-
localStorage.removeItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid);
|
|
190
|
-
}
|
|
191
|
-
} catch {
|
|
192
|
-
// Storage blocked - restapi's own 409 on rekey still guards a rotation.
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
179
|
const ROTATION_CONFLICT_MESSAGE =
|
|
197
180
|
"Your keys were not rotated because the server reported a conflicting change. If a signing certificate enrollment is still in progress for this mailbox (it may have been started on another device), wait for it to finish, then try again. If this mailbox is under escrow, its escrow protection may have changed meanwhile - reload this page and try again. Nothing was changed.";
|
|
198
181
|
|
|
@@ -304,6 +287,12 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
304
287
|
const [signingEnrollmentId, setSigningEnrollmentId] = useState<string | null>(null);
|
|
305
288
|
const [signingError, setSigningError] = useState<string | null>(null);
|
|
306
289
|
const rotationBlockedBySigning = signingStatus !== "idle";
|
|
290
|
+
// What is known of the mailbox's signing-certificate enrollment: kept current by the shared tracker (`signing/enrollmentTracker.ts`), which reads
|
|
291
|
+
// the server with backoff while this page is visible, stops when the enrollment ends, and goes on in the app frame when this page is left.
|
|
292
|
+
const enrollment = useEnrollmentSnapshot(mailboxUid);
|
|
293
|
+
const enrollmentResult = enrollment?.result ?? null;
|
|
294
|
+
// How this deployment issues certificates (a public CA by itself, or an administrator by hand): what the card and the request say follows it. Only the owner asks.
|
|
295
|
+
const signingInfo = useSigningEnrollmentInfo(canManageKeys);
|
|
307
296
|
|
|
308
297
|
const [cancelingEnrollment, setCancelingEnrollment] = useState(false);
|
|
309
298
|
const [cancelEnrollmentError, setCancelEnrollmentError] = useState<string | null>(null);
|
|
@@ -377,79 +366,80 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
377
366
|
}
|
|
378
367
|
}
|
|
379
368
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (result.status === "pending") {
|
|
383
|
-
setSigningEnrollmentId(enrollmentId);
|
|
384
|
-
setSigningStatus("pending");
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
storeSignEnrollment(mailboxUid!, null);
|
|
388
|
-
setSigningStatus("idle");
|
|
389
|
-
setSigningEnrollmentId(null);
|
|
390
|
-
if (result.status === "failed") {
|
|
391
|
-
setSigningError(result.error ?? "Signing certificate enrollment failed.");
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
try {
|
|
395
|
-
const refreshed = await getMailbox(mailboxUid!);
|
|
396
|
-
if (!isCancelled()) {
|
|
397
|
-
setRefreshedKeys(refreshed.keys ?? []);
|
|
398
|
-
}
|
|
399
|
-
} catch {
|
|
400
|
-
// The new key shows on the next load - the enrollment itself is finished either way.
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Confirms an enrollment started before a reload with the server before rotation is offered again.
|
|
369
|
+
// Follows the enrollment this browser started (its stored id) - or, for the owner, the one the server names as the mailbox's current one (started
|
|
370
|
+
// on another device, or long ago), so a pending one blocks rotation and shows its progress here too.
|
|
405
371
|
useEffect(() => {
|
|
406
372
|
const storedId = readStoredSignEnrollment(mailboxUid!);
|
|
407
|
-
if (!storedId) {
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
373
|
let cancelled = false;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
});
|
|
374
|
+
let release: (() => void) | undefined;
|
|
375
|
+
if (storedId) {
|
|
376
|
+
setSigningStatus("checking");
|
|
377
|
+
release = watchEnrollment(mailboxUid!, storedId);
|
|
378
|
+
} else if (canManageKeys) {
|
|
379
|
+
getCurrentSignEnrollment(mailboxUid!)
|
|
380
|
+
.then((current) => {
|
|
381
|
+
if (!cancelled && current) {
|
|
382
|
+
if (current.status === "pending") {
|
|
383
|
+
storeSignEnrollment(mailboxUid!, current.enrollmentId);
|
|
384
|
+
}
|
|
385
|
+
release = watchCurrentEnrollment(mailboxUid!, current, current.status === "pending");
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
.catch(() => undefined);
|
|
389
|
+
}
|
|
428
390
|
return () => {
|
|
429
391
|
cancelled = true;
|
|
392
|
+
release?.();
|
|
430
393
|
};
|
|
431
|
-
}, [mailboxUid]);
|
|
394
|
+
}, [mailboxUid, canManageKeys]);
|
|
432
395
|
|
|
396
|
+
// What the tracker knows becomes this page's state: pending (or not heard from yet - assume it is still going, and let the polling keep asking) blocks
|
|
397
|
+
// rotation; an enrollment that ended, or that the server no longer knows, does not.
|
|
433
398
|
useEffect(() => {
|
|
434
|
-
if (
|
|
399
|
+
if (!enrollment) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (enrollmentResult?.status === "pending" || (!enrollmentResult && enrollment.offline)) {
|
|
403
|
+
setSigningEnrollmentId(enrollment.enrollmentId);
|
|
404
|
+
setSigningStatus("pending");
|
|
405
|
+
} else if (enrollmentResult || enrollment.gone) {
|
|
406
|
+
setSigningEnrollmentId(null);
|
|
407
|
+
setSigningStatus("idle");
|
|
408
|
+
}
|
|
409
|
+
}, [enrollment?.enrollmentId, enrollmentResult?.status, enrollment?.gone, enrollment?.offline]);
|
|
410
|
+
|
|
411
|
+
// Once a certificate is issued the server has installed a new signing key that `mailbox.keys` (fetched once by the shell) never saw. The server installs it
|
|
412
|
+
// with a job a few minutes after the CA issues it (`installedAt`): until then the key is not there, so ask again every 15 s (for ten minutes at most).
|
|
413
|
+
const issuedUntil = timeOf(enrollmentResult?.notAfter);
|
|
414
|
+
const keyIsCurrent = !!activeSigningKey && (issuedUntil === undefined || issuedUntil <= activeSigningKey.notAfter + 60_000);
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
if (enrollmentResult?.status !== "issued" || keyIsCurrent) {
|
|
435
417
|
return;
|
|
436
418
|
}
|
|
437
419
|
let cancelled = false;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
420
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
421
|
+
let attempts = 0;
|
|
422
|
+
const refresh = () => {
|
|
423
|
+
getMailbox(mailboxUid!)
|
|
424
|
+
.then((refreshed) => {
|
|
425
|
+
if (cancelled) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const keys = refreshed.keys ?? [];
|
|
429
|
+
setRefreshedKeys(keys);
|
|
430
|
+
// Not installed yet, and the server said it is on its way: look again.
|
|
431
|
+
if (isInstalling(enrollmentResult) && ++attempts < INSTALL_WATCH_ATTEMPTS && !findActivePublicKey(keys, "sign")) {
|
|
432
|
+
timer = setTimeout(refresh, INSTALL_WATCH_INTERVAL_MS);
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
.catch(() => undefined);
|
|
436
|
+
};
|
|
437
|
+
refresh();
|
|
448
438
|
return () => {
|
|
449
439
|
cancelled = true;
|
|
450
|
-
|
|
440
|
+
clearTimeout(timer);
|
|
451
441
|
};
|
|
452
|
-
}, [
|
|
442
|
+
}, [enrollmentResult?.status, enrollmentResult?.installedAt, enrollment?.enrollmentId, keyIsCurrent, mailboxUid]);
|
|
453
443
|
|
|
454
444
|
/** Cancels the pending enrollment (restapi's owner-only DELETE), which is what lets rotation run again. */
|
|
455
445
|
async function handleCancelEnrollment(enrollmentId: string) {
|
|
@@ -461,16 +451,18 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
461
451
|
setCancelEnrollmentError("The enrollment couldn't be cancelled yet. Try again.");
|
|
462
452
|
} else if (result.status === "failed") {
|
|
463
453
|
// Cancelled - the "failure" is the cancellation itself, not something to report.
|
|
454
|
+
forgetEnrollment(mailboxUid!);
|
|
464
455
|
storeSignEnrollment(mailboxUid!, null);
|
|
465
456
|
setSigningStatus("idle");
|
|
466
457
|
setSigningEnrollmentId(null);
|
|
467
458
|
} else {
|
|
468
459
|
// Issued before the cancel landed: it finishes like any other issued enrollment.
|
|
469
|
-
|
|
460
|
+
recordEnrollmentResult(mailboxUid!, enrollmentId, result);
|
|
470
461
|
}
|
|
471
462
|
} catch (err) {
|
|
472
463
|
if (err instanceof ApiRequestError && err.status === 404) {
|
|
473
464
|
// The server no longer knows this enrollment, so nothing is pending.
|
|
465
|
+
forgetEnrollment(mailboxUid!);
|
|
474
466
|
storeSignEnrollment(mailboxUid!, null);
|
|
475
467
|
setSigningStatus("idle");
|
|
476
468
|
setSigningEnrollmentId(null);
|
|
@@ -499,6 +491,8 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
499
491
|
wrappedKey: { ciphertext: wrappedKeySealed.ciphertext, nonce: wrappedKeySealed.nonce, algorithm: "AES-256-GCM" },
|
|
500
492
|
});
|
|
501
493
|
storeSignEnrollment(mailboxUid!, enrollmentId);
|
|
494
|
+
// Followed from now on (and after this page is left): the server has just accepted it, so it is pending - no need to ask first.
|
|
495
|
+
watchEnrollment(mailboxUid!, enrollmentId, { initial: { status: "pending" } });
|
|
502
496
|
setSigningEnrollmentId(enrollmentId);
|
|
503
497
|
setSigningStatus("pending");
|
|
504
498
|
} catch (err) {
|
|
@@ -552,7 +546,8 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
552
546
|
await removeMasterKeyWrap(mailboxUid!, wrap.method, wrap.methodId);
|
|
553
547
|
await loadVault();
|
|
554
548
|
} catch (err) {
|
|
555
|
-
|
|
549
|
+
// A pop-up (the confirmation is already closed); `actionError` keeps the guidance tied to the forms below.
|
|
550
|
+
notifyApiError(err, "Couldn't remove this unlock method");
|
|
556
551
|
} finally {
|
|
557
552
|
setRemovingMethod(null);
|
|
558
553
|
}
|
|
@@ -889,6 +884,27 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
889
884
|
);
|
|
890
885
|
}
|
|
891
886
|
|
|
887
|
+
// The "Digital signatures" section: a pending enrollment first (even while an older certificate is still active - it is a renewal); else the
|
|
888
|
+
// active certificate; else how the last enrollment ended (a failure, or a certificate that has since expired); else nothing has been requested.
|
|
889
|
+
const expiredSigningKey = displayedKeys
|
|
890
|
+
.filter((key) => key.useType === "sign" && !key.revokedAt && key.notAfter <= Date.now())
|
|
891
|
+
.sort((a, b) => b.notAfter - a.notAfter)[0];
|
|
892
|
+
// A certificate the CA has issued but the server has not installed yet counts as issued too (there is no key to show yet: the card says it is installing).
|
|
893
|
+
const installing = canManageKeys && isInstalling(enrollmentResult);
|
|
894
|
+
const certificateMode: SigningCertificateMode | null =
|
|
895
|
+
signingStatus === "pending"
|
|
896
|
+
? "pending"
|
|
897
|
+
: activeSigningKey || installing
|
|
898
|
+
? canManageKeys
|
|
899
|
+
? "issued"
|
|
900
|
+
: null
|
|
901
|
+
: canManageKeys && enrollmentResult?.status === "failed"
|
|
902
|
+
? "failed"
|
|
903
|
+
: canManageKeys && expiredSigningKey
|
|
904
|
+
? "expired"
|
|
905
|
+
: null;
|
|
906
|
+
const certificateEnrollment = certificateMode === "issued" && enrollmentResult?.status !== "issued" ? null : certificateMode === "expired" ? null : enrollmentResult;
|
|
907
|
+
|
|
892
908
|
return (
|
|
893
909
|
<div className="flex-1 min-w-0 overflow-y-auto p-6">
|
|
894
910
|
<div className="max-w-xl flex flex-col gap-6">
|
|
@@ -931,36 +947,55 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
931
947
|
|
|
932
948
|
<div>
|
|
933
949
|
<h2 className="text-sm font-semibold mb-2">Digital signatures</h2>
|
|
934
|
-
{activeSigningKey
|
|
935
|
-
<p className="text-sm text-text-muted">
|
|
950
|
+
{activeSigningKey && signingStatus !== "pending" && (
|
|
951
|
+
<p className={["text-sm text-text-muted", certificateMode ? "mb-3" : ""].join(" ")}>
|
|
936
952
|
Enabled — outgoing mail from this mailbox is signed with a publicly-trusted
|
|
937
953
|
certificate.
|
|
938
954
|
</p>
|
|
939
|
-
)
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
955
|
+
)}
|
|
956
|
+
{certificateMode && (
|
|
957
|
+
<div className="flex flex-col gap-2">
|
|
958
|
+
{certificateMode === "failed" && signingError && <Alert>{signingError}</Alert>}
|
|
959
|
+
<SigningCertificateCard
|
|
960
|
+
mode={certificateMode}
|
|
961
|
+
enrollment={certificateEnrollment}
|
|
962
|
+
snapshot={enrollment}
|
|
963
|
+
info={signingInfo}
|
|
964
|
+
address={mailbox.primarySmtpAddress}
|
|
965
|
+
keyNotAfter={certificateMode === "expired" ? expiredSigningKey?.notAfter : activeSigningKey?.notAfter}
|
|
966
|
+
canRequest={canManageKeys}
|
|
967
|
+
requesting={signingStatus === "enrolling"}
|
|
968
|
+
onCheck={canManageKeys ? () => void checkEnrollmentNow(mailboxUid!) : undefined}
|
|
969
|
+
onRequest={handleEnrollSigning}
|
|
970
|
+
/>
|
|
971
|
+
</div>
|
|
972
|
+
)}
|
|
973
|
+
{!activeSigningKey && !certificateMode && !canManageKeys ? (
|
|
946
974
|
<p className="text-sm text-text-muted">Not enabled.</p>
|
|
947
|
-
) : (
|
|
975
|
+
) : activeSigningKey || certificateMode ? null : (
|
|
948
976
|
<div className="flex flex-col gap-2">
|
|
949
977
|
<p className="text-xs text-text-muted">
|
|
950
978
|
Lets recipients verify that mail from this mailbox is genuinely from you.
|
|
951
979
|
Optional — encryption already works without it.
|
|
952
980
|
</p>
|
|
981
|
+
{beforeRequestText(signingInfo, mailbox.primarySmtpAddress) && (
|
|
982
|
+
<p className="text-xs text-text-muted">{beforeRequestText(signingInfo, mailbox.primarySmtpAddress)}</p>
|
|
983
|
+
)}
|
|
984
|
+
{healthWarning(signingInfo) && <Alert>{healthWarning(signingInfo)}</Alert>}
|
|
985
|
+
{enrollment?.gone && <Alert>{STALE_REQUEST_TEXT}</Alert>}
|
|
953
986
|
{signingError && <Alert>{signingError}</Alert>}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
987
|
+
{signingInfo?.backend !== "none" && (
|
|
988
|
+
<Button
|
|
989
|
+
type="button"
|
|
990
|
+
variant="secondary"
|
|
991
|
+
className="!w-auto"
|
|
992
|
+
loading={signingStatus === "enrolling"}
|
|
993
|
+
disabled={signingStatus !== "idle"}
|
|
994
|
+
onClick={handleEnrollSigning}
|
|
995
|
+
>
|
|
996
|
+
Enable digital signatures
|
|
997
|
+
</Button>
|
|
998
|
+
)}
|
|
964
999
|
</div>
|
|
965
1000
|
)}
|
|
966
1001
|
</div>
|
|
@@ -19,6 +19,7 @@ import { MAIL_FILTER_CONDITION_FIELDS, buildMailFilterActionTypes } from "./_mai
|
|
|
19
19
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
20
20
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
21
21
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
22
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
22
23
|
|
|
23
24
|
const INPUT_CLASS =
|
|
24
25
|
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
@@ -106,7 +107,8 @@ function MailFilterDetailContent({ uid }: { uid: string }) {
|
|
|
106
107
|
setOriginal(updated);
|
|
107
108
|
setSaved(true);
|
|
108
109
|
} catch (err) {
|
|
109
|
-
|
|
110
|
+
// A pop-up: `error` is only this form's own validation (and the load failure above).
|
|
111
|
+
notifyApiError(err, "Couldn't save the mail filter");
|
|
110
112
|
} finally {
|
|
111
113
|
setSaving(false);
|
|
112
114
|
}
|
|
@@ -18,6 +18,7 @@ import { MAIL_FILTER_CONDITION_FIELDS, buildMailFilterActionTypes } from "../_ma
|
|
|
18
18
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
19
19
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
20
20
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
21
|
+
import { notifyApiError } from "../../../../shared/notifications/apiErrors.js";
|
|
21
22
|
|
|
22
23
|
const INPUT_CLASS =
|
|
23
24
|
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
@@ -76,7 +77,8 @@ function NewMailFilterForm() {
|
|
|
76
77
|
const created = await createMailFilterRule({ mailboxUid: mailboxUid!, name: name.trim(), ...rule });
|
|
77
78
|
navigate(`/settings/filters/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid!)}`);
|
|
78
79
|
} catch (err) {
|
|
79
|
-
|
|
80
|
+
// A pop-up: `error` above is only the form's own validation.
|
|
81
|
+
notifyApiError(err, "Couldn't create the mail filter");
|
|
80
82
|
} finally {
|
|
81
83
|
setSaving(false);
|
|
82
84
|
}
|
|
@@ -28,6 +28,7 @@ import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../sh
|
|
|
28
28
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
29
29
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
30
30
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
31
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
31
32
|
|
|
32
33
|
const NON_MAIL_FOLDER_TYPES = new Set(["calendar", "contacts", "tasks", "notes"]);
|
|
33
34
|
|
|
@@ -125,7 +126,6 @@ function ExportSection({ mailboxUid, mailboxLabel }: { mailboxUid?: string; mail
|
|
|
125
126
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
126
127
|
const [format, setFormat] = useState<DataExportFormat>("json");
|
|
127
128
|
const [creating, setCreating] = useState(false);
|
|
128
|
-
const [createError, setCreateError] = useState<string | null>(null);
|
|
129
129
|
|
|
130
130
|
// The create form below isn't gated behind `loading`, so a fast create-then-reload can resolve before
|
|
131
131
|
// the initial mount fetch does; without this guard the mount fetch's now-stale response would land
|
|
@@ -152,13 +152,12 @@ function ExportSection({ mailboxUid, mailboxLabel }: { mailboxUid?: string; mail
|
|
|
152
152
|
|
|
153
153
|
async function handleCreate(e: FormEvent) {
|
|
154
154
|
e.preventDefault();
|
|
155
|
-
setCreateError(null);
|
|
156
155
|
setCreating(true);
|
|
157
156
|
try {
|
|
158
157
|
await createExportRequest({ format });
|
|
159
158
|
await loadRequests();
|
|
160
159
|
} catch (err) {
|
|
161
|
-
|
|
160
|
+
notifyApiError(err, "Couldn't start the export");
|
|
162
161
|
} finally {
|
|
163
162
|
setCreating(false);
|
|
164
163
|
}
|
|
@@ -191,7 +190,6 @@ function ExportSection({ mailboxUid, mailboxLabel }: { mailboxUid?: string; mail
|
|
|
191
190
|
</Button>
|
|
192
191
|
</form>
|
|
193
192
|
|
|
194
|
-
{createError && <Alert>{createError}</Alert>}
|
|
195
193
|
{loadError && <Alert>{loadError}</Alert>}
|
|
196
194
|
|
|
197
195
|
{loading ? (
|
|
@@ -242,7 +240,6 @@ function ImportSection({ mailboxUid }: { mailboxUid?: string }) {
|
|
|
242
240
|
const [loading, setLoading] = useState(true);
|
|
243
241
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
244
242
|
const [uploading, setUploading] = useState(false);
|
|
245
|
-
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
246
243
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
247
244
|
|
|
248
245
|
// Same stale-response guard as ExportSection's loadRequests() - the Upload button is enabled
|
|
@@ -287,11 +284,10 @@ function ImportSection({ mailboxUid }: { mailboxUid?: string }) {
|
|
|
287
284
|
if (!file || !targetFolderUid) {
|
|
288
285
|
return;
|
|
289
286
|
}
|
|
290
|
-
setUploadError(null);
|
|
291
287
|
setUploading(true);
|
|
292
288
|
uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid })
|
|
293
289
|
.then(() => loadRequests())
|
|
294
|
-
.catch((err) =>
|
|
290
|
+
.catch((err) => notifyApiError(err, "Couldn't upload the file"))
|
|
295
291
|
.finally(() => setUploading(false));
|
|
296
292
|
}
|
|
297
293
|
|
|
@@ -341,7 +337,6 @@ function ImportSection({ mailboxUid }: { mailboxUid?: string }) {
|
|
|
341
337
|
/>
|
|
342
338
|
|
|
343
339
|
{foldersError && <Alert>{foldersError}</Alert>}
|
|
344
|
-
{uploadError && <Alert>{uploadError}</Alert>}
|
|
345
340
|
{loadError && <Alert>{loadError}</Alert>}
|
|
346
341
|
|
|
347
342
|
{loading ? (
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import { routedPage } from "../../_routedPage.js";
|
|
6
6
|
import React, { FormEvent, useState } from "react";
|
|
7
|
-
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
7
|
import { updateMailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
8
|
import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
10
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
9
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
10
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
12
11
|
|
|
13
12
|
export type SettingsReadReceiptsPageProps = Omit<SettingsShellProps, "active">;
|
|
14
13
|
|
|
@@ -34,13 +33,11 @@ function ReadReceiptsContent() {
|
|
|
34
33
|
const [autoSendReceiptsExternal, setAutoSendReceiptsExternal] = useState(mailbox.autoSendReceiptsExternal ?? false);
|
|
35
34
|
// See auto-reply/index.tsx's identical note - later saves must carry the version the previous save returned.
|
|
36
35
|
const [version, setVersion] = useState(mailbox.version);
|
|
37
|
-
const [error, setError] = useState<string | null>(null);
|
|
38
36
|
const [saving, setSaving] = useState(false);
|
|
39
37
|
const [saved, setSaved] = useState(false);
|
|
40
38
|
|
|
41
39
|
async function handleSubmit(e: FormEvent) {
|
|
42
40
|
e.preventDefault();
|
|
43
|
-
setError(null);
|
|
44
41
|
setSaved(false);
|
|
45
42
|
setSaving(true);
|
|
46
43
|
try {
|
|
@@ -57,7 +54,7 @@ function ReadReceiptsContent() {
|
|
|
57
54
|
setVersion(updated.version);
|
|
58
55
|
setSaved(true);
|
|
59
56
|
} catch (err) {
|
|
60
|
-
|
|
57
|
+
notifyApiError(err, "Couldn't save the read receipt settings");
|
|
61
58
|
} finally {
|
|
62
59
|
setSaving(false);
|
|
63
60
|
}
|
|
@@ -73,8 +70,7 @@ function ReadReceiptsContent() {
|
|
|
73
70
|
checkbox in Compose always overrides the request-side settings below for that one message.
|
|
74
71
|
</p>
|
|
75
72
|
|
|
76
|
-
{
|
|
77
|
-
{saved && !error && <div className="mb-4 text-sm text-success font-medium">Saved.</div>}
|
|
73
|
+
{saved && <div className="mb-4 text-sm text-success font-medium">Saved.</div>}
|
|
78
74
|
|
|
79
75
|
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
|
80
76
|
<fieldset className="flex flex-col gap-2">
|