@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
|
@@ -2,68 +2,69 @@
|
|
|
2
2
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
6
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
listMailboxAccess,
|
|
9
|
+
MailboxAccessMember,
|
|
10
|
+
MailboxAccessRole,
|
|
11
|
+
removeMailboxAccess,
|
|
12
|
+
setMailboxAccess,
|
|
13
|
+
} from "@rapidmx/react-shared/mail/mailboxAccessApi.js";
|
|
8
14
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
9
15
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
10
16
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
17
|
+
import PrincipalPicker from "../../sharing/PrincipalPicker.js";
|
|
11
18
|
|
|
12
|
-
const
|
|
19
|
+
const ROLE_LABELS: Record<MailboxAccessRole, string> = { viewer: "Read only", manager: "Full access" };
|
|
13
20
|
|
|
14
21
|
export interface ShareAccessCardProps {
|
|
15
22
|
mailboxUid: string;
|
|
16
|
-
/** The mailbox's owner
|
|
23
|
+
/** The mailbox's owner: with one, only the owner can grant access to it. Without, the mailbox is shared and an
|
|
24
|
+
* administrator grants access here - to themselves included. */
|
|
17
25
|
ownerUserUid?: string;
|
|
26
|
+
/** The signed-in administrator, for "Add me" on a shared mailbox. */
|
|
27
|
+
currentUserUid?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function roleLabel(member: MailboxAccessMember): string {
|
|
31
|
+
if (member.role === "viewer") return ROLE_LABELS.viewer;
|
|
32
|
+
if (member.role === "manager") return ROLE_LABELS.manager;
|
|
33
|
+
return (member.actions ?? []).join(", ");
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
37
|
+
* A mailbox's members - the mechanism behind Exchange-style shared mailboxes - through the Sharing endpoints
|
|
38
|
+
* (`/mail/mailboxes/:id/access`), the audited way an administrator reaches a mailbox they hold no grant on. An
|
|
39
|
+
* administrator can review any mailbox's members and revoke any of them; they can grant access only on a shared
|
|
40
|
+
* (ownerless) mailbox - themselves included, which is how it appears in their own mail client. A personal mailbox is
|
|
41
|
+
* shared by its owner (or by an administrator impersonating them).
|
|
24
42
|
*/
|
|
25
|
-
export default function ShareAccessCard({ mailboxUid, ownerUserUid }: ShareAccessCardProps) {
|
|
26
|
-
const [
|
|
43
|
+
export default function ShareAccessCard({ mailboxUid, ownerUserUid, currentUserUid }: ShareAccessCardProps) {
|
|
44
|
+
const [members, setMembers] = useState<MailboxAccessMember[]>([]);
|
|
27
45
|
const [loading, setLoading] = useState(true);
|
|
28
46
|
const [error, setError] = useState<string | null>(null);
|
|
29
|
-
const [notice, setNotice] = useState<string | null>(null);
|
|
30
|
-
const [newUserUid, setNewUserUid] = useState("");
|
|
31
47
|
const [saving, setSaving] = useState(false);
|
|
32
48
|
const [revokeTarget, setRevokeTarget] = useState<string | null>(null);
|
|
49
|
+
// An entry that is not a user uid, being replaced with the user it was meant for.
|
|
50
|
+
const [replacing, setReplacing] = useState<MailboxAccessMember | null>(null);
|
|
33
51
|
|
|
34
52
|
function reload() {
|
|
35
53
|
setLoading(true);
|
|
36
54
|
setError(null);
|
|
37
|
-
|
|
38
|
-
.then(
|
|
55
|
+
listMailboxAccess(mailboxUid)
|
|
56
|
+
.then(setMembers)
|
|
39
57
|
.catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load share settings."))
|
|
40
58
|
.finally(() => setLoading(false));
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
useEffect(reload, [mailboxUid]);
|
|
44
62
|
|
|
45
|
-
async function
|
|
46
|
-
e.preventDefault();
|
|
47
|
-
const userUid = newUserUid.trim();
|
|
48
|
-
if (!userUid) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
63
|
+
async function addMe(userUid: string) {
|
|
51
64
|
setSaving(true);
|
|
52
65
|
setError(null);
|
|
53
|
-
setNotice(null);
|
|
54
66
|
try {
|
|
55
|
-
|
|
56
|
-
// who already has more (e.g. the owner) would silently downgrade them. Merge with their current actions.
|
|
57
|
-
const current = await getMailboxAcl(mailboxUid);
|
|
58
|
-
const existing = current.records.find((record) => record.userOrRoleId === userUid)?.actions ?? [];
|
|
59
|
-
const missing = DEFAULT_DELEGATE_ACTIONS.filter((action) => !existing.includes(action));
|
|
60
|
-
if (missing.length === 0) {
|
|
61
|
-
setNotice(`${userUid} already has this access.`);
|
|
62
|
-
setNewUserUid("");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
await grantMailboxAccess(mailboxUid, userUid, [...existing, ...missing]);
|
|
66
|
-
setNewUserUid("");
|
|
67
|
+
await setMailboxAccess(mailboxUid, userUid, "manager");
|
|
67
68
|
reload();
|
|
68
69
|
} catch (err) {
|
|
69
70
|
setError(err instanceof ApiRequestError ? err.message : "Could not grant access.");
|
|
@@ -72,15 +73,33 @@ export default function ShareAccessCard({ mailboxUid, ownerUserUid }: ShareAcces
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Once the person is granted, the entry they replace (if any) is removed: the string it named never matched anyone.
|
|
77
|
+
async function handleGranted() {
|
|
78
|
+
const stale = replacing;
|
|
79
|
+
setReplacing(null);
|
|
80
|
+
let failure: string | null = null;
|
|
81
|
+
if (stale) {
|
|
82
|
+
try {
|
|
83
|
+
await removeMailboxAccess(mailboxUid, stale.userOrRoleId);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
failure = err instanceof ApiRequestError ? err.message : "Could not remove the old entry.";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
reload();
|
|
89
|
+
// After the reload, which clears the error it started with.
|
|
90
|
+
if (failure) {
|
|
91
|
+
setError(failure);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
// Only ever invoked from the revoke-confirmation modal below, which only renders once `revokeTarget` is set.
|
|
76
96
|
async function handleRevoke() {
|
|
77
97
|
const userOrRoleId = revokeTarget!;
|
|
78
98
|
setRevokeTarget(null);
|
|
79
99
|
setSaving(true);
|
|
80
100
|
setError(null);
|
|
81
|
-
setNotice(null);
|
|
82
101
|
try {
|
|
83
|
-
await
|
|
102
|
+
await removeMailboxAccess(mailboxUid, userOrRoleId);
|
|
84
103
|
reload();
|
|
85
104
|
} catch (err) {
|
|
86
105
|
setError(err instanceof ApiRequestError ? err.message : "Could not revoke access.");
|
|
@@ -89,61 +108,89 @@ export default function ShareAccessCard({ mailboxUid, ownerUserUid }: ShareAcces
|
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
110
|
|
|
111
|
+
const shared = !ownerUserUid;
|
|
112
|
+
const isMember = members.some((member) => member.userOrRoleId === currentUserUid);
|
|
113
|
+
|
|
92
114
|
return (
|
|
93
115
|
<div className="bg-surface border border-border rounded-md p-6">
|
|
94
116
|
<h2 className="text-base font-bold uppercase tracking-wide mb-1">Shared access</h2>
|
|
95
117
|
<p className="text-sm text-text-muted mb-4">
|
|
96
|
-
|
|
118
|
+
{shared
|
|
119
|
+
? "Who has access to this shared mailbox. Adding yourself is how it appears in your own mail client; every change is recorded in the audit log."
|
|
120
|
+
: "Who the owner has shared this mailbox with. You can review and revoke access; only the owner can grant it (impersonate them to do so)."}
|
|
97
121
|
</p>
|
|
98
122
|
|
|
99
123
|
{error && <Alert>{error}</Alert>}
|
|
100
|
-
{notice && !error && <div className="mb-4 text-sm text-text-muted">{notice}</div>}
|
|
101
124
|
|
|
102
125
|
{loading ? (
|
|
103
126
|
<p className="text-sm text-text-muted">Loading…</p>
|
|
104
127
|
) : (
|
|
105
128
|
<ul className="flex flex-col gap-2 mb-4">
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
)}
|
|
109
|
-
{acl?.records.map((record) => (
|
|
129
|
+
{members.length === 0 && <li className="text-sm text-text-muted">No grants on this mailbox yet.</li>}
|
|
130
|
+
{members.map((member) => (
|
|
110
131
|
<li
|
|
111
|
-
key={
|
|
132
|
+
key={member.userOrRoleId}
|
|
112
133
|
className="flex items-center justify-between gap-3 py-2 px-3 border border-border rounded-sm"
|
|
113
134
|
>
|
|
114
135
|
<div>
|
|
115
|
-
<div className="text-sm font-medium">{
|
|
116
|
-
<div className="text-xs text-text-muted">{
|
|
136
|
+
<div className="text-sm font-medium">{member.userOrRoleId}</div>
|
|
137
|
+
<div className="text-xs text-text-muted">{roleLabel(member)}</div>
|
|
138
|
+
{member.noEffect && (
|
|
139
|
+
<div className="text-xs text-danger mt-0.5">
|
|
140
|
+
Not a user - this entry has no effect
|
|
141
|
+
{shared ? " - replace it with the user it was meant for or remove it." : " - remove it."}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
117
144
|
</div>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
<span className="flex items-center gap-3 shrink-0">
|
|
146
|
+
{member.noEffect && shared && (
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={() => setReplacing(member)}
|
|
150
|
+
disabled={saving}
|
|
151
|
+
className="text-sm text-primary-dark hover:underline disabled:opacity-55"
|
|
152
|
+
>
|
|
153
|
+
Replace with a user
|
|
154
|
+
</button>
|
|
155
|
+
)}
|
|
121
156
|
<button
|
|
122
157
|
type="button"
|
|
123
|
-
onClick={() => setRevokeTarget(
|
|
158
|
+
onClick={() => setRevokeTarget(member.userOrRoleId)}
|
|
124
159
|
disabled={saving}
|
|
125
160
|
className="text-sm text-danger hover:underline disabled:opacity-55"
|
|
126
161
|
>
|
|
127
162
|
Revoke
|
|
128
163
|
</button>
|
|
129
|
-
|
|
164
|
+
</span>
|
|
130
165
|
</li>
|
|
131
166
|
))}
|
|
132
167
|
</ul>
|
|
133
168
|
)}
|
|
134
169
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
{shared && (
|
|
171
|
+
<>
|
|
172
|
+
{currentUserUid && !isMember && !loading && (
|
|
173
|
+
<Button
|
|
174
|
+
type="button"
|
|
175
|
+
variant="secondary"
|
|
176
|
+
className="!w-auto mb-3"
|
|
177
|
+
disabled={saving}
|
|
178
|
+
onClick={() => void addMe(currentUserUid)}
|
|
179
|
+
>
|
|
180
|
+
Add me
|
|
181
|
+
</Button>
|
|
182
|
+
)}
|
|
183
|
+
<PrincipalPicker
|
|
184
|
+
key={replacing?.userOrRoleId ?? "new"}
|
|
185
|
+
mailboxUid={mailboxUid}
|
|
186
|
+
roleLabels={ROLE_LABELS}
|
|
187
|
+
initialPrincipal={replacing?.userOrRoleId}
|
|
188
|
+
defaultRole={replacing?.role === "manager" ? "manager" : "viewer"}
|
|
189
|
+
onGranted={handleGranted}
|
|
190
|
+
onCancel={replacing ? () => setReplacing(null) : undefined}
|
|
191
|
+
/>
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
147
194
|
|
|
148
195
|
<Modal open={revokeTarget !== null} onClose={() => setRevokeTarget(null)} title="Revoke access">
|
|
149
196
|
<p className="text-sm mb-5">
|
|
@@ -16,12 +16,19 @@ import {
|
|
|
16
16
|
} from "@rapidmx/react-shared/branding/brandingApi.js";
|
|
17
17
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
18
18
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
19
|
+
import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
|
|
19
20
|
|
|
20
21
|
const INPUT_CLASS =
|
|
21
22
|
"w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
22
23
|
|
|
23
24
|
const MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
24
25
|
|
|
26
|
+
/** A header that uses both variables - the snippet the Branding page offers to copy. */
|
|
27
|
+
export const HEADER_HTML_EXAMPLE = `<div style="display:flex; align-items:center; justify-content:space-between; padding:0.5rem 1rem">
|
|
28
|
+
<span>{APP_TITLE}</span>
|
|
29
|
+
<span>{USER_MENU}</span>
|
|
30
|
+
</div>`;
|
|
31
|
+
|
|
25
32
|
/** SVG logo/icon uploads are refused (the server refuses them too): an SVG served from this origin can carry script. */
|
|
26
33
|
export function isSvgFile(file: File): boolean {
|
|
27
34
|
return file.type === "image/svg+xml" || file.name.toLowerCase().endsWith(".svg");
|
|
@@ -376,7 +383,21 @@ export default function BrandingForm({ branding, onChange, embedded = false }: B
|
|
|
376
383
|
value={headerHtml}
|
|
377
384
|
onChange={(e) => setHeaderHtml(e.target.value)}
|
|
378
385
|
/>
|
|
386
|
+
<span className="text-xs text-text-muted">
|
|
387
|
+
A header replaces the app's own title bar and the icon at the top of the icon rail, and stays at the top of the window. Write{" "}
|
|
388
|
+
<code>{"{USER_MENU}"}</code> where the account menu (avatar, Settings, Sign Out) should go, and <code>{"{APP_TITLE}"}</code>{" "}
|
|
389
|
+
where the name of the app on screen should show. Both work in the footer too. <code>{"{USER_MENU}"}</code> is replaced once
|
|
390
|
+
(the header's, else the footer's); without it the menu sits in a small cell at the right end of the header, so it is never lost.
|
|
391
|
+
Put it where your styles don't hide it on a phone.
|
|
392
|
+
</span>
|
|
379
393
|
</label>
|
|
394
|
+
<div className="flex flex-col gap-1.5 text-sm" role="group" aria-label="Header example">
|
|
395
|
+
<div className="flex items-center justify-between gap-2">
|
|
396
|
+
<span className="font-semibold">Example header</span>
|
|
397
|
+
<CopyButton value={HEADER_HTML_EXAMPLE} label="Copy the example header HTML" />
|
|
398
|
+
</div>
|
|
399
|
+
<pre className="overflow-x-auto rounded-sm border border-border bg-surface-alt p-3 font-mono text-xs text-text">{HEADER_HTML_EXAMPLE}</pre>
|
|
400
|
+
</div>
|
|
380
401
|
<label className="flex flex-col gap-1.5 text-sm">
|
|
381
402
|
<span className="font-semibold">Footer HTML (optional)</span>
|
|
382
403
|
<textarea
|
|
@@ -386,6 +407,9 @@ export default function BrandingForm({ branding, onChange, embedded = false }: B
|
|
|
386
407
|
value={footerHtml}
|
|
387
408
|
onChange={(e) => setFooterHtml(e.target.value)}
|
|
388
409
|
/>
|
|
410
|
+
<span className="text-xs text-text-muted">
|
|
411
|
+
<code>{"{APP_TITLE}"}</code> works here as well, and <code>{"{USER_MENU}"}</code> takes the account menu (opening upward) when the header has none.
|
|
412
|
+
</span>
|
|
389
413
|
</label>
|
|
390
414
|
<div>
|
|
391
415
|
<Button type="submit" loading={saving} disabled={saving} className="!w-auto">
|
|
@@ -15,6 +15,8 @@ const RECORD_TYPE_LABELS: Record<DnsRecordCheck["type"], string> = {
|
|
|
15
15
|
spf: "SPF",
|
|
16
16
|
dkim: "DKIM",
|
|
17
17
|
dmarc: "DMARC",
|
|
18
|
+
autodiscover_cname: "Autodiscover (CNAME)",
|
|
19
|
+
autodiscover_srv: "Autodiscover (SRV)",
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
/** How each record is named in a Copy button's accessible name ("Copy value for the SPF record"). */
|
|
@@ -24,6 +26,17 @@ const RECORD_TYPE_NAMES: Record<DnsRecordCheck["type"], string> = {
|
|
|
24
26
|
spf: "SPF",
|
|
25
27
|
dkim: "DKIM",
|
|
26
28
|
dmarc: "DMARC",
|
|
29
|
+
autodiscover_cname: "Autodiscover CNAME",
|
|
30
|
+
autodiscover_srv: "Autodiscover SRV",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A one-line "why this matters", shown under a checklist row's label - only for record types unfamiliar
|
|
34
|
+
* enough to need one; the well-known mail records (ownership/MX/SPF/DKIM/DMARC) don't. */
|
|
35
|
+
const RECORD_TYPE_HELP: Partial<Record<DnsRecordCheck["type"], string>> = {
|
|
36
|
+
autodiscover_cname:
|
|
37
|
+
"So EAS and Outlook clients can find this server automatically from just an email address. This hostname also needs to be covered by this server's TLS certificate.",
|
|
38
|
+
autodiscover_srv:
|
|
39
|
+
"Same purpose as the CNAME above, for clients that check DNS SRV instead - and, unlike the CNAME, needs no extra TLS certificate, since it points at a hostname already covered. Prefer this one when a second certificate is hard to obtain.",
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
/** An MX record's recommended value is `"<priority> <mail server>"` - two separate fields in a DNS provider's form. */
|
|
@@ -32,6 +45,12 @@ function splitMxValue(value: string): { priority: string; server: string } | nul
|
|
|
32
45
|
return match ? { priority: match[1], server: match[2] } : null;
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
/** A SRV record's recommended value is `"<priority> <weight> <port> <target>"` - four separate fields in a DNS provider's form. */
|
|
49
|
+
function splitSrvValue(value: string): { priority: string; weight: string; port: string; target: string } | null {
|
|
50
|
+
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)$/.exec(value.trim());
|
|
51
|
+
return match ? { priority: match[1], weight: match[2], port: match[3], target: match[4] } : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
/** A value to type into a DNS provider's form, shown in full (long DKIM keys wrap) with a Copy button beside it. */
|
|
36
55
|
function CopyableValue({ value, copyLabel }: { value: string; copyLabel: string }) {
|
|
37
56
|
return (
|
|
@@ -181,6 +200,9 @@ export default function DomainDnsSetup({ uid, onLoaded }: DomainDnsSetupProps) {
|
|
|
181
200
|
<td className="py-2.5 px-2.5 border-b border-border">
|
|
182
201
|
{RECORD_TYPE_LABELS[check.type]}
|
|
183
202
|
<div className="text-xs text-text-muted">Type: {check.recordKind}</div>
|
|
203
|
+
{RECORD_TYPE_HELP[check.type] && (
|
|
204
|
+
<div className="text-xs text-text-muted mt-1 max-w-xs">{RECORD_TYPE_HELP[check.type]}</div>
|
|
205
|
+
)}
|
|
184
206
|
</td>
|
|
185
207
|
<td className="py-2.5 px-2.5 border-b border-border">
|
|
186
208
|
{!check.configured ? (
|
|
@@ -228,23 +250,47 @@ export default function DomainDnsSetup({ uid, onLoaded }: DomainDnsSetupProps) {
|
|
|
228
250
|
);
|
|
229
251
|
}
|
|
230
252
|
|
|
231
|
-
/** The value cell of a checklist row: an MX value as its two form fields (priority and mail server),
|
|
253
|
+
/** The value cell of a checklist row: an MX value as its two form fields (priority and mail server), a SRV value as
|
|
254
|
+
* its four form fields (priority, weight, port and target), anything else whole. */
|
|
232
255
|
function DnsRecordValue({ check, value }: { check: DnsRecordCheck; value: string }) {
|
|
233
256
|
const recordName = RECORD_TYPE_NAMES[check.type];
|
|
234
257
|
const mx = check.type === "mx" ? splitMxValue(value) : null;
|
|
235
|
-
if (
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<
|
|
258
|
+
if (mx) {
|
|
259
|
+
return (
|
|
260
|
+
<div className="flex flex-col gap-1.5">
|
|
261
|
+
<div className="flex items-start gap-2">
|
|
262
|
+
<span className="text-xs w-20 shrink-0 pt-1">Priority</span>
|
|
263
|
+
<CopyableValue value={mx.priority} copyLabel={`Copy priority for the ${recordName} record`} />
|
|
264
|
+
</div>
|
|
265
|
+
<div className="flex items-start gap-2">
|
|
266
|
+
<span className="text-xs w-20 shrink-0 pt-1">Mail server</span>
|
|
267
|
+
<CopyableValue value={mx.server} copyLabel={`Copy mail server for the ${recordName} record`} />
|
|
268
|
+
</div>
|
|
243
269
|
</div>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const srv = check.type === "autodiscover_srv" ? splitSrvValue(value) : null;
|
|
273
|
+
if (srv) {
|
|
274
|
+
return (
|
|
275
|
+
<div className="flex flex-col gap-1.5">
|
|
276
|
+
<div className="flex items-start gap-2">
|
|
277
|
+
<span className="text-xs w-20 shrink-0 pt-1">Priority</span>
|
|
278
|
+
<CopyableValue value={srv.priority} copyLabel={`Copy priority for the ${recordName} record`} />
|
|
279
|
+
</div>
|
|
280
|
+
<div className="flex items-start gap-2">
|
|
281
|
+
<span className="text-xs w-20 shrink-0 pt-1">Weight</span>
|
|
282
|
+
<CopyableValue value={srv.weight} copyLabel={`Copy weight for the ${recordName} record`} />
|
|
283
|
+
</div>
|
|
284
|
+
<div className="flex items-start gap-2">
|
|
285
|
+
<span className="text-xs w-20 shrink-0 pt-1">Port</span>
|
|
286
|
+
<CopyableValue value={srv.port} copyLabel={`Copy port for the ${recordName} record`} />
|
|
287
|
+
</div>
|
|
288
|
+
<div className="flex items-start gap-2">
|
|
289
|
+
<span className="text-xs w-20 shrink-0 pt-1">Target</span>
|
|
290
|
+
<CopyableValue value={srv.target} copyLabel={`Copy target for the ${recordName} record`} />
|
|
291
|
+
</div>
|
|
247
292
|
</div>
|
|
248
|
-
|
|
249
|
-
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return <CopyableValue value={value} copyLabel={`Copy value for the ${recordName} record`} />;
|
|
250
296
|
}
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import React, { FormEvent, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
7
7
|
import { getMailboxPolicy } from "@rapidmx/react-shared/admin/mailboxPolicyApi.js";
|
|
8
|
-
import { createMailbox, listMailboxDomains, Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
8
|
+
import { createMailbox, listMailboxDomains, Mailbox, resolveMailboxOwner, ResolvedPrincipal } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
9
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
10
10
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
11
11
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
12
|
+
import PrincipalResolver, { describePerson } from "../../sharing/PrincipalResolver.js";
|
|
12
13
|
|
|
13
14
|
const INPUT_CLASS =
|
|
14
15
|
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
@@ -33,7 +34,8 @@ export default function MailboxCreateForm({ onCreated, defaults, submitLabel = "
|
|
|
33
34
|
const [domains, setDomains] = useState<string[]>([]);
|
|
34
35
|
const [domain, setDomain] = useState("");
|
|
35
36
|
const [displayName, setDisplayName] = useState(defaults?.displayName ?? "");
|
|
36
|
-
const [
|
|
37
|
+
const [ownerMode, setOwnerMode] = useState<"shared" | "owned">(defaults?.ownerUserUid ? "owned" : "shared");
|
|
38
|
+
const [resolvedOwner, setResolvedOwner] = useState<ResolvedPrincipal | null>(null);
|
|
37
39
|
const [timezone, setTimezone] = useState("UTC");
|
|
38
40
|
const [quotaGb, setQuotaGb] = useState(5);
|
|
39
41
|
// Only replaced by the mailbox policy's default if the admin hasn't typed a quota of their own yet.
|
|
@@ -90,16 +92,19 @@ export default function MailboxCreateForm({ onCreated, defaults, submitLabel = "
|
|
|
90
92
|
setError("A display name can't contain \"@\" (or a look-alike) or line breaks.");
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
95
|
+
if (ownerMode === "owned" && !resolvedOwner) {
|
|
96
|
+
setError("Look up and confirm the mailbox's owner, or choose a shared mailbox instead.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
93
99
|
|
|
94
100
|
setSaving(true);
|
|
95
101
|
try {
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// ownerless the same way — leave "Owner user uid" blank for one too.
|
|
102
|
+
// "Shared mailbox" creates a true ownerless mailbox (e.g. support@example.com); delegates are then
|
|
103
|
+
// granted access from the mailbox's detail page. A resource mailbox is ownerless the same way.
|
|
99
104
|
const mailbox = await createMailbox({
|
|
100
105
|
primarySmtpAddress: address,
|
|
101
106
|
displayName: displayName.trim(),
|
|
102
|
-
ownerUserUid:
|
|
107
|
+
ownerUserUid: ownerMode === "owned" ? resolvedOwner!.userUid : undefined,
|
|
103
108
|
timezone,
|
|
104
109
|
quotaBytes: Math.round(quotaGb * 1_000_000_000),
|
|
105
110
|
...(isResource && {
|
|
@@ -175,16 +180,60 @@ export default function MailboxCreateForm({ onCreated, defaults, submitLabel = "
|
|
|
175
180
|
/>
|
|
176
181
|
</FormField>
|
|
177
182
|
|
|
178
|
-
<
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
<div className="mb-4">
|
|
184
|
+
<span className="block text-sm font-semibold mb-1.5 text-text">Owner</span>
|
|
185
|
+
<div className="flex flex-col gap-2">
|
|
186
|
+
<label className="flex items-center gap-2 text-sm">
|
|
187
|
+
<input
|
|
188
|
+
type="radio"
|
|
189
|
+
name="ownerMode"
|
|
190
|
+
checked={ownerMode === "shared"}
|
|
191
|
+
onChange={() => {
|
|
192
|
+
setOwnerMode("shared");
|
|
193
|
+
setResolvedOwner(null);
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
Shared mailbox (no single owner)
|
|
197
|
+
</label>
|
|
198
|
+
<label className="flex items-center gap-2 text-sm">
|
|
199
|
+
<input
|
|
200
|
+
type="radio"
|
|
201
|
+
name="ownerMode"
|
|
202
|
+
checked={ownerMode === "owned"}
|
|
203
|
+
onChange={() => setOwnerMode("owned")}
|
|
204
|
+
/>
|
|
205
|
+
Owned by a specific person
|
|
206
|
+
</label>
|
|
207
|
+
{ownerMode === "owned" &&
|
|
208
|
+
(resolvedOwner ? (
|
|
209
|
+
<div className="flex items-center justify-between gap-3 text-sm py-2 px-3 border border-border rounded-sm">
|
|
210
|
+
<span>
|
|
211
|
+
Owner: <strong className="break-all">{describePerson(resolvedOwner)}</strong>
|
|
212
|
+
</span>
|
|
213
|
+
<Button type="button" variant="text" className="!w-auto" onClick={() => setResolvedOwner(null)}>
|
|
214
|
+
Change
|
|
215
|
+
</Button>
|
|
216
|
+
</div>
|
|
217
|
+
) : (
|
|
218
|
+
<PrincipalResolver
|
|
219
|
+
resolve={resolveMailboxOwner}
|
|
220
|
+
onResolved={(person) => {
|
|
221
|
+
setResolvedOwner(person);
|
|
222
|
+
}}
|
|
223
|
+
initialPrincipal={defaults?.ownerUserUid}
|
|
224
|
+
placeholder="Email address, username or user id"
|
|
225
|
+
ariaLabel="Mailbox owner"
|
|
226
|
+
confirmLabel="Use this owner"
|
|
227
|
+
confirmErrorMessage="Could not confirm this person."
|
|
228
|
+
describeConfirm={(person) => (
|
|
229
|
+
<>
|
|
230
|
+
Make <strong className="break-all">{describePerson(person)}</strong> the owner?
|
|
231
|
+
</>
|
|
232
|
+
)}
|
|
233
|
+
/>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
188
237
|
|
|
189
238
|
<FormField label="Timezone" htmlFor="timezone">
|
|
190
239
|
<input
|