@rapidmx/web-client 0.10.0 → 0.11.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 (310) hide show
  1. package/README.md +183 -2
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  21. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  22. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  23. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  24. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  25. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  26. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  27. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  28. package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
  29. package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
  30. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  31. package/apps/shared/components/layout/AppShell.tsx +98 -32
  32. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  33. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  34. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  35. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  36. package/apps/shared/components/layout/UserMenu.tsx +75 -6
  37. package/apps/shared/components/mail/ConversationList.tsx +4 -2
  38. package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
  39. package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
  40. package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
  41. package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
  42. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  43. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  44. package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
  45. package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
  46. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  47. package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
  48. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  49. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  50. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  51. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  52. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  53. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  54. package/apps/shared/components/mail/reading/color.ts +245 -0
  55. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  56. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  57. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  58. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  59. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  60. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  61. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  62. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  63. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  64. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  65. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
  66. package/apps/shared/mail/folderCounts.ts +9 -3
  67. package/apps/shared/mail/folderTree.ts +143 -0
  68. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  69. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  70. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  71. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  72. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  73. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  74. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  75. package/apps/shared/mail/outbox/sendState.ts +35 -0
  76. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  77. package/apps/shared/mail/useMailConnection.ts +81 -28
  78. package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
  79. package/apps/shared/mail/useNewMailNotifications.ts +47 -32
  80. package/apps/shared/navigation/AppRouter.tsx +2 -2
  81. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  82. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  83. package/apps/shared/notifications/apiErrors.ts +98 -0
  84. package/apps/shared/notifications/headerOffset.ts +35 -0
  85. package/apps/shared/notifications/pushStatus.ts +56 -0
  86. package/apps/shared/notifications/store.ts +549 -0
  87. package/apps/shared/notifications/systemErrors.ts +63 -0
  88. package/apps/shared/notifications/useNotifications.ts +41 -0
  89. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  90. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  91. package/apps/shared/signing/enrollmentView.ts +251 -0
  92. package/apps/shared/signing/signingInfo.ts +58 -0
  93. package/apps/shared/signing/useNow.ts +19 -0
  94. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  95. package/apps/shared/styles/app.css +80 -0
  96. package/apps/www/_layout.tsx +8 -2
  97. package/apps/www/_routes.ts +1 -0
  98. package/apps/www/calendar/index.tsx +9 -14
  99. package/apps/www/contacts/[uid].tsx +5 -8
  100. package/apps/www/contacts/index.tsx +587 -584
  101. package/apps/www/index.tsx +38 -27
  102. package/apps/www/messages/[uid].tsx +3 -1
  103. package/apps/www/settings/appearance/index.tsx +21 -0
  104. package/apps/www/settings/auto-reply/index.tsx +3 -7
  105. package/apps/www/settings/encryption/index.tsx +151 -116
  106. package/apps/www/settings/filters/[uid].tsx +3 -1
  107. package/apps/www/settings/filters/new/index.tsx +3 -1
  108. package/apps/www/settings/privacy/index.tsx +3 -8
  109. package/apps/www/settings/read-receipts/index.tsx +3 -7
  110. package/apps/www/settings/sharing/index.tsx +44 -65
  111. package/apps/www/settings/signatures/[uid].tsx +3 -1
  112. package/apps/www/settings/signatures/new/index.tsx +3 -2
  113. package/apps/www/tasks/index.tsx +11 -16
  114. package/dist/apps/admin/_layout.d.ts +5 -1
  115. package/dist/apps/admin/_layout.js +3 -2
  116. package/dist/apps/admin/index.js +3 -2
  117. package/dist/apps/admin/ingest-queue/index.js +2 -1
  118. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  119. package/dist/apps/admin/quarantine/index.js +2 -1
  120. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  121. package/dist/apps/admin/signing-certificates/index.js +161 -0
  122. package/dist/apps/escrow/_layout.d.ts +5 -1
  123. package/dist/apps/escrow/_layout.js +3 -2
  124. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  125. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  126. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  127. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  128. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  129. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  130. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  131. package/dist/apps/shared/appearance/ColorField.js +40 -0
  132. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  133. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  134. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  135. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  136. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  137. package/dist/apps/shared/appearance/bootScript.js +33 -0
  138. package/dist/apps/shared/appearance/color.d.ts +65 -0
  139. package/dist/apps/shared/appearance/color.js +132 -0
  140. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  141. package/dist/apps/shared/appearance/photo.js +79 -0
  142. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  143. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  144. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  145. package/dist/apps/shared/appearance/theme.js +251 -0
  146. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  147. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  148. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  149. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  150. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  151. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  152. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  153. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  154. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  155. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  156. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  157. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  158. package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
  159. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
  160. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  161. package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
  162. package/dist/apps/shared/components/layout/AppShell.js +59 -13
  163. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  164. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  165. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  166. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  167. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  168. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  169. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  170. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  171. package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
  172. package/dist/apps/shared/components/layout/UserMenu.js +41 -7
  173. package/dist/apps/shared/components/mail/ConversationList.js +3 -2
  174. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
  175. package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
  176. package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
  177. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
  178. package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
  179. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
  180. package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
  181. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  182. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  183. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  184. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  185. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
  186. package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
  187. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
  188. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
  189. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  190. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  191. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
  192. package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
  193. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  194. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  195. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  196. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  197. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  198. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  199. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  200. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  201. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  202. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  203. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  204. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  205. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  206. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  207. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  208. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  209. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  210. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  211. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  212. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  213. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  214. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  215. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  216. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  217. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  218. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  219. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  220. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  221. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  222. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  223. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  224. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  225. package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
  226. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  227. package/dist/apps/shared/mail/folderCounts.js +9 -3
  228. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  229. package/dist/apps/shared/mail/folderTree.js +116 -0
  230. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  231. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  232. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  233. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  234. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  235. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  236. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  237. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  238. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  239. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  240. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  241. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  242. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  243. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  244. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  245. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  246. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  247. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  248. package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
  249. package/dist/apps/shared/mail/useMailConnection.js +64 -22
  250. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
  251. package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
  252. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
  253. package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
  254. package/dist/apps/shared/navigation/AppRouter.js +2 -2
  255. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  256. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  257. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  258. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  259. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  260. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  261. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  262. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  263. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  264. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  265. package/dist/apps/shared/notifications/store.d.ts +136 -0
  266. package/dist/apps/shared/notifications/store.js +416 -0
  267. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  268. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  269. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  270. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  271. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  272. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  273. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  274. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  275. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  276. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  277. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  278. package/dist/apps/shared/signing/signingInfo.js +48 -0
  279. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  280. package/dist/apps/shared/signing/useNow.js +18 -0
  281. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  282. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  283. package/dist/apps/shared/styles/app.css +80 -0
  284. package/dist/apps/www/_layout.d.ts +5 -1
  285. package/dist/apps/www/_layout.js +3 -2
  286. package/dist/apps/www/_routes.js +1 -0
  287. package/dist/apps/www/calendar/index.js +3 -4
  288. package/dist/apps/www/contacts/[uid].js +5 -7
  289. package/dist/apps/www/contacts/index.js +14 -21
  290. package/dist/apps/www/index.js +37 -19
  291. package/dist/apps/www/messages/[uid].js +3 -1
  292. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  293. package/dist/apps/www/settings/appearance/index.js +13 -0
  294. package/dist/apps/www/settings/auto-reply/index.js +3 -6
  295. package/dist/apps/www/settings/encryption/index.js +103 -95
  296. package/dist/apps/www/settings/filters/[uid].js +3 -1
  297. package/dist/apps/www/settings/filters/new/index.js +3 -1
  298. package/dist/apps/www/settings/privacy/index.js +5 -8
  299. package/dist/apps/www/settings/read-receipts/index.js +3 -6
  300. package/dist/apps/www/settings/sharing/index.js +19 -33
  301. package/dist/apps/www/settings/signatures/[uid].js +3 -1
  302. package/dist/apps/www/settings/signatures/new/index.js +3 -2
  303. package/dist/apps/www/tasks/index.js +8 -14
  304. package/package.json +2 -2
  305. package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
  306. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  307. package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
  308. package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
  309. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  310. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -0,0 +1,215 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { useEffect } from "react";
6
+ import type { IconType } from "react-icons";
7
+ import {
8
+ HiOutlineCheckCircle,
9
+ HiOutlineEnvelope,
10
+ HiOutlineExclamationCircle,
11
+ HiOutlineExclamationTriangle,
12
+ HiOutlineInformationCircle,
13
+ HiOutlineXMark,
14
+ } from "react-icons/hi2";
15
+ import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
16
+ import { NotificationAction, NotificationKind, NotificationView, dismiss, setAllPaused, setPaused } from "./store.js";
17
+ import { useNotifications } from "./useNotifications.js";
18
+ import { useSystemErrorNotifications } from "./systemErrors.js";
19
+
20
+ const KIND_STYLE: Record<NotificationKind, { border: string; icon: string; Icon: IconType; label: string }> = {
21
+ mail: { border: "border-l-primary", icon: "text-primary-dark", Icon: HiOutlineEnvelope, label: "New message" },
22
+ info: { border: "border-l-primary", icon: "text-primary-dark", Icon: HiOutlineInformationCircle, label: "Information" },
23
+ success: { border: "border-l-success", icon: "text-success", Icon: HiOutlineCheckCircle, label: "Success" },
24
+ warning: { border: "border-l-warning", icon: "text-warning", Icon: HiOutlineExclamationTriangle, label: "Warning" },
25
+ error: { border: "border-l-danger", icon: "text-danger", Icon: HiOutlineExclamationCircle, label: "Error" },
26
+ };
27
+
28
+ const ACTION_CLASS =
29
+ "rounded-sm px-2.5 py-1 text-xs font-semibold text-primary-dark hover:bg-surface-alt focus-visible:outline-2 focus-visible:outline-primary";
30
+
31
+ /** Stops every clock while the tab is out of view: nobody could have read what is on screen, and it is still there when they come back. */
32
+ function usePauseWhileHidden(): void {
33
+ useEffect(() => {
34
+ const update = () => setAllPaused(document.visibilityState === "hidden");
35
+ update();
36
+ document.addEventListener("visibilitychange", update);
37
+ return () => {
38
+ document.removeEventListener("visibilitychange", update);
39
+ setAllPaused(false);
40
+ };
41
+ }, []);
42
+ }
43
+
44
+ function ActionButton({ action, id }: { action: NotificationAction; id: string }) {
45
+ function run() {
46
+ action.onClick?.();
47
+ if (!action.keepOpen) {
48
+ dismiss(id);
49
+ }
50
+ }
51
+ return action.href ? (
52
+ <a href={action.href} onClick={run} className={ACTION_CLASS}>
53
+ {action.label}
54
+ </a>
55
+ ) : (
56
+ <button type="button" onClick={run} className={ACTION_CLASS}>
57
+ {action.label}
58
+ </button>
59
+ );
60
+ }
61
+
62
+ /** The expandable, copyable technical lines behind a notification. */
63
+ function Details({ lines }: { lines: string[] }) {
64
+ return (
65
+ <details className="mt-1.5">
66
+ <summary className="cursor-pointer text-xs font-semibold text-text-muted">Technical details</summary>
67
+ <ul
68
+ aria-label="Technical details"
69
+ className="mt-1.5 max-h-40 overflow-auto rounded-sm border border-border bg-surface-alt p-2 font-mono text-xs text-text select-text"
70
+ >
71
+ {lines.map((line, index) => (
72
+ <li key={index} className="whitespace-pre-wrap break-words">
73
+ {line}
74
+ </li>
75
+ ))}
76
+ </ul>
77
+ <div className="mt-1.5">
78
+ <CopyButton value={lines.join("\n")} label="Copy technical details" />
79
+ </div>
80
+ </details>
81
+ );
82
+ }
83
+
84
+ /** One pop-up. Its clock (kept by the store) stops while it is hovered or holds the keyboard focus; Escape dismisses it. */
85
+ function NotificationItem({ notification }: { notification: NotificationView }) {
86
+ const { id, kind, title, subtitle, message, preview, hint, details, actions, href, count } = notification;
87
+ const style = KIND_STYLE[kind];
88
+ const body = (
89
+ <>
90
+ {kind === "mail" && <span className="block text-xs font-semibold uppercase tracking-wide text-primary-dark">{style.label}</span>}
91
+ <span className="flex min-w-0 items-baseline gap-1.5 text-sm">
92
+ <span className={["truncate font-bold", kind === "error" ? "text-danger" : ""].join(" ")}>{title}</span>
93
+ {count > 1 && (
94
+ <span className="shrink-0 rounded-pill bg-surface-alt px-1.5 text-xs font-semibold text-text-muted">
95
+ <span aria-hidden="true">&times;{count}</span>
96
+ <span className="sr-only"> ({count} times)</span>
97
+ </span>
98
+ )}
99
+ {subtitle && <span className="truncate text-xs text-text-muted">{subtitle}</span>}
100
+ </span>
101
+ {message && <span className={["mt-0.5 block text-sm [overflow-wrap:anywhere]", kind === "mail" ? "truncate font-semibold" : ""].join(" ")}>{message}</span>}
102
+ {preview && <span className="mt-0.5 block text-xs text-text-muted [overflow-wrap:anywhere]">{preview}</span>}
103
+ </>
104
+ );
105
+ return (
106
+ <div
107
+ data-testid="notification"
108
+ role={kind === "error" ? "alert" : "status"}
109
+ data-kind={kind}
110
+ data-notification-id={id}
111
+ onMouseEnter={() => setPaused(id, true)}
112
+ onMouseLeave={() => setPaused(id, false)}
113
+ onFocus={() => setPaused(id, true)}
114
+ onBlur={(event) => {
115
+ // Focus moving between this pop-up's own controls is not leaving it.
116
+ if (!event.currentTarget.contains(event.relatedTarget)) {
117
+ setPaused(id, false);
118
+ }
119
+ }}
120
+ onKeyDown={(event) => {
121
+ if (event.key === "Escape") {
122
+ event.preventDefault();
123
+ dismiss(id);
124
+ }
125
+ }}
126
+ className={`rr-toast-in pointer-events-auto relative rounded-md border border-border border-l-4 ${style.border} bg-surface text-text shadow-lg`}
127
+ >
128
+ <div className="flex items-start gap-2 p-3 pr-10">
129
+ {kind !== "mail" && <style.Icon size={18} aria-hidden="true" className={`mt-0.5 shrink-0 ${style.icon}`} />}
130
+ <div className="min-w-0 flex-1">
131
+ {href ? (
132
+ <a
133
+ href={href}
134
+ onClick={() => dismiss(id)}
135
+ className="-m-1 block rounded-md p-1 hover:bg-surface-alt focus-visible:outline-2 focus-visible:outline-primary"
136
+ >
137
+ {body}
138
+ </a>
139
+ ) : (
140
+ body
141
+ )}
142
+ {hint && <p className="mt-1.5 text-xs text-text-muted">{hint}</p>}
143
+ {details.length > 0 && <Details lines={details} />}
144
+ {actions.length > 0 && (
145
+ <div className="-ml-2.5 mt-1.5 flex flex-wrap items-center gap-1">
146
+ {actions.map((action, index) => (
147
+ <ActionButton key={index} action={action} id={id} />
148
+ ))}
149
+ </div>
150
+ )}
151
+ </div>
152
+ </div>
153
+ <button
154
+ type="button"
155
+ onClick={() => dismiss(id)}
156
+ aria-label={`Dismiss notification: ${title}`}
157
+ className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text focus-visible:outline-2 focus-visible:outline-primary"
158
+ >
159
+ <HiOutlineXMark size={16} aria-hidden="true" />
160
+ </button>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ /**
166
+ * The pop-up stack, and the one place the app's notifications (see `store.ts`) are drawn. Mounted once, in the persistent app frame, so
167
+ * it survives page changes and covers every app.
168
+ *
169
+ * **Where it sits.** Top right, *below* the header row: the stack is anchored to a zero-height `sticky` line placed right after the
170
+ * frame's header, so it starts under the header (and so under the account menu button, whatever height a branded header has) and, as
171
+ * the page scrolls, sticks just below the header (`--rr-header-h`, published by the title bar / `FrameBrandingHeader` - see `headerOffset.ts`;
172
+ * never less than 2.5 rem, which clears a full-screen compose sheet's own title bar on a phone).
173
+ * On a desktop the compose windows are anchored at the bottom right, the far end of the screen from this; it never covers the account menu,
174
+ * the header's actions or a compose window's title bar and Send button unless the window is stretched over most of the screen height.
175
+ * It sits above the compose sheet (z-index 60 against 50), so a pop-up is never hidden behind it. Its height is capped and scrolls.
176
+ *
177
+ * **Live regions.** Two always-mounted containers with `aria-live` (a live region has to exist before its content changes to be announced):
178
+ * errors in an assertive one, everything else in a polite one, and each pop-up carries the matching role - `alert` for an error, `status`
179
+ * for the rest - so it is announced accordingly, and the empty containers add no `alert`/`status` of their own to the page.
180
+ * `prefers-reduced-motion` switches the entrance animation off (`.rr-toast-in`).
181
+ */
182
+ export default function NotificationCenter() {
183
+ const { visible, queued } = useNotifications();
184
+ usePauseWhileHidden();
185
+ useSystemErrorNotifications();
186
+ const errors = visible.filter((notification) => notification.kind === "error");
187
+ const others = visible.filter((notification) => notification.kind !== "error");
188
+ return (
189
+ <div data-testid="notification-anchor" className="sticky top-[max(var(--rr-header-h,0px),2.5rem)] z-[60] h-0 w-full">
190
+ <div
191
+ data-testid="notification-stack"
192
+ // Never taller than the room above the open compose windows (`ComposeProvider` publishes their top edge as `--rr-compose-top`), so
193
+ // a stack of pop-ups scrolls inside its own box rather than covering a compose window's title bar and buttons.
194
+ // (The padding is room for the pop-ups' shadow, which the scroll box would otherwise cut off in a visible edge.)
195
+ className="pointer-events-none absolute right-0 -top-1 flex max-h-[max(8rem,calc(var(--rr-compose-top,100vh)-max(var(--rr-header-h,0px),2.5rem)-2rem))] w-[25rem] max-w-[calc(100vw-0.5rem)] flex-col overflow-y-auto p-4"
196
+ >
197
+ <div data-testid="notification-errors" aria-live="assertive" className="flex flex-col gap-2 [&:not(:empty)]:mb-2">
198
+ {errors.map((notification) => (
199
+ <NotificationItem key={notification.id} notification={notification} />
200
+ ))}
201
+ </div>
202
+ <div data-testid="notification-others" aria-live="polite" className="flex flex-col gap-2">
203
+ {others.map((notification) => (
204
+ <NotificationItem key={notification.id} notification={notification} />
205
+ ))}
206
+ {queued > 0 && (
207
+ <p className="rr-solid pointer-events-auto self-end rounded-pill bg-surface px-2.5 py-0.5 text-xs text-text-muted shadow-md">
208
+ {queued} more waiting
209
+ </p>
210
+ )}
211
+ </div>
212
+ </div>
213
+ </div>
214
+ );
215
+ }
@@ -0,0 +1,90 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { useEffect, useRef } from "react";
6
+ import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
7
+ import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
8
+ import { HistoryEntry, NotificationKind } from "./store.js";
9
+ import { useNotifications } from "./useNotifications.js";
10
+
11
+ const KIND_LABEL: Record<NotificationKind, string> = {
12
+ mail: "New message",
13
+ info: "Information",
14
+ success: "Done",
15
+ warning: "Warning",
16
+ error: "Error",
17
+ };
18
+
19
+ function HistoryItem({ entry, unseen }: { entry: HistoryEntry; unseen: boolean }) {
20
+ return (
21
+ <li className={["border-b border-border py-2.5 last:border-b-0", unseen ? "-mx-2 rounded-sm bg-danger-bg/60 px-2" : ""].join(" ")}>
22
+ <div className="flex items-baseline justify-between gap-3 text-xs text-text-muted">
23
+ <span className={["font-semibold uppercase tracking-wide", entry.kind === "error" ? "text-danger" : ""].join(" ")}>{KIND_LABEL[entry.kind]}</span>
24
+ <time dateTime={new Date(entry.at).toISOString()}>{new Date(entry.at).toLocaleTimeString()}</time>
25
+ </div>
26
+ <p className="mt-0.5 text-sm font-semibold [overflow-wrap:anywhere]">
27
+ {entry.title}
28
+ {entry.count > 1 && <span className="ml-1.5 text-xs font-medium text-text-muted">&times;{entry.count}</span>}
29
+ </p>
30
+ {entry.message && <p className="text-sm text-text-muted [overflow-wrap:anywhere]">{entry.message}</p>}
31
+ {entry.details.length > 0 && (
32
+ <details className="mt-1">
33
+ <summary className="cursor-pointer text-xs font-semibold text-text-muted">Technical details</summary>
34
+ <ul aria-label="Technical details" className="mt-1.5 max-h-40 overflow-auto rounded-sm border border-border bg-surface-alt p-2 font-mono text-xs select-text">
35
+ {entry.details.map((line, index) => (
36
+ <li key={index} className="whitespace-pre-wrap break-words">
37
+ {line}
38
+ </li>
39
+ ))}
40
+ </ul>
41
+ <div className="mt-1.5">
42
+ <CopyButton value={entry.details.join("\n")} label="Copy technical details" />
43
+ </div>
44
+ </details>
45
+ )}
46
+ </li>
47
+ );
48
+ }
49
+
50
+ export interface NotificationHistoryDialogProps {
51
+ open: boolean;
52
+ onClose: () => void;
53
+ }
54
+
55
+ /**
56
+ * "Recent notifications": the last few pop-ups, newest first, so nothing is lost when one expires. Opening it counts as having seen
57
+ * them (the unseen-error count on the account menu goes), but the errors that were unseen when it opened stay highlighted until it closes.
58
+ */
59
+ export default function NotificationHistoryDialog({ open, onClose }: NotificationHistoryDialogProps) {
60
+ const { history, markHistorySeen, clearHistory } = useNotifications();
61
+ const unseenAtOpenRef = useRef<Set<string>>(new Set());
62
+ if (!open) {
63
+ unseenAtOpenRef.current = new Set(history.filter((entry) => entry.unseen && entry.kind === "error").map((entry) => entry.id));
64
+ }
65
+ useEffect(() => {
66
+ if (open) {
67
+ markHistorySeen();
68
+ }
69
+ }, [open, markHistorySeen]);
70
+ return (
71
+ <Modal open={open} onClose={onClose} title="Recent notifications">
72
+ {history.length === 0 ? (
73
+ <p className="text-sm text-text-muted">Nothing yet. Errors, sent messages and other pop-ups from this session are listed here.</p>
74
+ ) : (
75
+ <>
76
+ <ul aria-label="Recent notifications">
77
+ {history.map((entry) => (
78
+ <HistoryItem key={entry.id} entry={entry} unseen={unseenAtOpenRef.current.has(entry.id)} />
79
+ ))}
80
+ </ul>
81
+ <div className="mt-4">
82
+ <button type="button" onClick={clearHistory} className="rounded-sm px-2.5 py-1 text-xs font-semibold text-primary-dark hover:bg-surface-alt">
83
+ Clear list
84
+ </button>
85
+ </div>
86
+ </>
87
+ )}
88
+ </Modal>
89
+ );
90
+ }
@@ -0,0 +1,98 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
6
+ import { describeSendFailure } from "@rapidmx/react-shared/mail/sendFailure.js";
7
+ import { NotificationAction, notify } from "./store.js";
8
+
9
+ /** Whether `err` is a request that was cancelled (`AbortError`) - by name, since a `DOMException` is not always an `Error` (jsdom's is not). */
10
+ export function isAbortError(err: unknown): boolean {
11
+ return typeof err === "object" && err !== null && (err as { name?: unknown }).name === "AbortError";
12
+ }
13
+
14
+ /** Where "Sign in" goes: auth-server's base URL, set once by the app frame. Without one it reloads the page, which redirects when signed out. */
15
+ let signInBaseUrl: string | undefined;
16
+
17
+ export function setSignInUrl(authServerUrl: string | undefined): void {
18
+ signInBaseUrl = authServerUrl;
19
+ }
20
+
21
+ /** The "Your session expired" pop-up: one however many requests noticed, with a "Sign in" action. Returns its id. */
22
+ export function notifySessionExpired(): string {
23
+ return notify({
24
+ kind: "error",
25
+ title: "Your session expired",
26
+ message: "Sign in again to keep working. Anything not yet saved in this tab is still here until you leave it.",
27
+ dedupeKey: "session-expired",
28
+ actions: [
29
+ {
30
+ label: "Sign in",
31
+ onClick: () => {
32
+ if (signInBaseUrl) {
33
+ window.location.href = `${signInBaseUrl}/auth/signin?return_to=${encodeURIComponent(window.location.href)}`;
34
+ } else {
35
+ window.location.reload();
36
+ }
37
+ },
38
+ },
39
+ ],
40
+ });
41
+ }
42
+
43
+ export interface NotifyApiErrorOptions {
44
+ /** Extra actions (a "Retry"). */
45
+ actions?: NotificationAction[];
46
+ /** Replaces the default key (`api:<context>:<status>`): errors with one key are one pop-up with a count. */
47
+ dedupeKey?: string;
48
+ }
49
+
50
+ /** Whether `err` is a request that never got an answer (offline, DNS, a dropped connection) rather than an error response. */
51
+ function isNetworkFailure(err: unknown): boolean {
52
+ return err instanceof TypeError || (err instanceof Error && /network|failed to fetch|load failed/i.test(err.message));
53
+ }
54
+
55
+ /**
56
+ * Turns the failure of something the user did into an `error` pop-up and returns its id - or, for a `401`, into "Your session expired"
57
+ * with a Sign in action, and for a request that was cancelled into nothing (`""`). `context` says what was being attempted, as the
58
+ * pop-up's title ("Couldn't archive this message"); the message is the server's own, and its technical facts (`describeSendFailure()`'s
59
+ * lines, the status and code) are the expandable details. The same failing thing repeating is one pop-up with a count.
60
+ */
61
+ export function notifyApiError(err: unknown, context: string, options: NotifyApiErrorOptions = {}): string {
62
+ if (isAbortError(err)) {
63
+ return "";
64
+ }
65
+ if (err instanceof ApiRequestError) {
66
+ if (err.status === 401) {
67
+ return notifySessionExpired();
68
+ }
69
+ const failure = describeSendFailure(err, context);
70
+ const facts = [`Status: ${err.status}${err.code ? ` (${err.code})` : ""}`, ...failure.lines];
71
+ return notify({
72
+ kind: "error",
73
+ title: context,
74
+ message: failure.message,
75
+ details: facts,
76
+ actions: options.actions,
77
+ dedupeKey: options.dedupeKey ?? `api:${context}:${err.status}`,
78
+ });
79
+ }
80
+ if (isNetworkFailure(err)) {
81
+ return notify({
82
+ kind: "error",
83
+ title: context,
84
+ message: "The server couldn't be reached. Check your connection and try again.",
85
+ details: [(err as Error).message],
86
+ actions: options.actions,
87
+ dedupeKey: options.dedupeKey ?? `api:${context}:network`,
88
+ });
89
+ }
90
+ return notify({
91
+ kind: "error",
92
+ title: context,
93
+ message: "Something unexpected went wrong.",
94
+ details: err instanceof Error ? [`${err.name}: ${err.message}`] : [String(err)],
95
+ actions: options.actions,
96
+ dedupeKey: options.dedupeKey ?? `api:${context}:unexpected`,
97
+ });
98
+ }
@@ -0,0 +1,35 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useCallback, useRef } from "react";
6
+
7
+ /** The CSS variable (on `<html>`) that holds the height, in px, of the frame's sticky header - its title bar or the branding header. */
8
+ export const HEADER_HEIGHT_VAR = "--rr-header-h";
9
+
10
+ /**
11
+ * A ref callback for the frame's sticky `<header>`: publishes its height as `--rr-header-h` (and keeps it current as the header resizes -
12
+ * a branded header can be any height and wraps on a phone), so the pop-up stack (`NotificationCenter`) can stick just below it instead of
13
+ * sliding under it or over the account menu. When the header goes (a page without one) the variable is set to 0 rather than left stale.
14
+ */
15
+ export function useHeaderHeightRef(): (element: HTMLElement | null) => void {
16
+ const stop = useRef<(() => void) | undefined>(undefined);
17
+ return useCallback((element: HTMLElement | null) => {
18
+ stop.current?.();
19
+ stop.current = undefined;
20
+ const root = document.documentElement;
21
+ if (!element) {
22
+ root.style.setProperty(HEADER_HEIGHT_VAR, "0px");
23
+ return;
24
+ }
25
+ const publish = () => root.style.setProperty(HEADER_HEIGHT_VAR, `${Math.round(element.getBoundingClientRect().height)}px`);
26
+ publish();
27
+ const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(publish);
28
+ observer?.observe(element);
29
+ window.addEventListener("resize", publish);
30
+ stop.current = () => {
31
+ observer?.disconnect();
32
+ window.removeEventListener("resize", publish);
33
+ };
34
+ }, []);
35
+ }
@@ -0,0 +1,56 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useEffect } from "react";
6
+ import { getPushClient, PushStatus } from "@rapidmx/react-shared/mail/pushClient.js";
7
+ import { dismiss, notify } from "./store.js";
8
+
9
+ /** How long the live connection has to be down before anyone is told: a reconnect within a few seconds is not worth a word. */
10
+ export const PUSH_OFFLINE_NOTICE_MS = 10_000;
11
+
12
+ export const PUSH_NOTICE_ID = "push-connection";
13
+
14
+ /**
15
+ * A subtle info pop-up when the live connection (new mail, folder counts, send results) has been down for more than `PUSH_OFFLINE_NOTICE_MS`,
16
+ * which goes by itself the moment it is back. Shown once per outage - dismissing it does not bring it back until the connection has
17
+ * reconnected and dropped again. The page keeps working meanwhile (it polls), which is what the message says.
18
+ */
19
+ export function usePushConnectionNotice(enabled: boolean): void {
20
+ useEffect(() => {
21
+ if (!enabled) {
22
+ return;
23
+ }
24
+ const client = getPushClient();
25
+ let timer: ReturnType<typeof setTimeout> | undefined;
26
+ let shown = false;
27
+ const off = client.onStatus((status: PushStatus) => {
28
+ if (status === "reconnecting") {
29
+ if (timer === undefined && !shown) {
30
+ timer = setTimeout(() => {
31
+ shown = true;
32
+ notify({
33
+ id: PUSH_NOTICE_ID,
34
+ kind: "info",
35
+ title: "Live updates are paused",
36
+ message: "The connection is being restored. The page keeps refreshing on its own, so new mail may take a little longer to appear.",
37
+ sticky: true,
38
+ history: false,
39
+ });
40
+ }, PUSH_OFFLINE_NOTICE_MS);
41
+ }
42
+ } else {
43
+ clearTimeout(timer);
44
+ timer = undefined;
45
+ if (status === "open" && shown) {
46
+ shown = false;
47
+ dismiss(PUSH_NOTICE_ID);
48
+ }
49
+ }
50
+ });
51
+ return () => {
52
+ off();
53
+ clearTimeout(timer);
54
+ };
55
+ }, [enabled]);
56
+ }