@rapidmx/web-client 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +25 -8
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
- package/apps/shared/components/layout/AppShell.tsx +36 -18
- package/apps/shared/components/mail/layout/MailShell.tsx +420 -418
- package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -9
- package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
- package/apps/shared/plugins/pluginNav.ts +73 -0
- package/apps/shared/search/localIndexRpcClient.ts +3 -1
- package/dist/apps/admin/_404.d.ts +2 -0
- package/dist/apps/admin/_500.d.ts +2 -0
- package/dist/apps/admin/_layout.d.ts +8 -0
- package/dist/apps/admin/audit-log/index.d.ts +11 -0
- package/dist/apps/admin/branding/index.d.ts +3 -0
- package/dist/apps/admin/data-requests/index.d.ts +3 -0
- package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
- package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
- package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
- package/dist/apps/admin/domains/[uid].d.ts +7 -0
- package/dist/apps/admin/domains/index.d.ts +3 -0
- package/dist/apps/admin/domains/new/index.d.ts +3 -0
- package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
- package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
- package/dist/apps/admin/index.d.ts +3 -0
- package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
- package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
- package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
- package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
- package/dist/apps/admin/plugins/index.d.ts +3 -0
- package/dist/apps/admin/quarantine/index.d.ts +6 -0
- package/dist/apps/admin/retention-policy/index.d.ts +3 -0
- package/dist/apps/admin/setup/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
- package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
- package/dist/apps/admin/transport-rules/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
- package/dist/apps/escrow/_layout.d.ts +10 -0
- package/dist/apps/escrow/audit-log/index.d.ts +9 -0
- package/dist/apps/escrow/index.d.ts +3 -0
- package/dist/apps/escrow/matters/[uid].d.ts +7 -0
- package/dist/apps/escrow/matters/new/index.d.ts +3 -0
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.js +12 -5
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +10 -0
- package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +4 -0
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
- package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
- package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
- package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
- package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
- package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
- package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
- package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +2 -2
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
- package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
- package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
- package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
- package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
- package/dist/apps/shared/components/layout/AppShell.js +15 -13
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
- package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +23 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +93 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +58 -0
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
- package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +34 -0
- package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +57 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +2 -2
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
- package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
- package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
- package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -9
- package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
- package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
- package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
- package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
- package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
- package/dist/apps/shared/plugins/pluginNav.js +33 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
- package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
- package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
- package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
- package/dist/apps/shared/search/localIndexRpcClient.js +3 -1
- package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
- package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
- package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
- package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
- package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
- package/dist/apps/shared/search/searchTier2.d.ts +30 -0
- package/dist/apps/www/_404.d.ts +2 -0
- package/dist/apps/www/_500.d.ts +2 -0
- package/dist/apps/www/_layout.d.ts +14 -0
- package/dist/apps/www/calendar/index.d.ts +3 -0
- package/dist/apps/www/contacts/[uid].d.ts +13 -0
- package/dist/apps/www/contacts/index.d.ts +3 -0
- package/dist/apps/www/index.d.ts +3 -0
- package/dist/apps/www/messages/[uid].d.ts +11 -0
- package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
- package/dist/apps/www/settings/encryption/index.d.ts +7 -0
- package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
- package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
- package/dist/apps/www/settings/filters/index.d.ts +4 -0
- package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
- package/dist/apps/www/settings/focused-inbox/index.d.ts +4 -0
- package/dist/apps/www/settings/labels/index.d.ts +4 -0
- package/dist/apps/www/settings/privacy/index.d.ts +4 -0
- package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
- package/dist/apps/www/settings/sharing/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
- package/dist/apps/www/settings/signatures/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
- package/dist/apps/www/tasks/index.d.ts +3 -0
- package/package.json +3 -3
- package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
- package/apps/www/settings/booking-types/[uid].tsx +0 -306
- package/apps/www/settings/booking-types/index.tsx +0 -98
- package/apps/www/settings/booking-types/new/index.tsx +0 -212
- package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
- package/dist/apps/www/settings/booking-types/[uid].js +0 -134
- package/dist/apps/www/settings/booking-types/index.js +0 -29
- package/dist/apps/www/settings/booking-types/new/index.js +0 -86
|
@@ -1,197 +1,199 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
|
6
|
-
import { HiOutlineBars3 } from "react-icons/hi2";
|
|
7
|
-
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
-
import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
|
|
9
|
-
import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
10
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
|
-
import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
12
|
-
import AppShell, { AppShellProps } from "../../layout/AppShell.js";
|
|
13
|
-
import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
|
|
14
|
-
|
|
15
|
-
export type ContactsShellProps = Omit<AppShellProps, "active">;
|
|
16
|
-
|
|
17
|
-
export interface ContactsShellContextValue {
|
|
18
|
-
/** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox). */
|
|
19
|
-
mailboxUid?: string;
|
|
20
|
-
/** The selected mailbox's single `contacts`-type folder. */
|
|
21
|
-
folderUid?: string;
|
|
22
|
-
mailboxes: Mailbox[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const ContactsShellContext = createContext<ContactsShellContextValue>({ mailboxes: [] });
|
|
26
|
-
|
|
27
|
-
/** Reads the mailbox/folder Contacts is currently showing, as resolved by the enclosing `ContactsShell`. */
|
|
28
|
-
export function useContactsShell(): ContactsShellContextValue {
|
|
29
|
-
return useContext(ContactsShellContext);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type Status = "checking" | "error" | "ready";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The Contacts app's shell: unlike `MailShell` (a folder tree, since a mailbox has many mail folders),
|
|
36
|
-
* a mailbox has exactly one well-known `contacts` folder — see `BaseMailboxRoute.create()`'s eager
|
|
37
|
-
* provisioning in `@rapidmx/restapi`, which guarantees it exists for every mailbox. This shell's own
|
|
38
|
-
* sidebar is therefore just a mailbox switcher (when the caller has more than one); the actual contact
|
|
39
|
-
* list/detail UI is `apps/www/contacts/index.tsx`'s own content, rendered as `children` here.
|
|
40
|
-
*/
|
|
41
|
-
export default function ContactsShell({
|
|
42
|
-
userUid,
|
|
43
|
-
authServerUrl,
|
|
44
|
-
impersonating,
|
|
45
|
-
impersonationBaseUrl,
|
|
46
|
-
trusted,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
55
|
-
const [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
mailboxes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// label-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{mb.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { HiOutlineBars3 } from "react-icons/hi2";
|
|
7
|
+
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
+
import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
|
|
9
|
+
import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
10
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
|
+
import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
12
|
+
import AppShell, { AppShellProps } from "../../layout/AppShell.js";
|
|
13
|
+
import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
|
|
14
|
+
|
|
15
|
+
export type ContactsShellProps = Omit<AppShellProps, "active">;
|
|
16
|
+
|
|
17
|
+
export interface ContactsShellContextValue {
|
|
18
|
+
/** The mailbox currently selected (`?mailboxUid=`, or the caller's first accessible mailbox). */
|
|
19
|
+
mailboxUid?: string;
|
|
20
|
+
/** The selected mailbox's single `contacts`-type folder. */
|
|
21
|
+
folderUid?: string;
|
|
22
|
+
mailboxes: Mailbox[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ContactsShellContext = createContext<ContactsShellContextValue>({ mailboxes: [] });
|
|
26
|
+
|
|
27
|
+
/** Reads the mailbox/folder Contacts is currently showing, as resolved by the enclosing `ContactsShell`. */
|
|
28
|
+
export function useContactsShell(): ContactsShellContextValue {
|
|
29
|
+
return useContext(ContactsShellContext);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Status = "checking" | "error" | "ready";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The Contacts app's shell: unlike `MailShell` (a folder tree, since a mailbox has many mail folders),
|
|
36
|
+
* a mailbox has exactly one well-known `contacts` folder — see `BaseMailboxRoute.create()`'s eager
|
|
37
|
+
* provisioning in `@rapidmx/restapi`, which guarantees it exists for every mailbox. This shell's own
|
|
38
|
+
* sidebar is therefore just a mailbox switcher (when the caller has more than one); the actual contact
|
|
39
|
+
* list/detail UI is `apps/www/contacts/index.tsx`'s own content, rendered as `children` here.
|
|
40
|
+
*/
|
|
41
|
+
export default function ContactsShell({
|
|
42
|
+
userUid,
|
|
43
|
+
authServerUrl,
|
|
44
|
+
impersonating,
|
|
45
|
+
impersonationBaseUrl,
|
|
46
|
+
trusted,
|
|
47
|
+
pluginNav,
|
|
48
|
+
children,
|
|
49
|
+
}: PropsWithChildren<ContactsShellProps>) {
|
|
50
|
+
const [status, setStatus] = useState<Status>("checking");
|
|
51
|
+
const [error, setError] = useState<string | null>(null);
|
|
52
|
+
const [mailboxes, setMailboxes] = useState<Mailbox[]>([]);
|
|
53
|
+
const [folders, setFolders] = useState<Folder[]>([]);
|
|
54
|
+
const [folderError, setFolderError] = useState<string | null>(null);
|
|
55
|
+
const [requestedMailboxUid, setRequestedMailboxUid] = useState<string | null>(null);
|
|
56
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setRequestedMailboxUid(new URLSearchParams(window.location.search).get("mailboxUid"));
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!userUid) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
listMailboxes({ limit: 100 })
|
|
67
|
+
.then((result) => {
|
|
68
|
+
setMailboxes(result);
|
|
69
|
+
setStatus("ready");
|
|
70
|
+
})
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
setError(err instanceof ApiRequestError ? err.message : "Could not load your mailboxes.");
|
|
73
|
+
setStatus("error");
|
|
74
|
+
});
|
|
75
|
+
}, [userUid]);
|
|
76
|
+
|
|
77
|
+
const mailboxUid: string | undefined =
|
|
78
|
+
(requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined) ??
|
|
79
|
+
mailboxes[0]?.uid;
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!mailboxUid) {
|
|
83
|
+
setFolders([]);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setFolderError(null);
|
|
87
|
+
listFolders(mailboxUid)
|
|
88
|
+
.then(setFolders)
|
|
89
|
+
.catch((err) => setFolderError(err instanceof ApiRequestError ? err.message : "Could not load this mailbox's contacts folder."));
|
|
90
|
+
}, [mailboxUid]);
|
|
91
|
+
|
|
92
|
+
const folderUid: string | undefined = folders.find((f) => f.type === "contacts")?.uid;
|
|
93
|
+
|
|
94
|
+
const contextValue = useMemo<ContactsShellContextValue>(
|
|
95
|
+
() => ({ mailboxUid, folderUid, mailboxes }),
|
|
96
|
+
[mailboxUid, folderUid, mailboxes],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else
|
|
100
|
+
// for a mailbox-less caller to do here yet, so the icon rail/header don't render at all.
|
|
101
|
+
if (userUid && status === "ready" && !mailboxUid) {
|
|
102
|
+
return <MailboxProvisioning />;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let inner: ReactNode = null;
|
|
106
|
+
if (userUid && status === "checking") {
|
|
107
|
+
// Renders immediately (no network round trip needed) so switching into Contacts never shows a
|
|
108
|
+
// blank pane while `listMailboxes()` is in flight — the real sidebar (favorites/lists/categories)
|
|
109
|
+
// lives in `apps/www/contacts/index.tsx`'s own content, so this mimics its rough shape rather
|
|
110
|
+
// than the (minimal, mailbox-switcher-only) shape of this shell's own sidebar.
|
|
111
|
+
inner = (
|
|
112
|
+
<div className="w-56 shrink-0 border-r border-border flex flex-col gap-4 p-3">
|
|
113
|
+
<Skeleton height="h-9" />
|
|
114
|
+
<SkeletonList count={5} />
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
} else if (userUid && status === "error") {
|
|
118
|
+
inner = (
|
|
119
|
+
<div className="flex-1 flex items-center justify-center p-8">
|
|
120
|
+
<div className="w-full max-w-md">
|
|
121
|
+
<Alert>{error}</Alert>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
} else if (userUid && status === "ready") {
|
|
126
|
+
// A function, not a plain JSX constant — see MailShell's identical comment: it's rendered twice
|
|
127
|
+
// (desktop `<aside>` + mobile `Drawer`), possibly simultaneously mounted, so the `<select>`'s
|
|
128
|
+
// `id`/its `<label>`'s `htmlFor` need a distinct value per instance to avoid a duplicate-id
|
|
129
|
+
// label-association break.
|
|
130
|
+
const sidebarContent = (idPrefix: string) => (
|
|
131
|
+
<>
|
|
132
|
+
{mailboxes.length > 1 && (
|
|
133
|
+
<div>
|
|
134
|
+
<label
|
|
135
|
+
className="block text-xs font-bold uppercase tracking-wide text-text-muted mb-1"
|
|
136
|
+
htmlFor={`${idPrefix}-contacts-mailbox-switcher`}
|
|
137
|
+
>
|
|
138
|
+
Mailbox
|
|
139
|
+
</label>
|
|
140
|
+
<select
|
|
141
|
+
id={`${idPrefix}-contacts-mailbox-switcher`}
|
|
142
|
+
className="w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface"
|
|
143
|
+
value={mailboxUid}
|
|
144
|
+
onChange={(e) => {
|
|
145
|
+
window.location.href = `/contacts?mailboxUid=${encodeURIComponent(e.target.value)}`;
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{mailboxes.map((mb) => (
|
|
149
|
+
<option key={mb.uid} value={mb.uid}>
|
|
150
|
+
{mb.displayName}
|
|
151
|
+
{mb.ownerUserUid ? "" : " (shared)"}
|
|
152
|
+
</option>
|
|
153
|
+
))}
|
|
154
|
+
</select>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
{folderError && <Alert>{folderError}</Alert>}
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
inner = (
|
|
162
|
+
<>
|
|
163
|
+
<aside className="hidden md:flex w-56 shrink-0 bg-surface border-r border-border flex-col p-3 gap-3">
|
|
164
|
+
{sidebarContent("desktop")}
|
|
165
|
+
</aside>
|
|
166
|
+
<Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Mailbox">
|
|
167
|
+
<div className="flex flex-col gap-3">{sidebarContent("mobile")}</div>
|
|
168
|
+
</Drawer>
|
|
169
|
+
<main className="flex-1 min-w-0 flex flex-col">
|
|
170
|
+
{(mailboxes.length > 1 || folderError) && (
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
className="md:hidden m-3 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text"
|
|
174
|
+
aria-label="Open mailbox switcher"
|
|
175
|
+
onClick={() => setDrawerOpen(true)}
|
|
176
|
+
>
|
|
177
|
+
<HiOutlineBars3 size={20} aria-hidden="true" />
|
|
178
|
+
</button>
|
|
179
|
+
)}
|
|
180
|
+
<ContactsShellContext.Provider value={contextValue}>{children}</ContactsShellContext.Provider>
|
|
181
|
+
</main>
|
|
182
|
+
</>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<AppShell
|
|
188
|
+
active="contacts"
|
|
189
|
+
userUid={userUid}
|
|
190
|
+
authServerUrl={authServerUrl}
|
|
191
|
+
impersonating={impersonating}
|
|
192
|
+
impersonationBaseUrl={impersonationBaseUrl}
|
|
193
|
+
trusted={trusted}
|
|
194
|
+
pluginNav={pluginNav}
|
|
195
|
+
>
|
|
196
|
+
{inner}
|
|
197
|
+
</AppShell>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
import "../../styles/app.css";
|
|
6
6
|
import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
|
|
7
7
|
import type { IconType } from "react-icons";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
HiOutlineCalendarDays,
|
|
10
|
+
HiOutlineClipboardDocumentList,
|
|
11
|
+
HiOutlineEnvelope,
|
|
12
|
+
HiOutlinePuzzlePiece,
|
|
13
|
+
HiOutlineUsers,
|
|
14
|
+
} from "react-icons/hi2";
|
|
9
15
|
import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
|
|
10
16
|
import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
|
|
11
17
|
import { stopImpersonating } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
@@ -13,7 +19,7 @@ import useBranding from "@rapidmx/react-shared/branding/useBranding.js";
|
|
|
13
19
|
import { useIdleKeyTimeout } from "@rapidmx/react-shared/crypto/useIdleKeyTimeout.js";
|
|
14
20
|
import ComposeProvider from "../mail/compose/ComposeContext.js";
|
|
15
21
|
import { flushComposeDrafts, markSigningOut } from "../mail/compose/composeFlushRegistry.js";
|
|
16
|
-
import BottomTabBar from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
|
|
22
|
+
import BottomTabBar, { NavItem } from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
|
|
17
23
|
import { BrandingFooter, BrandingHeader } from "./BrandingChrome.js";
|
|
18
24
|
import UserMenu from "./UserMenu.js";
|
|
19
25
|
import { UnlockPromptProvider } from "./UnlockPromptProvider.js";
|
|
@@ -21,6 +27,7 @@ import { SIGN_OUT_CHANNEL, destroyAllLocalIndexes } from "../../search/localInde
|
|
|
21
27
|
import { authApiFetch } from "@rapidmx/react-shared/util/api.js";
|
|
22
28
|
import { destroyUnlockedKeys } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
23
29
|
import { clearPinnedSignerCache } from "../mail/pinnedSigners.js";
|
|
30
|
+
import { mergePluginNavItems, PluginNav, PluginNavProps } from "../../plugins/pluginNav.js";
|
|
24
31
|
|
|
25
32
|
/** How long sign-out waits for auth-server's logout before navigating anyway. */
|
|
26
33
|
export const LOGOUT_TIMEOUT_MS = 3_000;
|
|
@@ -49,11 +56,13 @@ export type AppShellApp = "mail" | "calendar" | "contacts" | "tasks";
|
|
|
49
56
|
|
|
50
57
|
/** `"settings"` is a valid `active` value but deliberately has no entry in `APPS` below — Settings is
|
|
51
58
|
* reached via a `UserMenu` item, not a 5th rail icon (see `SettingsShell.tsx`), so it highlights no
|
|
52
|
-
* rail/tab icon at all; only the header title
|
|
53
|
-
|
|
59
|
+
* rail/tab icon at all; only the header title needs to account for it. Any other string is a plugin's
|
|
60
|
+
* `appRail` item id (see `PluginNav`). */
|
|
61
|
+
export type AppShellActive = AppShellApp | "settings" | (string & {});
|
|
54
62
|
|
|
55
|
-
export interface AppShellProps {
|
|
56
|
-
/** Which icon in the rail is highlighted as the current app — `"settings"` highlights none
|
|
63
|
+
export interface AppShellProps extends PluginNavProps {
|
|
64
|
+
/** Which icon in the rail is highlighted as the current app — `"settings"` highlights none, and a plugin
|
|
65
|
+
* app page passes its own `appRail` item id. */
|
|
57
66
|
active: AppShellActive;
|
|
58
67
|
/** Populated automatically by the framework from an authenticated request (e.g. a valid `jwt` cookie). */
|
|
59
68
|
userUid?: string;
|
|
@@ -85,15 +94,19 @@ export const APPS: AppDef[] = [
|
|
|
85
94
|
{ id: "tasks", href: "/tasks", label: "Tasks", icon: HiOutlineClipboardDocumentList },
|
|
86
95
|
];
|
|
87
96
|
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
/** Ids plugin `appRail` items can't take besides `APPS`' own - `"settings"` has no rail icon but is still a
|
|
98
|
+
* core `active` value. */
|
|
99
|
+
const RESERVED_APP_IDS = ["settings"];
|
|
100
|
+
|
|
101
|
+
/** `APPS` followed by the plugins' `appRail` items (generic icon), core ids winning - see `mergePluginNavItems`. */
|
|
102
|
+
export function appRailItems(pluginNav?: PluginNav): NavItem[] {
|
|
103
|
+
return mergePluginNavItems<NavItem>(
|
|
104
|
+
APPS,
|
|
105
|
+
pluginNav?.appRail,
|
|
106
|
+
({ id, href, label }) => ({ id, href, label, icon: HiOutlinePuzzlePiece }),
|
|
107
|
+
RESERVED_APP_IDS,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
97
110
|
|
|
98
111
|
/**
|
|
99
112
|
* The persistent chrome shared by every webmail app (Mail, Calendar, Contacts, Tasks): a left icon rail for
|
|
@@ -109,6 +122,7 @@ export default function AppShell({
|
|
|
109
122
|
impersonating,
|
|
110
123
|
impersonationBaseUrl,
|
|
111
124
|
trusted,
|
|
125
|
+
pluginNav,
|
|
112
126
|
children,
|
|
113
127
|
}: PropsWithChildren<AppShellProps>) {
|
|
114
128
|
const [stoppingImpersonation, setStoppingImpersonation] = useState(false);
|
|
@@ -204,6 +218,10 @@ export default function AppShell({
|
|
|
204
218
|
return <div className="min-h-screen" />;
|
|
205
219
|
}
|
|
206
220
|
|
|
221
|
+
const apps = appRailItems(pluginNav);
|
|
222
|
+
// The header title: "settings" has no rail item, everything else is labelled by its own rail item.
|
|
223
|
+
const activeLabel = active === "settings" ? "Settings" : apps.find((app) => app.id === active)?.label;
|
|
224
|
+
|
|
207
225
|
return (
|
|
208
226
|
// Mounted here, not scoped to Mail/Settings, for the same reason as useIdleKeyTimeout() above -
|
|
209
227
|
// ComposeWindow's sign/encrypt toggles and MessageDetailPane's encrypted-message view (both Mail)
|
|
@@ -235,7 +253,7 @@ export default function AppShell({
|
|
|
235
253
|
className="hidden md:flex w-16 shrink-0 bg-surface border-r border-border flex-col items-center py-3 gap-1"
|
|
236
254
|
>
|
|
237
255
|
<img src={iconSrc} width="96" height="96" alt="" className="mb-3" />
|
|
238
|
-
{
|
|
256
|
+
{apps.map(({ id, href, label, icon: Icon }) => (
|
|
239
257
|
<a
|
|
240
258
|
key={id}
|
|
241
259
|
href={href}
|
|
@@ -253,10 +271,10 @@ export default function AppShell({
|
|
|
253
271
|
</a>
|
|
254
272
|
))}
|
|
255
273
|
</nav>
|
|
256
|
-
<BottomTabBar apps={
|
|
274
|
+
<BottomTabBar apps={apps} active={active} />
|
|
257
275
|
<div className="flex-1 flex flex-col min-w-0">
|
|
258
276
|
<header className="h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6">
|
|
259
|
-
<span className="font-display font-bold text-lg uppercase tracking-wide">{
|
|
277
|
+
<span className="font-display font-bold text-lg uppercase tracking-wide">{activeLabel}</span>
|
|
260
278
|
<UserMenu userUid={userUid} authServerUrl={authServerUrl} onSignOut={handleSignOut} showAdminLink={trusted} showSettingsLink />
|
|
261
279
|
</header>
|
|
262
280
|
<div className="flex-1 flex min-h-0 pb-14 md:pb-0">{children}</div>
|