@rapidmx/web-client 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +373 -373
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -480
  4. package/apps/admin/domains/[uid].tsx +164 -164
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -348
  6. package/apps/admin/index.tsx +127 -124
  7. package/apps/admin/mailboxes/[uid].tsx +269 -224
  8. package/apps/admin/mailboxes/new/index.tsx +28 -28
  9. package/apps/admin/plugins/index.tsx +15 -15
  10. package/apps/admin/retention-policy/index.tsx +39 -39
  11. package/apps/admin/signing-certificates/index.tsx +343 -343
  12. package/apps/escrow/audit-log/index.tsx +196 -196
  13. package/apps/escrow/matters/[uid].tsx +621 -621
  14. package/apps/shared/auth/adminAccess.ts +99 -99
  15. package/apps/shared/components/admin/layout/AdminShell.tsx +378 -378
  16. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
  17. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  18. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
  19. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  20. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  21. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +50 -3
  22. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  23. package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1545
  24. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  25. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  26. package/apps/shared/components/admin/setup/SetupWizard.tsx +444 -444
  27. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  28. package/apps/shared/components/calendar/EventEditor.tsx +5 -10
  29. package/apps/shared/components/calendar/EventFormParts.tsx +16 -47
  30. package/apps/shared/components/calendar/EventModal.tsx +206 -206
  31. package/apps/shared/components/calendar/GuestInput.tsx +100 -0
  32. package/apps/shared/components/calendar/MonthView.tsx +185 -185
  33. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
  34. package/apps/shared/components/calendar/RequestChangeForm.tsx +23 -54
  35. package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
  36. package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
  37. package/apps/shared/components/calendar/allDay.ts +124 -124
  38. package/apps/shared/components/calendar/eventForm.ts +2 -2
  39. package/apps/shared/components/calendar/eventFormat.ts +49 -22
  40. package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
  41. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  42. package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
  43. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
  44. package/apps/shared/components/contacts/layout/ContactsShell.tsx +217 -216
  45. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  46. package/apps/shared/components/layout/AppShell.tsx +480 -480
  47. package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
  48. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  49. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  50. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  51. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  52. package/apps/shared/components/layout/UserMenu.tsx +439 -424
  53. package/apps/shared/components/mail/ConversationList.tsx +323 -323
  54. package/apps/shared/components/mail/ConversationThreadPane.tsx +612 -491
  55. package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
  56. package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1559
  57. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  58. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  59. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1677
  60. package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
  61. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  62. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  63. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  64. package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
  65. package/apps/shared/components/mail/layout/MailShell.tsx +137 -113
  66. package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
  67. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
  68. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  69. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  70. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  71. package/apps/shared/components/settings/layout/SettingsShell.tsx +255 -254
  72. package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
  73. package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
  74. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  75. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  76. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  77. package/apps/shared/keyboard/dispatch.ts +124 -124
  78. package/apps/shared/keyboard/format.ts +89 -89
  79. package/apps/shared/keyboard/keymap.ts +114 -114
  80. package/apps/shared/keyboard/registry.ts +65 -65
  81. package/apps/shared/keyboard/targets.ts +79 -79
  82. package/apps/shared/mail/folderCounts.ts +17 -0
  83. package/apps/shared/mail/folderTree.ts +143 -143
  84. package/apps/shared/mail/listAllPages.ts +39 -39
  85. package/apps/shared/mail/newMailNotifications.ts +171 -171
  86. package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
  87. package/apps/shared/mail/outbox/sendJob.ts +19 -1
  88. package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
  89. package/apps/shared/mail/primaryMailbox.ts +71 -0
  90. package/apps/shared/mail/useCollapsedSections.ts +146 -0
  91. package/apps/shared/mail/useMailConnection.ts +205 -203
  92. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  93. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  94. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  95. package/apps/shared/navigation/AppRouter.tsx +300 -300
  96. package/apps/shared/navigation/routerContext.tsx +83 -83
  97. package/apps/shared/notifications/store.ts +560 -560
  98. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  99. package/apps/shared/search/crossMailboxSearch.ts +169 -0
  100. package/apps/shared/search/localIndexBuilder.ts +481 -481
  101. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  102. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  103. package/apps/shared/signing/enrollmentView.ts +251 -251
  104. package/apps/shared/signing/useNow.ts +19 -19
  105. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  106. package/apps/shared/styles/app.css +396 -386
  107. package/apps/www/calendar/index.tsx +578 -571
  108. package/apps/www/contacts/[uid].tsx +109 -109
  109. package/apps/www/contacts/index.tsx +6 -0
  110. package/apps/www/index.tsx +2847 -2479
  111. package/apps/www/messages/[uid].tsx +135 -135
  112. package/apps/www/settings/auto-reply/index.tsx +133 -133
  113. package/apps/www/settings/encryption/index.tsx +1287 -1287
  114. package/apps/www/settings/filters/[uid].tsx +176 -176
  115. package/apps/www/settings/filters/index.tsx +102 -102
  116. package/apps/www/settings/filters/new/index.tsx +140 -140
  117. package/apps/www/settings/labels/index.tsx +204 -204
  118. package/apps/www/settings/privacy/index.tsx +492 -492
  119. package/apps/www/settings/profile/index.tsx +248 -248
  120. package/apps/www/settings/read-receipts/index.tsx +147 -147
  121. package/apps/www/settings/sharing/index.tsx +256 -256
  122. package/apps/www/settings/signatures/[uid].tsx +172 -172
  123. package/apps/www/settings/signatures/index.tsx +88 -88
  124. package/apps/www/settings/signatures/new/index.tsx +135 -135
  125. package/apps/www/tasks/index.tsx +649 -649
  126. package/dist/apps/admin/data-requests/index.js +1 -1
  127. package/dist/apps/admin/index.js +2 -1
  128. package/dist/apps/admin/mailboxes/[uid].js +12 -2
  129. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -2
  130. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
  131. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
  132. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
  133. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
  134. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  135. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +25 -3
  136. package/dist/apps/shared/components/calendar/EventEditor.js +5 -10
  137. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -2
  138. package/dist/apps/shared/components/calendar/EventFormParts.js +5 -16
  139. package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
  140. package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
  141. package/dist/apps/shared/components/calendar/RequestChangeForm.js +11 -22
  142. package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -2
  143. package/dist/apps/shared/components/calendar/eventFormat.d.ts +21 -8
  144. package/dist/apps/shared/components/calendar/eventFormat.js +50 -23
  145. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
  146. package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
  147. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  148. package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
  149. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -3
  150. package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
  151. package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
  152. package/dist/apps/shared/components/layout/UserMenu.d.ts +4 -1
  153. package/dist/apps/shared/components/layout/UserMenu.js +6 -5
  154. package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
  155. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
  156. package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
  157. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -4
  158. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
  159. package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
  160. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
  161. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
  162. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +5 -0
  163. package/dist/apps/shared/components/mail/layout/MailShell.js +64 -45
  164. package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
  165. package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
  166. package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
  167. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
  168. package/dist/apps/shared/components/settings/layout/SettingsShell.js +6 -5
  169. package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
  170. package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
  171. package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
  172. package/dist/apps/shared/mail/folderCounts.js +12 -0
  173. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
  174. package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
  175. package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
  176. package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
  177. package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
  178. package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
  179. package/dist/apps/shared/mail/primaryMailbox.js +61 -0
  180. package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
  181. package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
  182. package/dist/apps/shared/mail/useMailConnection.js +3 -1
  183. package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
  184. package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
  185. package/dist/apps/shared/styles/app.css +396 -386
  186. package/dist/apps/www/calendar/index.js +3 -2
  187. package/dist/apps/www/contacts/index.js +3 -1
  188. package/dist/apps/www/index.js +426 -189
  189. package/package.json +2 -2
@@ -1,109 +1,109 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import { routedPage } from "../_routedPage.js";
6
- import { useNavigate } from "../../shared/navigation/AppRouter.js";
7
- import React, { useEffect, useState } from "react";
8
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
9
- import { Contact, deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
10
- import ContactsShell, { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
11
- import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
12
- import ContactForm from "../../shared/components/contacts/ContactForm.js";
13
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
- import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
15
- import { notifyApiError } from "../../shared/notifications/apiErrors.js";
16
-
17
- /**
18
- * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
19
- * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
20
- * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
21
- * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
22
- */
23
- function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
24
- return (
25
- <ContactsShell {...props}>
26
- <ContactDetailContent uid={props.params.uid} />
27
- </ContactsShell>
28
- );
29
- }
30
-
31
- type Mode = "view" | "edit";
32
-
33
- function ContactDetailContent({ uid }: { uid: string }) {
34
- const navigate = useNavigate();
35
- const [contact, setContact] = useState<Contact | null>(null);
36
- const [loading, setLoading] = useState(true);
37
- const [error, setError] = useState<string | null>(null);
38
- const [mode, setMode] = useState<Mode>("view");
39
-
40
- useEffect(() => {
41
- setLoading(true);
42
- setError(null);
43
- getContact(uid)
44
- .then(setContact)
45
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
46
- .finally(() => setLoading(false));
47
- }, [uid]);
48
-
49
- // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
50
- // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
51
- // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
52
- // never runs with a stale/absent contact — no redundant null check needed here.
53
- async function handleDelete(contact: Contact) {
54
- try {
55
- await deleteContact(contact.uid, contact.version);
56
- // A deleted contact's pinned signing keys must stop vouching for signatures.
57
- clearPinnedSignerCache();
58
- navigate("/contacts");
59
- } catch (err) {
60
- // A pop-up: a failed delete leaves the contact on screen rather than replacing the page with an error.
61
- notifyApiError(err, "Couldn't delete the contact");
62
- }
63
- }
64
-
65
- // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
66
- function handleKeysChanged() {
67
- getContact(uid).then(setContact, () => undefined);
68
- }
69
-
70
- if (loading) {
71
- return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
72
- }
73
- if (error || !contact) {
74
- return <Alert>{error ?? "Contact not found."}</Alert>;
75
- }
76
-
77
- const backHref = "/contacts";
78
- if (mode === "edit") {
79
- return (
80
- <div>
81
- <a href={backHref} className="text-sm text-primary-dark hover:underline block px-6 pt-6">
82
- &larr; Back to contacts
83
- </a>
84
- <ContactForm
85
- contact={contact}
86
- onSaved={(saved) => {
87
- setContact(saved);
88
- setMode("view");
89
- }}
90
- onCancel={() => setMode("view")}
91
- />
92
- </div>
93
- );
94
- }
95
-
96
- return (
97
- <div className="p-6">
98
- <ContactDetailPane
99
- contact={contact}
100
- onEdit={() => setMode("edit")}
101
- onDelete={() => handleDelete(contact)}
102
- backHref={backHref}
103
- onKeysChanged={handleKeysChanged}
104
- />
105
- </div>
106
- );
107
- }
108
-
109
- export default routedPage("/contacts/:uid", ContactDetailPage);
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { routedPage } from "../_routedPage.js";
6
+ import { useNavigate } from "../../shared/navigation/AppRouter.js";
7
+ import React, { useEffect, useState } from "react";
8
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
9
+ import { Contact, deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
10
+ import ContactsShell, { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
11
+ import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
12
+ import ContactForm from "../../shared/components/contacts/ContactForm.js";
13
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
+ import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
15
+ import { notifyApiError } from "../../shared/notifications/apiErrors.js";
16
+
17
+ /**
18
+ * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
19
+ * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
20
+ * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
21
+ * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
22
+ */
23
+ function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
24
+ return (
25
+ <ContactsShell {...props}>
26
+ <ContactDetailContent uid={props.params.uid} />
27
+ </ContactsShell>
28
+ );
29
+ }
30
+
31
+ type Mode = "view" | "edit";
32
+
33
+ function ContactDetailContent({ uid }: { uid: string }) {
34
+ const navigate = useNavigate();
35
+ const [contact, setContact] = useState<Contact | null>(null);
36
+ const [loading, setLoading] = useState(true);
37
+ const [error, setError] = useState<string | null>(null);
38
+ const [mode, setMode] = useState<Mode>("view");
39
+
40
+ useEffect(() => {
41
+ setLoading(true);
42
+ setError(null);
43
+ getContact(uid)
44
+ .then(setContact)
45
+ .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
46
+ .finally(() => setLoading(false));
47
+ }, [uid]);
48
+
49
+ // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
50
+ // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
51
+ // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
52
+ // never runs with a stale/absent contact — no redundant null check needed here.
53
+ async function handleDelete(contact: Contact) {
54
+ try {
55
+ await deleteContact(contact.uid, contact.version);
56
+ // A deleted contact's pinned signing keys must stop vouching for signatures.
57
+ clearPinnedSignerCache();
58
+ navigate("/contacts");
59
+ } catch (err) {
60
+ // A pop-up: a failed delete leaves the contact on screen rather than replacing the page with an error.
61
+ notifyApiError(err, "Couldn't delete the contact");
62
+ }
63
+ }
64
+
65
+ // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
66
+ function handleKeysChanged() {
67
+ getContact(uid).then(setContact, () => undefined);
68
+ }
69
+
70
+ if (loading) {
71
+ return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
72
+ }
73
+ if (error || !contact) {
74
+ return <Alert>{error ?? "Contact not found."}</Alert>;
75
+ }
76
+
77
+ const backHref = "/contacts";
78
+ if (mode === "edit") {
79
+ return (
80
+ <div>
81
+ <a href={backHref} className="text-sm text-primary-dark hover:underline block px-6 pt-6">
82
+ &larr; Back to contacts
83
+ </a>
84
+ <ContactForm
85
+ contact={contact}
86
+ onSaved={(saved) => {
87
+ setContact(saved);
88
+ setMode("view");
89
+ }}
90
+ onCancel={() => setMode("view")}
91
+ />
92
+ </div>
93
+ );
94
+ }
95
+
96
+ return (
97
+ <div className="p-6">
98
+ <ContactDetailPane
99
+ contact={contact}
100
+ onEdit={() => setMode("edit")}
101
+ onDelete={() => handleDelete(contact)}
102
+ backHref={backHref}
103
+ onKeysChanged={handleKeysChanged}
104
+ />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ export default routedPage("/contacts/:uid", ContactDetailPage);
@@ -21,6 +21,8 @@ import { useCompose } from "../../shared/components/mail/compose/ComposeContext.
21
21
  import ContactsShell, { ContactsShellProps, useContactsShell } from "../../shared/components/contacts/layout/ContactsShell.js";
22
22
  import ContactsSidebar, { ContactsView } from "../../shared/components/contacts/ContactsSidebar.js";
23
23
  import ContactsToolbar from "../../shared/components/contacts/ContactsToolbar.js";
24
+ import FloatingActionButton from "../../shared/components/layout/FloatingActionButton.js";
25
+ import { HiOutlineUserPlus } from "react-icons/hi2";
24
26
  import ContactAvatar from "@rapidmx/react-shared/components/avatar/ContactAvatar.js";
25
27
  import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
26
28
  import ContactForm from "../../shared/components/contacts/ContactForm.js";
@@ -419,6 +421,7 @@ function ContactsContent({ userUid }: { userUid?: string }) {
419
421
  onExportVCard={handleExportVCard}
420
422
  onImportFile={handleImportFile}
421
423
  shortcuts
424
+ hideNew={isMobile}
422
425
  />
423
426
  <div className="p-3 border-b border-border">
424
427
  <input
@@ -580,6 +583,9 @@ function ContactsContent({ userUid }: { userUid?: string }) {
580
583
  </Button>
581
584
  </div>
582
585
  </Modal>
586
+ {/* The phone layout's New contact, in place of the toolbar's button: the same action, and not while the new-contact form (which takes the
587
+ list's place there) is open. */}
588
+ {isMobile && mode !== "new" && <FloatingActionButton label="New contact" icon={HiOutlineUserPlus} onClick={handleNew} />}
583
589
  </div>
584
590
  );
585
591
  }