@rapidmx/web-client 0.8.0 → 0.10.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 (247) hide show
  1. package/README.md +192 -88
  2. package/apps/shared/auth/accountUrl.ts +9 -0
  3. package/apps/shared/auth/adminAccess.ts +99 -0
  4. package/apps/shared/components/admin/elevation.ts +61 -0
  5. package/apps/shared/components/admin/layout/AdminShell.tsx +73 -7
  6. package/apps/shared/components/admin/settings/BrandingForm.tsx +2 -1
  7. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +87 -24
  8. package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
  9. package/apps/shared/components/contacts/ContactsToolbar.tsx +125 -115
  10. package/apps/shared/components/contacts/layout/ContactsShell.tsx +15 -3
  11. package/apps/shared/components/layout/AppShell.tsx +411 -288
  12. package/apps/shared/components/layout/BrandingChrome.tsx +3 -2
  13. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
  14. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -126
  15. package/apps/shared/components/layout/UserMenu.tsx +303 -150
  16. package/apps/shared/components/mail/ConversationList.tsx +269 -253
  17. package/apps/shared/components/mail/ConversationThreadPane.tsx +475 -434
  18. package/apps/shared/components/mail/LazyReadingPane.tsx +115 -0
  19. package/apps/shared/components/mail/MailAddress.tsx +88 -0
  20. package/apps/shared/components/mail/MailSelectionBar.tsx +235 -222
  21. package/apps/shared/components/mail/MessageDetailPane.tsx +1458 -1383
  22. package/apps/shared/components/mail/NewMailToasts.tsx +141 -0
  23. package/apps/shared/components/mail/compose/ComposeContext.tsx +279 -160
  24. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
  25. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1842 -1700
  26. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
  27. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
  28. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +48 -0
  29. package/apps/shared/components/mail/compose/composePerf.ts +46 -0
  30. package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
  31. package/apps/shared/components/mail/layout/MailShell.tsx +476 -445
  32. package/apps/shared/components/mail/unreadStyle.tsx +75 -0
  33. package/apps/shared/components/settings/layout/SettingsShell.tsx +252 -240
  34. package/apps/shared/components/tasks/layout/TasksShell.tsx +15 -3
  35. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
  36. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
  37. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
  38. package/apps/shared/keyboard/dispatch.ts +124 -0
  39. package/apps/shared/keyboard/format.ts +89 -0
  40. package/apps/shared/keyboard/keymap.ts +114 -0
  41. package/apps/shared/keyboard/match.ts +44 -0
  42. package/apps/shared/keyboard/parse.ts +136 -0
  43. package/apps/shared/keyboard/platform.ts +34 -0
  44. package/apps/shared/keyboard/registry.ts +65 -0
  45. package/apps/shared/keyboard/targets.ts +79 -0
  46. package/apps/shared/keyboard/useShortcut.ts +50 -0
  47. package/apps/shared/keyboard/useShortcutProps.ts +17 -0
  48. package/apps/shared/mail/folderCounts.ts +302 -0
  49. package/apps/shared/mail/listSnapshots.ts +87 -0
  50. package/apps/shared/mail/mergeFirstPage.ts +47 -0
  51. package/apps/shared/mail/messageReadState.ts +85 -0
  52. package/apps/shared/mail/newMailNotifications.ts +183 -0
  53. package/apps/shared/mail/useMailConnection.ts +150 -0
  54. package/apps/shared/mail/useMailLiveUpdates.ts +232 -0
  55. package/apps/shared/mail/useMarkMessageRead.ts +47 -0
  56. package/apps/shared/mail/useNewMailNotifications.ts +163 -0
  57. package/apps/shared/mail/useUnreadTitle.ts +42 -0
  58. package/apps/shared/navigation/AppRouter.tsx +300 -0
  59. package/apps/shared/navigation/appHrefs.ts +23 -0
  60. package/apps/shared/navigation/frameContext.tsx +35 -0
  61. package/apps/shared/navigation/idle.ts +45 -0
  62. package/apps/shared/navigation/routerContext.tsx +83 -0
  63. package/apps/shared/navigation/routes.ts +72 -0
  64. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
  65. package/apps/shared/styles/app.css +28 -10
  66. package/apps/www/_routedPage.tsx +24 -0
  67. package/apps/www/_routes.ts +35 -0
  68. package/apps/www/calendar/index.tsx +55 -19
  69. package/apps/www/contacts/[uid].tsx +112 -107
  70. package/apps/www/contacts/index.tsx +584 -567
  71. package/apps/www/index.tsx +2280 -1854
  72. package/apps/www/messages/[uid].tsx +106 -101
  73. package/apps/www/settings/auto-reply/index.tsx +4 -1
  74. package/apps/www/settings/encryption/index.tsx +1252 -1249
  75. package/apps/www/settings/filters/[uid].tsx +4 -1
  76. package/apps/www/settings/filters/index.tsx +102 -99
  77. package/apps/www/settings/filters/new/index.tsx +138 -133
  78. package/apps/www/settings/labels/index.tsx +204 -201
  79. package/apps/www/settings/privacy/index.tsx +4 -1
  80. package/apps/www/settings/read-receipts/index.tsx +4 -1
  81. package/apps/www/settings/sharing/index.tsx +277 -274
  82. package/apps/www/settings/signatures/[uid].tsx +170 -167
  83. package/apps/www/settings/signatures/index.tsx +88 -85
  84. package/apps/www/settings/signatures/new/index.tsx +134 -129
  85. package/apps/www/tasks/index.tsx +18 -2
  86. package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
  87. package/dist/apps/shared/auth/accountUrl.js +8 -0
  88. package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
  89. package/dist/apps/shared/auth/adminAccess.js +89 -0
  90. package/dist/apps/shared/components/admin/elevation.d.ts +24 -0
  91. package/dist/apps/shared/components/admin/elevation.js +56 -0
  92. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +12 -4
  93. package/dist/apps/shared/components/admin/layout/AdminShell.js +49 -6
  94. package/dist/apps/shared/components/admin/settings/BrandingForm.js +1 -1
  95. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +31 -13
  96. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  97. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
  98. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  99. package/dist/apps/shared/components/contacts/ContactsToolbar.js +7 -4
  100. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  101. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +15 -5
  102. package/dist/apps/shared/components/layout/AppShell.d.ts +29 -4
  103. package/dist/apps/shared/components/layout/AppShell.js +73 -14
  104. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +3 -2
  105. package/dist/apps/shared/components/layout/BrandingChrome.js +3 -2
  106. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
  107. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +5 -2
  108. package/dist/apps/shared/components/layout/MailboxProvisioning.js +45 -8
  109. package/dist/apps/shared/components/layout/UserMenu.d.ts +28 -9
  110. package/dist/apps/shared/components/layout/UserMenu.js +87 -14
  111. package/dist/apps/shared/components/mail/ConversationList.js +9 -13
  112. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  113. package/dist/apps/shared/components/mail/ConversationThreadPane.js +53 -28
  114. package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
  115. package/dist/apps/shared/components/mail/LazyReadingPane.js +82 -0
  116. package/dist/apps/shared/components/mail/MailAddress.d.ts +27 -0
  117. package/dist/apps/shared/components/mail/MailAddress.js +39 -0
  118. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +4 -1
  119. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -3
  120. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +7 -1
  121. package/dist/apps/shared/components/mail/MessageDetailPane.js +87 -36
  122. package/dist/apps/shared/components/mail/NewMailToasts.d.ts +19 -0
  123. package/dist/apps/shared/components/mail/NewMailToasts.js +55 -0
  124. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +30 -0
  125. package/dist/apps/shared/components/mail/compose/ComposeContext.js +71 -5
  126. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
  127. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +1 -1
  128. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +122 -27
  129. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
  130. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
  131. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
  132. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
  133. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +14 -0
  134. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +11 -0
  135. package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
  136. package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
  137. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
  138. package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
  139. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +19 -4
  140. package/dist/apps/shared/components/mail/layout/MailShell.js +85 -85
  141. package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
  142. package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
  143. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  144. package/dist/apps/shared/components/settings/layout/SettingsShell.js +15 -5
  145. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  146. package/dist/apps/shared/components/tasks/layout/TasksShell.js +15 -5
  147. package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
  148. package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
  149. package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
  150. package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
  151. package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
  152. package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
  153. package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
  154. package/dist/apps/shared/keyboard/dispatch.js +109 -0
  155. package/dist/apps/shared/keyboard/format.d.ts +12 -0
  156. package/dist/apps/shared/keyboard/format.js +74 -0
  157. package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
  158. package/dist/apps/shared/keyboard/keymap.js +84 -0
  159. package/dist/apps/shared/keyboard/match.d.ts +20 -0
  160. package/dist/apps/shared/keyboard/match.js +31 -0
  161. package/dist/apps/shared/keyboard/parse.d.ts +31 -0
  162. package/dist/apps/shared/keyboard/parse.js +107 -0
  163. package/dist/apps/shared/keyboard/platform.d.ts +15 -0
  164. package/dist/apps/shared/keyboard/platform.js +22 -0
  165. package/dist/apps/shared/keyboard/registry.d.ts +39 -0
  166. package/dist/apps/shared/keyboard/registry.js +33 -0
  167. package/dist/apps/shared/keyboard/targets.d.ts +14 -0
  168. package/dist/apps/shared/keyboard/targets.js +67 -0
  169. package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
  170. package/dist/apps/shared/keyboard/useShortcut.js +35 -0
  171. package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
  172. package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
  173. package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
  174. package/dist/apps/shared/mail/folderCounts.js +212 -0
  175. package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
  176. package/dist/apps/shared/mail/listSnapshots.js +43 -0
  177. package/dist/apps/shared/mail/mergeFirstPage.d.ts +23 -0
  178. package/dist/apps/shared/mail/mergeFirstPage.js +30 -0
  179. package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
  180. package/dist/apps/shared/mail/messageReadState.js +63 -0
  181. package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
  182. package/dist/apps/shared/mail/newMailNotifications.js +138 -0
  183. package/dist/apps/shared/mail/useMailConnection.d.ts +55 -0
  184. package/dist/apps/shared/mail/useMailConnection.js +96 -0
  185. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +55 -0
  186. package/dist/apps/shared/mail/useMailLiveUpdates.js +182 -0
  187. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
  188. package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
  189. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +41 -0
  190. package/dist/apps/shared/mail/useNewMailNotifications.js +110 -0
  191. package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
  192. package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
  193. package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
  194. package/dist/apps/shared/navigation/AppRouter.js +242 -0
  195. package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
  196. package/dist/apps/shared/navigation/appHrefs.js +20 -0
  197. package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
  198. package/dist/apps/shared/navigation/frameContext.js +24 -0
  199. package/dist/apps/shared/navigation/idle.d.ts +14 -0
  200. package/dist/apps/shared/navigation/idle.js +43 -0
  201. package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
  202. package/dist/apps/shared/navigation/routerContext.js +56 -0
  203. package/dist/apps/shared/navigation/routes.d.ts +32 -0
  204. package/dist/apps/shared/navigation/routes.js +37 -0
  205. package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
  206. package/dist/apps/shared/styles/app.css +28 -10
  207. package/dist/apps/www/_routedPage.d.ts +12 -0
  208. package/dist/apps/www/_routedPage.js +19 -0
  209. package/dist/apps/www/_routes.d.ts +11 -0
  210. package/dist/apps/www/_routes.js +29 -0
  211. package/dist/apps/www/calendar/index.d.ts +2 -2
  212. package/dist/apps/www/calendar/index.js +39 -8
  213. package/dist/apps/www/contacts/[uid].d.ts +3 -9
  214. package/dist/apps/www/contacts/[uid].js +6 -2
  215. package/dist/apps/www/contacts/index.d.ts +2 -2
  216. package/dist/apps/www/contacts/index.js +17 -4
  217. package/dist/apps/www/index.d.ts +2 -2
  218. package/dist/apps/www/index.js +361 -34
  219. package/dist/apps/www/messages/[uid].d.ts +3 -7
  220. package/dist/apps/www/messages/[uid].js +7 -4
  221. package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
  222. package/dist/apps/www/settings/auto-reply/index.js +3 -1
  223. package/dist/apps/www/settings/encryption/index.d.ts +2 -2
  224. package/dist/apps/www/settings/encryption/index.js +3 -1
  225. package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
  226. package/dist/apps/www/settings/filters/[uid].js +3 -1
  227. package/dist/apps/www/settings/filters/index.d.ts +2 -2
  228. package/dist/apps/www/settings/filters/index.js +3 -1
  229. package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
  230. package/dist/apps/www/settings/filters/new/index.js +6 -2
  231. package/dist/apps/www/settings/labels/index.d.ts +2 -2
  232. package/dist/apps/www/settings/labels/index.js +3 -1
  233. package/dist/apps/www/settings/privacy/index.d.ts +2 -2
  234. package/dist/apps/www/settings/privacy/index.js +3 -1
  235. package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
  236. package/dist/apps/www/settings/read-receipts/index.js +3 -1
  237. package/dist/apps/www/settings/sharing/index.d.ts +2 -2
  238. package/dist/apps/www/settings/sharing/index.js +3 -1
  239. package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
  240. package/dist/apps/www/settings/signatures/[uid].js +3 -1
  241. package/dist/apps/www/settings/signatures/index.d.ts +2 -2
  242. package/dist/apps/www/settings/signatures/index.js +3 -1
  243. package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
  244. package/dist/apps/www/settings/signatures/new/index.js +6 -2
  245. package/dist/apps/www/tasks/index.d.ts +2 -2
  246. package/dist/apps/www/tasks/index.js +15 -3
  247. package/package.json +2 -2
@@ -1,107 +1,112 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { useEffect, useState } from "react";
6
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
7
- import { Contact, deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
8
- import ContactsShell, { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
9
- import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
10
- import ContactForm from "../../shared/components/contacts/ContactForm.js";
11
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
- import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
13
-
14
- /**
15
- * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
16
- * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
17
- * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
18
- * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
19
- */
20
- export default function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
21
- return (
22
- <ContactsShell {...props}>
23
- <ContactDetailContent uid={props.params.uid} />
24
- </ContactsShell>
25
- );
26
- }
27
-
28
- type Mode = "view" | "edit";
29
-
30
- function ContactDetailContent({ uid }: { uid: string }) {
31
- const [contact, setContact] = useState<Contact | null>(null);
32
- const [loading, setLoading] = useState(true);
33
- const [error, setError] = useState<string | null>(null);
34
- // Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
35
- // reason) rather than replacing the whole page with an error as if it had never loaded.
36
- const [deleteError, setDeleteError] = useState<string | null>(null);
37
- const [mode, setMode] = useState<Mode>("view");
38
-
39
- useEffect(() => {
40
- setLoading(true);
41
- setError(null);
42
- getContact(uid)
43
- .then(setContact)
44
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
45
- .finally(() => setLoading(false));
46
- }, [uid]);
47
-
48
- // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
49
- // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
50
- // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
51
- // never runs with a stale/absent contact — no redundant null check needed here.
52
- async function handleDelete(contact: Contact) {
53
- setDeleteError(null);
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
- window.location.href = "/contacts";
59
- } catch (err) {
60
- setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this contact.");
61
- }
62
- }
63
-
64
- // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
65
- function handleKeysChanged() {
66
- getContact(uid).then(setContact, () => undefined);
67
- }
68
-
69
- if (loading) {
70
- return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
71
- }
72
- if (error || !contact) {
73
- return <Alert>{error ?? "Contact not found."}</Alert>;
74
- }
75
-
76
- const backHref = "/contacts";
77
- if (mode === "edit") {
78
- return (
79
- <div className="p-6">
80
- <a href={backHref} className="text-sm text-primary-dark hover:underline block mb-4">
81
- &larr; Back to contacts
82
- </a>
83
- <ContactForm
84
- contact={contact}
85
- onSaved={(saved) => {
86
- setContact(saved);
87
- setMode("view");
88
- }}
89
- onCancel={() => setMode("view")}
90
- />
91
- </div>
92
- );
93
- }
94
-
95
- return (
96
- <div className="p-6">
97
- {deleteError && <Alert>{deleteError}</Alert>}
98
- <ContactDetailPane
99
- contact={contact}
100
- onEdit={() => setMode("edit")}
101
- onDelete={() => handleDelete(contact)}
102
- backHref={backHref}
103
- onKeysChanged={handleKeysChanged}
104
- />
105
- </div>
106
- );
107
- }
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
+
16
+ /**
17
+ * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
18
+ * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
19
+ * equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
20
+ * in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
21
+ */
22
+ function ContactDetailPage(props: ContactsShellProps & { params: { uid: string } }) {
23
+ return (
24
+ <ContactsShell {...props}>
25
+ <ContactDetailContent uid={props.params.uid} />
26
+ </ContactsShell>
27
+ );
28
+ }
29
+
30
+ type Mode = "view" | "edit";
31
+
32
+ function ContactDetailContent({ uid }: { uid: string }) {
33
+ const navigate = useNavigate();
34
+ const [contact, setContact] = useState<Contact | null>(null);
35
+ const [loading, setLoading] = useState(true);
36
+ const [error, setError] = useState<string | null>(null);
37
+ // Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
38
+ // reason) rather than replacing the whole page with an error as if it had never loaded.
39
+ const [deleteError, setDeleteError] = useState<string | null>(null);
40
+ const [mode, setMode] = useState<Mode>("view");
41
+
42
+ useEffect(() => {
43
+ setLoading(true);
44
+ setError(null);
45
+ getContact(uid)
46
+ .then(setContact)
47
+ .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this contact."))
48
+ .finally(() => setLoading(false));
49
+ }, [uid]);
50
+
51
+ // Takes `contact` as a parameter rather than reading it from the closure/state — mirrors the desktop
52
+ // pane's own `handleDelete` in `apps/www/contacts/index.tsx`. The only caller (below) is only ever
53
+ // rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
54
+ // never runs with a stale/absent contact — no redundant null check needed here.
55
+ async function handleDelete(contact: Contact) {
56
+ setDeleteError(null);
57
+ try {
58
+ await deleteContact(contact.uid, contact.version);
59
+ // A deleted contact's pinned signing keys must stop vouching for signatures.
60
+ clearPinnedSignerCache();
61
+ navigate("/contacts");
62
+ } catch (err) {
63
+ setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this contact.");
64
+ }
65
+ }
66
+
67
+ // After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
68
+ function handleKeysChanged() {
69
+ getContact(uid).then(setContact, () => undefined);
70
+ }
71
+
72
+ if (loading) {
73
+ return <p className="p-8 text-sm text-text-muted">Loading&hellip;</p>;
74
+ }
75
+ if (error || !contact) {
76
+ return <Alert>{error ?? "Contact not found."}</Alert>;
77
+ }
78
+
79
+ const backHref = "/contacts";
80
+ if (mode === "edit") {
81
+ return (
82
+ <div className="p-6">
83
+ <a href={backHref} className="text-sm text-primary-dark hover:underline block mb-4">
84
+ &larr; Back to contacts
85
+ </a>
86
+ <ContactForm
87
+ contact={contact}
88
+ onSaved={(saved) => {
89
+ setContact(saved);
90
+ setMode("view");
91
+ }}
92
+ onCancel={() => setMode("view")}
93
+ />
94
+ </div>
95
+ );
96
+ }
97
+
98
+ return (
99
+ <div className="p-6">
100
+ {deleteError && <Alert>{deleteError}</Alert>}
101
+ <ContactDetailPane
102
+ contact={contact}
103
+ onEdit={() => setMode("edit")}
104
+ onDelete={() => handleDelete(contact)}
105
+ backHref={backHref}
106
+ onKeysChanged={handleKeysChanged}
107
+ />
108
+ </div>
109
+ );
110
+ }
111
+
112
+ export default routedPage("/contacts/:uid", ContactDetailPage);