@rapidmx/web-client 0.5.0 → 0.7.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 (224) hide show
  1. package/README.md +88 -0
  2. package/apps/shared/components/admin/layout/AdminShell.tsx +25 -8
  3. package/apps/shared/components/admin/settings/BrandingForm.tsx +398 -385
  4. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -112
  5. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +159 -152
  6. package/apps/shared/components/admin/settings/PluginsManager.tsx +1288 -1235
  7. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +9 -1
  8. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -290
  9. package/apps/shared/components/admin/setup/SetupWizard.tsx +443 -416
  10. package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
  11. package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
  12. package/apps/shared/components/layout/AppShell.tsx +288 -270
  13. package/apps/shared/components/mail/ConversationList.tsx +225 -33
  14. package/apps/shared/components/mail/ConversationThreadPane.tsx +434 -216
  15. package/apps/shared/components/mail/MailListToolbar.tsx +265 -0
  16. package/apps/shared/components/mail/MailSelectionBar.tsx +222 -0
  17. package/apps/shared/components/mail/MenuButton.tsx +384 -0
  18. package/apps/shared/components/mail/MessageDetailPane.tsx +280 -215
  19. package/apps/shared/components/mail/MoveToFolderDialog.tsx +263 -0
  20. package/apps/shared/components/mail/compose/ComposeContext.tsx +29 -2
  21. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1700 -1652
  22. package/apps/shared/components/mail/compose/RecipientInput.tsx +356 -0
  23. package/apps/shared/components/mail/compose/RichTextEditor.tsx +38 -3
  24. package/apps/shared/components/mail/compose/quotedBody.ts +100 -0
  25. package/apps/shared/components/mail/compose/recipients.ts +104 -0
  26. package/apps/shared/components/mail/labelMenu.tsx +278 -0
  27. package/apps/shared/components/mail/layout/MailShell.tsx +31 -4
  28. package/apps/shared/components/mail/listPreferences.ts +228 -0
  29. package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -10
  30. package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
  31. package/apps/shared/plugins/pluginNav.ts +73 -0
  32. package/apps/shared/search/localIndexRpcClient.ts +3 -1
  33. package/apps/shared/styles/app.css +28 -5
  34. package/apps/www/index.tsx +1854 -1314
  35. package/apps/www/messages/[uid].tsx +5 -4
  36. package/dist/apps/admin/_404.d.ts +2 -0
  37. package/dist/apps/admin/_500.d.ts +2 -0
  38. package/dist/apps/admin/_layout.d.ts +8 -0
  39. package/dist/apps/admin/audit-log/index.d.ts +11 -0
  40. package/dist/apps/admin/branding/index.d.ts +3 -0
  41. package/dist/apps/admin/data-requests/index.d.ts +3 -0
  42. package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
  43. package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
  44. package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
  45. package/dist/apps/admin/domains/[uid].d.ts +7 -0
  46. package/dist/apps/admin/domains/index.d.ts +3 -0
  47. package/dist/apps/admin/domains/new/index.d.ts +3 -0
  48. package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
  49. package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
  50. package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
  51. package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
  52. package/dist/apps/admin/index.d.ts +3 -0
  53. package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
  54. package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
  55. package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
  56. package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
  57. package/dist/apps/admin/plugins/index.d.ts +3 -0
  58. package/dist/apps/admin/quarantine/index.d.ts +6 -0
  59. package/dist/apps/admin/retention-policy/index.d.ts +3 -0
  60. package/dist/apps/admin/setup/index.d.ts +3 -0
  61. package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
  62. package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
  63. package/dist/apps/admin/transport-rules/index.d.ts +3 -0
  64. package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
  65. package/dist/apps/escrow/_layout.d.ts +10 -0
  66. package/dist/apps/escrow/audit-log/index.d.ts +9 -0
  67. package/dist/apps/escrow/index.d.ts +3 -0
  68. package/dist/apps/escrow/matters/[uid].d.ts +7 -0
  69. package/dist/apps/escrow/matters/new/index.d.ts +3 -0
  70. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
  71. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
  72. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
  73. package/dist/apps/shared/components/admin/layout/AdminShell.js +12 -5
  74. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
  75. package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
  76. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
  77. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
  78. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +13 -0
  79. package/dist/apps/shared/components/admin/settings/BrandingForm.js +6 -5
  80. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
  81. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +15 -0
  82. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +2 -2
  83. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
  84. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
  85. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +13 -0
  86. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +2 -2
  87. package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +11 -0
  88. package/dist/apps/shared/components/admin/settings/PluginsManager.js +33 -14
  89. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +12 -0
  90. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +2 -2
  91. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
  92. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +1 -1
  93. package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
  94. package/dist/apps/shared/components/admin/setup/SetupWizard.js +14 -10
  95. package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
  96. package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
  97. package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
  98. package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
  99. package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
  100. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
  101. package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
  102. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
  103. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
  104. package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
  105. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
  106. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +2 -2
  107. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
  108. package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
  109. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
  110. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
  111. package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
  112. package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
  113. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
  114. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
  115. package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
  116. package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
  117. package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
  118. package/dist/apps/shared/components/layout/AppShell.js +15 -13
  119. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
  120. package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
  121. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
  122. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
  123. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
  124. package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
  125. package/dist/apps/shared/components/mail/ConversationList.d.ts +47 -0
  126. package/dist/apps/shared/components/mail/ConversationList.js +84 -10
  127. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +62 -0
  128. package/dist/apps/shared/components/mail/ConversationThreadPane.js +217 -60
  129. package/dist/apps/shared/components/mail/MailListToolbar.d.ts +55 -0
  130. package/dist/apps/shared/components/mail/MailListToolbar.js +149 -0
  131. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +59 -0
  132. package/dist/apps/shared/components/mail/MailSelectionBar.js +48 -0
  133. package/dist/apps/shared/components/mail/MenuButton.d.ts +71 -0
  134. package/dist/apps/shared/components/mail/MenuButton.js +211 -0
  135. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +101 -0
  136. package/dist/apps/shared/components/mail/MessageDetailPane.js +126 -129
  137. package/dist/apps/shared/components/mail/MoveToFolderDialog.d.ts +57 -0
  138. package/dist/apps/shared/components/mail/MoveToFolderDialog.js +138 -0
  139. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +73 -0
  140. package/dist/apps/shared/components/mail/compose/ComposeContext.js +3 -1
  141. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
  142. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
  143. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +52 -20
  144. package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
  145. package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
  146. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +36 -0
  147. package/dist/apps/shared/components/mail/compose/RecipientInput.js +203 -0
  148. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +43 -0
  149. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +21 -3
  150. package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
  151. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
  152. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +37 -0
  153. package/dist/apps/shared/components/mail/compose/quotedBody.js +85 -0
  154. package/dist/apps/shared/components/mail/compose/recipients.d.ts +24 -0
  155. package/dist/apps/shared/components/mail/compose/recipients.js +87 -0
  156. package/dist/apps/shared/components/mail/labelMenu.d.ts +97 -0
  157. package/dist/apps/shared/components/mail/labelMenu.js +134 -0
  158. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +65 -0
  159. package/dist/apps/shared/components/mail/layout/MailShell.js +17 -5
  160. package/dist/apps/shared/components/mail/listPreferences.d.ts +101 -0
  161. package/dist/apps/shared/components/mail/listPreferences.js +186 -0
  162. package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
  163. package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
  164. package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
  165. package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
  166. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
  167. package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -10
  168. package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
  169. package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
  170. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
  171. package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
  172. package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
  173. package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
  174. package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
  175. package/dist/apps/shared/plugins/pluginNav.js +33 -0
  176. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
  177. package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
  178. package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
  179. package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
  180. package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
  181. package/dist/apps/shared/search/localIndexRpcClient.js +3 -1
  182. package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
  183. package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
  184. package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
  185. package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
  186. package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
  187. package/dist/apps/shared/search/searchTier2.d.ts +30 -0
  188. package/dist/apps/shared/styles/app.css +28 -5
  189. package/dist/apps/www/_404.d.ts +2 -0
  190. package/dist/apps/www/_500.d.ts +2 -0
  191. package/dist/apps/www/_layout.d.ts +14 -0
  192. package/dist/apps/www/calendar/index.d.ts +3 -0
  193. package/dist/apps/www/contacts/[uid].d.ts +13 -0
  194. package/dist/apps/www/contacts/index.d.ts +3 -0
  195. package/dist/apps/www/index.d.ts +3 -0
  196. package/dist/apps/www/index.js +506 -112
  197. package/dist/apps/www/messages/[uid].d.ts +11 -0
  198. package/dist/apps/www/messages/[uid].js +2 -3
  199. package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
  200. package/dist/apps/www/settings/encryption/index.d.ts +7 -0
  201. package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
  202. package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
  203. package/dist/apps/www/settings/filters/index.d.ts +4 -0
  204. package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
  205. package/dist/apps/www/settings/labels/index.d.ts +4 -0
  206. package/dist/apps/www/settings/privacy/index.d.ts +4 -0
  207. package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
  208. package/dist/apps/www/settings/sharing/index.d.ts +4 -0
  209. package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
  210. package/dist/apps/www/settings/signatures/index.d.ts +4 -0
  211. package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
  212. package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
  213. package/dist/apps/www/tasks/index.d.ts +3 -0
  214. package/package.json +3 -3
  215. package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
  216. package/apps/www/settings/booking-types/[uid].tsx +0 -306
  217. package/apps/www/settings/booking-types/index.tsx +0 -98
  218. package/apps/www/settings/booking-types/new/index.tsx +0 -212
  219. package/apps/www/settings/focused-inbox/index.tsx +0 -150
  220. package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
  221. package/dist/apps/www/settings/booking-types/[uid].js +0 -134
  222. package/dist/apps/www/settings/booking-types/index.js +0 -29
  223. package/dist/apps/www/settings/booking-types/new/index.js +0 -86
  224. package/dist/apps/www/settings/focused-inbox/index.js +0 -71
@@ -1,98 +0,0 @@
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 { BookingType, listBookingTypes } from "@rapidmx/react-shared/booking/bookingApi.js";
8
- import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
9
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
10
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
11
-
12
- export type SettingsBookingTypesPageProps = Omit<SettingsShellProps, "active">;
13
-
14
- export default function SettingsBookingTypesPage(props: SettingsBookingTypesPageProps) {
15
- return (
16
- <SettingsShell {...props} active="booking-types">
17
- <BookingTypesContent />
18
- </SettingsShell>
19
- );
20
- }
21
-
22
- function BookingTypesContent() {
23
- const { mailboxUid } = useSettingsShell();
24
- const [bookingTypes, setBookingTypes] = useState<BookingType[]>([]);
25
- const [loading, setLoading] = useState(true);
26
- const [error, setError] = useState<string | null>(null);
27
-
28
- // `SettingsShell` only ever renders its children once `mailboxUid` has resolved — same established
29
- // non-null pattern as `apps/www/settings/filters/index.tsx`.
30
- useEffect(() => {
31
- listBookingTypes(mailboxUid!)
32
- .then(setBookingTypes)
33
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load your booking links."))
34
- .finally(() => setLoading(false));
35
- }, [mailboxUid]);
36
-
37
- return (
38
- <div className="flex-1 min-w-0 overflow-y-auto p-6">
39
- <div className="max-w-3xl">
40
- <div className="flex items-center justify-between mb-5">
41
- <h1 className="text-lg font-bold tracking-tight">Booking Links</h1>
42
- <a href={`/settings/booking-types/new?mailboxUid=${encodeURIComponent(mailboxUid!)}`}>
43
- <Button type="button" className="!w-auto">
44
- + New booking link
45
- </Button>
46
- </a>
47
- </div>
48
- <p className="text-sm text-text-muted mb-4">
49
- Share a link and let anyone pick a real open slot on your calendar — no account needed on
50
- their end.
51
- </p>
52
-
53
- {error && <Alert>{error}</Alert>}
54
-
55
- {loading ? (
56
- <p className="text-sm text-text-muted">Loading&hellip;</p>
57
- ) : bookingTypes.length === 0 ? (
58
- <p className="text-sm text-text-muted">No booking links yet.</p>
59
- ) : (
60
- <div className="overflow-x-auto">
61
- <table className="w-full text-sm border-collapse">
62
- <thead>
63
- <tr>
64
- {["Name", "Link", "Duration", "Enabled", ""].map((h) => (
65
- <th
66
- key={h}
67
- className="text-left text-xs uppercase tracking-wide text-text-muted py-2 px-2.5 border-b border-border"
68
- >
69
- {h}
70
- </th>
71
- ))}
72
- </tr>
73
- </thead>
74
- <tbody>
75
- {bookingTypes.map((bookingType) => (
76
- <tr key={bookingType.uid}>
77
- <td className="py-2.5 px-2.5 border-b border-border">{bookingType.name}</td>
78
- <td className="py-2.5 px-2.5 border-b border-border">/book/{bookingType.slug}</td>
79
- <td className="py-2.5 px-2.5 border-b border-border">{bookingType.durationMinutes} min</td>
80
- <td className="py-2.5 px-2.5 border-b border-border">{bookingType.enabled ? "Yes" : "No"}</td>
81
- <td className="py-2.5 px-2.5 border-b border-border text-right">
82
- <a
83
- href={`/settings/booking-types/${encodeURIComponent(bookingType.uid)}?mailboxUid=${encodeURIComponent(mailboxUid!)}`}
84
- className="text-primary-dark hover:underline font-medium"
85
- >
86
- View
87
- </a>
88
- </td>
89
- </tr>
90
- ))}
91
- </tbody>
92
- </table>
93
- </div>
94
- )}
95
- </div>
96
- </div>
97
- );
98
- }
@@ -1,212 +0,0 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { FormEvent, useEffect, useState } from "react";
6
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
7
- import { Folder, listFolders } from "@rapidmx/react-shared/mail/mailApi.js";
8
- import { BookingAvailabilityWindow, createBookingType } from "@rapidmx/react-shared/booking/bookingApi.js";
9
- import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../../shared/components/settings/layout/SettingsShell.js";
10
- import AvailabilityEditor from "../../../../shared/components/booking/AvailabilityEditor.js";
11
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
13
- import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
14
-
15
- const INPUT_CLASS =
16
- "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
17
-
18
- export type NewBookingTypePageProps = Omit<SettingsShellProps, "active">;
19
-
20
- export default function NewBookingTypePage(props: NewBookingTypePageProps) {
21
- return (
22
- <SettingsShell {...props} active="booking-types">
23
- <NewBookingTypeForm />
24
- </SettingsShell>
25
- );
26
- }
27
-
28
- function NewBookingTypeForm() {
29
- const { mailboxUid, mailboxes } = useSettingsShell();
30
- // Same established non-null pattern as `apps/www/settings/filters/new/index.tsx`.
31
- const mailbox = mailboxes.find((mb) => mb.uid === mailboxUid)!;
32
-
33
- const [calendarFolderUid, setCalendarFolderUid] = useState<string | null>(null);
34
- const [folderError, setFolderError] = useState<string | null>(null);
35
- const [slug, setSlug] = useState("");
36
- const [name, setName] = useState("");
37
- const [description, setDescription] = useState("");
38
- const [hostDisplayName, setHostDisplayName] = useState(mailbox.displayName);
39
- const [durationMinutes, setDurationMinutes] = useState(30);
40
- const [timezone, setTimezone] = useState(mailbox.timezone);
41
- const [availability, setAvailability] = useState<BookingAvailabilityWindow[]>([]);
42
- const [minimumNoticeMinutes, setMinimumNoticeMinutes] = useState(60);
43
- const [bookingWindowDays, setBookingWindowDays] = useState(30);
44
- const [requiresApproval, setRequiresApproval] = useState(false);
45
- const [error, setError] = useState<string | null>(null);
46
- const [saving, setSaving] = useState(false);
47
-
48
- useEffect(() => {
49
- listFolders(mailboxUid!)
50
- .then((folders) => {
51
- const calendar = folders.find((f: Folder) => f.type === "calendar");
52
- if (!calendar) {
53
- setFolderError("This mailbox has no Calendar folder yet.");
54
- return;
55
- }
56
- setCalendarFolderUid(calendar.uid);
57
- })
58
- .catch((err) => setFolderError(err instanceof ApiRequestError ? err.message : "Could not load this mailbox's folders."));
59
- }, [mailboxUid]);
60
-
61
- async function handleSubmit(e: FormEvent) {
62
- e.preventDefault();
63
- setError(null);
64
-
65
- if (!slug.trim() || !name.trim() || !hostDisplayName.trim()) {
66
- setError("Slug, name, and host name are all required.");
67
- return;
68
- }
69
- if (!calendarFolderUid) {
70
- setError("This mailbox has no Calendar folder yet.");
71
- return;
72
- }
73
-
74
- setSaving(true);
75
- try {
76
- const created = await createBookingType({
77
- mailboxUid: mailboxUid!,
78
- calendarFolderUid,
79
- slug: slug.trim(),
80
- name: name.trim(),
81
- description: description.trim() || undefined,
82
- hostDisplayName: hostDisplayName.trim(),
83
- durationMinutes,
84
- timezone,
85
- availability,
86
- minimumNoticeMinutes,
87
- bookingWindowDays,
88
- requiresApproval,
89
- });
90
- window.location.href = `/settings/booking-types/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid!)}`;
91
- } catch (err) {
92
- setError(err instanceof ApiRequestError ? err.message : "Could not create this booking link.");
93
- } finally {
94
- setSaving(false);
95
- }
96
- }
97
-
98
- return (
99
- <div className="flex-1 min-w-0 overflow-y-auto p-6">
100
- <div className="max-w-2xl">
101
- <h1 className="text-lg font-bold tracking-tight mb-1">New booking link</h1>
102
- <p className="text-sm text-text-muted mb-5">
103
- Anyone with the link can pick an open slot from your live availability below — no account
104
- needed on their end.
105
- </p>
106
-
107
- {error && <Alert>{error}</Alert>}
108
- {folderError && <Alert>{folderError}</Alert>}
109
-
110
- <form onSubmit={handleSubmit} className="flex flex-col gap-1">
111
- <FormField label="Name" htmlFor="name">
112
- <input
113
- id="name"
114
- type="text"
115
- className={INPUT_CLASS}
116
- value={name}
117
- onChange={(e) => setName(e.target.value)}
118
- placeholder="30 Minute Intro Call"
119
- />
120
- </FormField>
121
- <FormField label="Slug (used in the public link)" htmlFor="slug">
122
- <input
123
- id="slug"
124
- type="text"
125
- className={INPUT_CLASS}
126
- value={slug}
127
- onChange={(e) => setSlug(e.target.value)}
128
- placeholder="intro-call"
129
- />
130
- </FormField>
131
- <FormField label="Description (optional)" htmlFor="description">
132
- <textarea
133
- id="description"
134
- className={INPUT_CLASS}
135
- rows={2}
136
- value={description}
137
- onChange={(e) => setDescription(e.target.value)}
138
- />
139
- </FormField>
140
- <FormField label="Host name shown to visitors" htmlFor="hostDisplayName">
141
- <input
142
- id="hostDisplayName"
143
- type="text"
144
- className={INPUT_CLASS}
145
- value={hostDisplayName}
146
- onChange={(e) => setHostDisplayName(e.target.value)}
147
- />
148
- </FormField>
149
- <div className="grid grid-cols-2 gap-3">
150
- <FormField label="Duration (minutes)" htmlFor="durationMinutes">
151
- <input
152
- id="durationMinutes"
153
- type="number"
154
- min={1}
155
- className={INPUT_CLASS}
156
- value={durationMinutes}
157
- onChange={(e) => setDurationMinutes(Number(e.target.value))}
158
- />
159
- </FormField>
160
- <FormField label="Timezone" htmlFor="timezone">
161
- <input
162
- id="timezone"
163
- type="text"
164
- className={INPUT_CLASS}
165
- value={timezone}
166
- onChange={(e) => setTimezone(e.target.value)}
167
- placeholder="America/New_York"
168
- />
169
- </FormField>
170
- </div>
171
- <div className="grid grid-cols-2 gap-3">
172
- <FormField label="Minimum notice (minutes)" htmlFor="minimumNoticeMinutes">
173
- <input
174
- id="minimumNoticeMinutes"
175
- type="number"
176
- min={0}
177
- className={INPUT_CLASS}
178
- value={minimumNoticeMinutes}
179
- onChange={(e) => setMinimumNoticeMinutes(Number(e.target.value))}
180
- />
181
- </FormField>
182
- <FormField label="Booking window (days ahead)" htmlFor="bookingWindowDays">
183
- <input
184
- id="bookingWindowDays"
185
- type="number"
186
- min={1}
187
- className={INPUT_CLASS}
188
- value={bookingWindowDays}
189
- onChange={(e) => setBookingWindowDays(Number(e.target.value))}
190
- />
191
- </FormField>
192
- </div>
193
- <label className="flex items-center gap-2 text-sm my-3">
194
- <input type="checkbox" checked={requiresApproval} onChange={(e) => setRequiresApproval(e.target.checked)} />
195
- Require my approval before confirming a booking
196
- </label>
197
-
198
- <div className="mb-4">
199
- <span className="block text-sm font-semibold mb-1.5 text-text">Weekly availability</span>
200
- <AvailabilityEditor value={availability} onChange={setAvailability} />
201
- </div>
202
-
203
- <div>
204
- <Button type="submit" loading={saving} disabled={saving} className="!w-auto">
205
- Create
206
- </Button>
207
- </div>
208
- </form>
209
- </div>
210
- </div>
211
- );
212
- }
@@ -1,150 +0,0 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { FormEvent, useEffect, useState } from "react";
6
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
7
- import { MessageClassification } from "@rapidmx/react-shared/mail/mailApi.js";
8
- import {
9
- FocusedInboxOverride,
10
- createFocusedInboxOverride,
11
- deleteFocusedInboxOverride,
12
- listFocusedInboxOverrides,
13
- } from "@rapidmx/react-shared/mail/focusedInboxOverridesApi.js";
14
- import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
15
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
16
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
17
-
18
- export type SettingsFocusedInboxPageProps = Omit<SettingsShellProps, "active">;
19
-
20
- export default function SettingsFocusedInboxPage(props: SettingsFocusedInboxPageProps) {
21
- return (
22
- <SettingsShell {...props} active="focused-inbox">
23
- <FocusedInboxContent />
24
- </SettingsShell>
25
- );
26
- }
27
-
28
- function FocusedInboxContent() {
29
- const { mailboxUid } = useSettingsShell();
30
- const [overrides, setOverrides] = useState<FocusedInboxOverride[]>([]);
31
- const [loading, setLoading] = useState(true);
32
- const [error, setError] = useState<string | null>(null);
33
- const [saving, setSaving] = useState(false);
34
- const [newSender, setNewSender] = useState("");
35
- const [newClassifyAs, setNewClassifyAs] = useState<MessageClassification>("other");
36
-
37
- function reload() {
38
- setLoading(true);
39
- setError(null);
40
- // `SettingsShell` only ever renders its children once `mailboxUid` has resolved — same
41
- // established non-null pattern as `apps/www/settings/filters/index.tsx`.
42
- listFocusedInboxOverrides(mailboxUid!)
43
- .then(setOverrides)
44
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load your Focused Inbox rules."))
45
- .finally(() => setLoading(false));
46
- }
47
-
48
- useEffect(reload, [mailboxUid]);
49
-
50
- async function handleAdd(e: FormEvent) {
51
- e.preventDefault();
52
- // Lowercased to match restapi's own `normalizeAddress()` - what gets sent is exactly what gets
53
- // stored and matched against, rather than relying on the server to silently rewrite it.
54
- const senderAddress = newSender.trim().toLowerCase();
55
- if (!senderAddress) {
56
- return;
57
- }
58
- setSaving(true);
59
- setError(null);
60
- try {
61
- await createFocusedInboxOverride({ mailboxUid: mailboxUid!, senderAddress, classifyAs: newClassifyAs });
62
- setNewSender("");
63
- reload();
64
- } catch (err) {
65
- setError(err instanceof ApiRequestError ? err.message : "Could not add this rule.");
66
- } finally {
67
- setSaving(false);
68
- }
69
- }
70
-
71
- async function handleRemove(override: FocusedInboxOverride) {
72
- setSaving(true);
73
- setError(null);
74
- try {
75
- await deleteFocusedInboxOverride(override.uid, override.version);
76
- reload();
77
- } catch (err) {
78
- setError(err instanceof ApiRequestError ? err.message : "Could not remove this rule.");
79
- } finally {
80
- setSaving(false);
81
- }
82
- }
83
-
84
- return (
85
- <div className="flex-1 min-w-0 overflow-y-auto p-6">
86
- <div className="max-w-xl">
87
- <h1 className="text-lg font-bold tracking-tight mb-1">Focused Inbox</h1>
88
- <p className="text-sm text-text-muted mb-4">
89
- Mail from a sender listed here always goes to the matching half of your Inbox, overriding
90
- the automatic Focused/Other classification. Add one directly, or use &ldquo;Always for this
91
- sender&rdquo; when moving a message.
92
- </p>
93
-
94
- {error && <Alert>{error}</Alert>}
95
-
96
- {loading ? (
97
- <p className="text-sm text-text-muted">Loading&hellip;</p>
98
- ) : (
99
- <ul className="flex flex-col gap-2 mb-4">
100
- {overrides.length === 0 && <li className="text-sm text-text-muted">No rules yet.</li>}
101
- {overrides.map((override) => (
102
- <li
103
- key={override.uid}
104
- className="flex items-center justify-between gap-3 py-2 px-3 border border-border rounded-sm"
105
- >
106
- <div>
107
- <div className="text-sm font-medium">{override.senderAddress}</div>
108
- <div className="text-xs text-text-muted">
109
- Always {override.classifyAs === "other" ? "Other" : "Focused"}
110
- </div>
111
- </div>
112
- <button
113
- type="button"
114
- onClick={() => handleRemove(override)}
115
- disabled={saving}
116
- className="text-sm text-danger hover:underline disabled:opacity-55"
117
- >
118
- Remove
119
- </button>
120
- </li>
121
- ))}
122
- </ul>
123
- )}
124
-
125
- <form onSubmit={handleAdd} className="flex gap-2">
126
- <input
127
- type="text"
128
- aria-label="Sender address"
129
- value={newSender}
130
- onChange={(e) => setNewSender(e.target.value)}
131
- placeholder="sender@example.com"
132
- className="flex-1 text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary"
133
- />
134
- <select
135
- aria-label="Classify as"
136
- value={newClassifyAs}
137
- onChange={(e) => setNewClassifyAs(e.target.value as MessageClassification)}
138
- className="text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary"
139
- >
140
- <option value="other">Other</option>
141
- <option value="focused">Focused</option>
142
- </select>
143
- <Button type="submit" loading={saving} disabled={saving} className="!w-auto">
144
- Add
145
- </Button>
146
- </form>
147
- </div>
148
- </div>
149
- );
150
- }
@@ -1,45 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- ///////////////////////////////////////////////////////////////////////////////
3
- // Copyright (C) 2026 Jean-Philippe Steinmetz
4
- // SPDX-License-Identifier: MPL-2.0
5
- ///////////////////////////////////////////////////////////////////////////////
6
- import { useState } from "react";
7
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
8
- const DAY_LABELS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
9
- const INPUT_CLASS = "text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
10
- function minutesToTimeValue(minutes) {
11
- const h = Math.floor(minutes / 60)
12
- .toString()
13
- .padStart(2, "0");
14
- const m = (minutes % 60).toString().padStart(2, "0");
15
- return `${h}:${m}`;
16
- }
17
- function timeValueToMinutes(value) {
18
- const [h, m] = value.split(":").map(Number);
19
- return h * 60 + m;
20
- }
21
- /**
22
- * Adds/removes `BookingType.availability` weekly windows — deliberately does not cover `dateOverrides`
23
- * (per-date blackouts/exceptions), a separate, less commonly needed escape hatch left for a future pass;
24
- * `BookingUtils.generateCandidateSlots()` already handles an empty `dateOverrides` array gracefully.
25
- */
26
- export default function AvailabilityEditor({ value, onChange }) {
27
- const [dayOfWeek, setDayOfWeek] = useState(1);
28
- const [startTime, setStartTime] = useState("09:00");
29
- const [endTime, setEndTime] = useState("17:00");
30
- const [error, setError] = useState(null);
31
- function handleAdd() {
32
- const startMinute = timeValueToMinutes(startTime);
33
- const endMinute = timeValueToMinutes(endTime);
34
- if (startMinute >= endMinute) {
35
- setError("Start time must be before end time.");
36
- return;
37
- }
38
- setError(null);
39
- onChange([...value, { dayOfWeek, startMinute, endMinute }]);
40
- }
41
- function handleRemove(index) {
42
- onChange(value.filter((_, i) => i !== index));
43
- }
44
- return (_jsxs("div", { className: "flex flex-col gap-3", children: [error && _jsx("p", { className: "text-sm text-danger", children: error }), value.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No availability windows yet \u2014 add one below." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: value.map((window, index) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-2 px-3 border border-border rounded-sm text-sm", children: [_jsxs("span", { children: [DAY_LABELS[window.dayOfWeek], " ", minutesToTimeValue(window.startMinute), "\u2013", minutesToTimeValue(window.endMinute)] }), _jsx("button", { type: "button", onClick: () => handleRemove(index), className: "text-sm text-danger hover:underline", children: "Remove" })] }, index))) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { "aria-label": "Day of week", value: dayOfWeek, onChange: (e) => setDayOfWeek(Number(e.target.value)), className: INPUT_CLASS, children: DAY_LABELS.map((label, i) => (_jsx("option", { value: i, children: label }, label))) }), _jsx("input", { type: "time", "aria-label": "Start time", value: startTime, onChange: (e) => setStartTime(e.target.value), className: INPUT_CLASS }), _jsx("span", { className: "text-sm text-text-muted", children: "to" }), _jsx("input", { type: "time", "aria-label": "End time", value: endTime, onChange: (e) => setEndTime(e.target.value), className: INPUT_CLASS }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: handleAdd, children: "Add window" })] })] }));
45
- }