@rapidmx/web-client 0.14.1 → 0.15.1

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 (171) hide show
  1. package/apps/admin/domains/[uid].tsx +3 -2
  2. package/apps/shared/appearance/AppearanceForm.tsx +9 -1
  3. package/apps/shared/calendar/bookingPlugin.ts +100 -0
  4. package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
  5. package/apps/shared/components/admin/layout/AdminShell.tsx +37 -5
  6. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +3 -1
  7. package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1491
  8. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
  9. package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
  10. package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
  11. package/apps/shared/components/calendar/EventDetails.tsx +401 -0
  12. package/apps/shared/components/calendar/EventEditor.tsx +640 -0
  13. package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
  14. package/apps/shared/components/calendar/EventFormParts.tsx +622 -0
  15. package/apps/shared/components/calendar/EventModal.tsx +126 -866
  16. package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
  17. package/apps/shared/components/calendar/EventShell.tsx +202 -0
  18. package/apps/shared/components/calendar/FindATime.tsx +379 -0
  19. package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
  20. package/apps/shared/components/calendar/MonthView.tsx +15 -3
  21. package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
  22. package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
  23. package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
  24. package/apps/shared/components/calendar/RecurrenceEditor.tsx +10 -5
  25. package/apps/shared/components/calendar/RequestChangeForm.tsx +271 -0
  26. package/apps/shared/components/calendar/SplitDayView.tsx +6 -5
  27. package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
  28. package/apps/shared/components/calendar/TimeGridView.tsx +8 -6
  29. package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
  30. package/apps/shared/components/calendar/eventForm.ts +108 -0
  31. package/apps/shared/components/calendar/eventFormat.ts +183 -0
  32. package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
  33. package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
  34. package/apps/shared/components/layout/UserMenu.tsx +80 -28
  35. package/apps/shared/components/mail/ConversationList.tsx +100 -48
  36. package/apps/shared/components/mail/ConversationThreadPane.tsx +490 -486
  37. package/apps/shared/components/mail/InviteCard.tsx +387 -0
  38. package/apps/shared/components/mail/MessageDetailPane.tsx +24 -2
  39. package/apps/shared/components/mail/SwipeRow.tsx +120 -0
  40. package/apps/shared/components/mail/compose/ComposeWindow.tsx +7 -3
  41. package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
  42. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
  43. package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
  44. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
  45. package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
  46. package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
  47. package/apps/shared/components/mail/layout/MailShell.tsx +522 -490
  48. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  49. package/apps/shared/gestures/useEnterSlide.ts +54 -0
  50. package/apps/shared/gestures/useSwipe.ts +114 -0
  51. package/apps/shared/gestures/useSwipeSlide.ts +113 -0
  52. package/apps/shared/gestures/useWheelPaging.ts +107 -0
  53. package/apps/shared/mail/newMailNotifications.ts +171 -183
  54. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  55. package/apps/shared/notifications/preferences.ts +36 -0
  56. package/apps/shared/notifications/store.ts +560 -551
  57. package/apps/www/_routes.ts +1 -0
  58. package/apps/www/calendar/index.tsx +139 -37
  59. package/apps/www/index.tsx +273 -126
  60. package/apps/www/messages/[uid].tsx +31 -4
  61. package/apps/www/settings/profile/index.tsx +248 -0
  62. package/dist/apps/admin/domains/[uid].js +4 -2
  63. package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
  64. package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
  65. package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
  66. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
  67. package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
  68. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  69. package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
  70. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +3 -1
  71. package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
  72. package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
  73. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
  74. package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
  75. package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
  76. package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
  77. package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
  78. package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
  79. package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
  80. package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
  81. package/dist/apps/shared/components/calendar/EventEditor.js +549 -0
  82. package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
  83. package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
  84. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +69 -0
  85. package/dist/apps/shared/components/calendar/EventFormParts.js +202 -0
  86. package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
  87. package/dist/apps/shared/components/calendar/EventModal.js +57 -459
  88. package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
  89. package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
  90. package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
  91. package/dist/apps/shared/components/calendar/EventShell.js +134 -0
  92. package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
  93. package/dist/apps/shared/components/calendar/FindATime.js +190 -0
  94. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
  95. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
  96. package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
  97. package/dist/apps/shared/components/calendar/MonthView.js +11 -5
  98. package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
  99. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
  100. package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
  101. package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
  102. package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
  103. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
  104. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  105. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
  106. package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
  107. package/dist/apps/shared/components/calendar/RequestChangeForm.js +138 -0
  108. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
  109. package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
  110. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
  111. package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
  112. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
  113. package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
  114. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
  115. package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
  116. package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
  117. package/dist/apps/shared/components/calendar/eventForm.js +1 -0
  118. package/dist/apps/shared/components/calendar/eventFormat.d.ts +67 -0
  119. package/dist/apps/shared/components/calendar/eventFormat.js +163 -0
  120. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
  121. package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
  122. package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
  123. package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
  124. package/dist/apps/shared/components/layout/UserMenu.d.ts +10 -8
  125. package/dist/apps/shared/components/layout/UserMenu.js +70 -23
  126. package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
  127. package/dist/apps/shared/components/mail/ConversationList.js +21 -4
  128. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  129. package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
  130. package/dist/apps/shared/components/mail/InviteCard.js +150 -0
  131. package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
  132. package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
  133. package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
  134. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -3
  135. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
  136. package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
  137. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
  138. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
  139. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
  140. package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
  141. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
  142. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
  143. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
  144. package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
  145. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
  146. package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
  147. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +5 -0
  148. package/dist/apps/shared/components/mail/layout/MailShell.js +14 -3
  149. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  150. package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
  151. package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
  152. package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
  153. package/dist/apps/shared/gestures/useSwipe.js +90 -0
  154. package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
  155. package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
  156. package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
  157. package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
  158. package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
  159. package/dist/apps/shared/mail/newMailNotifications.js +3 -12
  160. package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
  161. package/dist/apps/shared/notifications/preferences.d.ts +13 -0
  162. package/dist/apps/shared/notifications/preferences.js +36 -0
  163. package/dist/apps/shared/notifications/store.d.ts +1 -0
  164. package/dist/apps/shared/notifications/store.js +7 -23
  165. package/dist/apps/www/_routes.js +1 -0
  166. package/dist/apps/www/calendar/index.js +76 -6
  167. package/dist/apps/www/index.js +140 -39
  168. package/dist/apps/www/messages/[uid].js +13 -5
  169. package/dist/apps/www/settings/profile/index.d.ts +4 -0
  170. package/dist/apps/www/settings/profile/index.js +124 -0
  171. package/package.json +2 -2
@@ -0,0 +1,137 @@
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 { useEffect, useState } from "react";
7
+ import { HiOutlineBars3BottomLeft, HiOutlineBell, HiOutlineBriefcase, HiOutlineCheck, HiOutlineClock, HiOutlineMapPin, HiOutlineUser, HiOutlineUserGroup, HiOutlineVideoCamera, HiOutlineXMark, } from "react-icons/hi2";
8
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
9
+ import { guestPermissionsOf, respondToEvent, visibilityOf } from "@rapidmx/react-shared/calendar/calendarApi.js";
10
+ import { htmlToPlainText } from "@rapidmx/react-shared/calendar/eventDescription.js";
11
+ import { deleteEventOccurrence, deleteEventSeries } from "@rapidmx/react-shared/calendar/calendarMutations.js";
12
+ import { describeRecurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
13
+ import { getVideoMeeting } from "@rapidmx/react-shared/videoconf/videoMeetingsApi.js";
14
+ import { deviceTimeZone } from "@rapidmx/react-shared/util/timeZone.js";
15
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
16
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
17
+ import { joinMeetingUrl } from "../../calendar/calendarReminders.js";
18
+ import EventDescriptionView from "./EventDescriptionView.js";
19
+ import RequestChangeForm from "./RequestChangeForm.js";
20
+ import { BUSY_STATUS_LABEL, RESPONSE_STATUS_LABEL, VISIBILITY_LABEL, describeGuestPermissions, describeReminder, formatStoredWhen } from "./eventFormat.js";
21
+ const ROLE_LABEL = { required: "", optional: "Optional", resource: "Room/equipment" };
22
+ function DetailRow({ icon, children }) {
23
+ return (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted mt-0.5", children: icon }), _jsx("div", { className: "flex-1 min-w-0 break-words", children: children })] }));
24
+ }
25
+ /**
26
+ * What clicking an existing event shows first: everything the event says, read-only. The reader who organized it (or whose event has no
27
+ * other organizer) can Modify or Delete it; one who was invited can answer (Accept, Tentative, Decline) or remove it from their own
28
+ * calendar, but not change it - the organizer's next update would overwrite that anyway. When the organizer allows guests to ask for changes (or for
29
+ * guests to be added) the invited reader can "Request a change" / "Add guests": the request goes to the organizer (`RequestChangeForm`) and nothing here
30
+ * changes until their update arrives. When the organizer hides the guest list the reader sees only themselves and is told so.
31
+ *
32
+ * An event this reader may only see as a busy block (`occurrence.redacted`: a private or confidential event on a shared calendar) is drawn as just that -
33
+ * its time and that it is busy - with nothing to change, delete or answer.
34
+ */
35
+ export default function EventDetails({ occurrence, isInvited, myResponse, isOwnAddress, calendarName, calendarColor, onClose, onModify, onSaved, onDeleted, }) {
36
+ const [error, setError] = useState(null);
37
+ const [busy, setBusy] = useState(false);
38
+ const [confirmingDelete, setConfirmingDelete] = useState(false);
39
+ const [requesting, setRequesting] = useState(false);
40
+ // The organizer's own join link for the event's video meeting: `undefined` while it is fetched, `null` once known to be none.
41
+ const [organizerJoinUrl, setOrganizerJoinUrl] = useState(undefined);
42
+ const videoMeetingUid = occurrence.videoMeetingUid;
43
+ // The link lives on the meeting, not on the event, so it is fetched once here. A failure and a meeting with no link at all are the
44
+ // same thing to this view - there is nothing to open either way - so both settle on `null`.
45
+ useEffect(() => {
46
+ if (!videoMeetingUid) {
47
+ return;
48
+ }
49
+ let cancelled = false;
50
+ void getVideoMeeting(videoMeetingUid)
51
+ .then((meeting) => meeting.organizerJoinUrl ?? null)
52
+ .catch(() => null)
53
+ .then((url) => {
54
+ if (!cancelled) {
55
+ setOrganizerJoinUrl(url);
56
+ }
57
+ });
58
+ return () => {
59
+ cancelled = true;
60
+ };
61
+ }, [videoMeetingUid]);
62
+ // The same scheme allow-list a reminder's own location gets: a hostile or malformed join link never reaches `window.open()`.
63
+ const joinUrl = joinMeetingUrl(organizerJoinUrl);
64
+ async function handleDelete(scope) {
65
+ setError(null);
66
+ setBusy(true);
67
+ try {
68
+ if (occurrence.isRecurringOccurrence && scope === "occurrence") {
69
+ await deleteEventOccurrence(occurrence);
70
+ }
71
+ else {
72
+ await deleteEventSeries(occurrence);
73
+ }
74
+ onDeleted();
75
+ }
76
+ catch (err) {
77
+ setError(err instanceof ApiRequestError ? err.message : "Could not delete this event.");
78
+ setBusy(false);
79
+ }
80
+ }
81
+ // `@rapidmx/restapi`'s `respond()` route has no occurrence-vs-series scope of its own (see `calendarApi.ts`'s `respondToEvent` doc
82
+ // comment) - it always acts on `occurrence.uid` as a single event document.
83
+ async function handleRespond(responseStatus) {
84
+ setError(null);
85
+ setBusy(true);
86
+ try {
87
+ await respondToEvent(occurrence.uid, responseStatus);
88
+ // A decline soft-deletes the mailbox's own copy server-side - treat it the same as a
89
+ // delete rather than a save so the calendar view drops it immediately.
90
+ if (responseStatus === "declined") {
91
+ onDeleted();
92
+ }
93
+ else {
94
+ onSaved();
95
+ }
96
+ }
97
+ catch (err) {
98
+ setError(err instanceof ApiRequestError ? err.message : "Could not send your response.");
99
+ setBusy(false);
100
+ }
101
+ }
102
+ if (occurrence.redacted) {
103
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-start gap-3 px-5 pt-4", children: [_jsx("span", { "aria-hidden": "true", className: "w-3.5 h-3.5 rounded-full shrink-0 mt-2", style: { backgroundColor: calendarColor } }), _jsx("h2", { className: "flex-1 min-w-0 text-xl font-semibold break-words", children: "Busy" }), _jsx("button", { type: "button", "aria-label": "Close details", onClick: onClose, className: "w-8 h-8 shrink-0 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: _jsx(HiOutlineXMark, { size: 20, "aria-hidden": "true" }) })] }), _jsxs("div", { className: "flex flex-col gap-3 px-5 pt-3 pb-4 text-sm", children: [_jsxs(DetailRow, { icon: _jsx(HiOutlineClock, { size: 18 }), children: [_jsx("div", { children: formatStoredWhen(occurrence.startDate, occurrence.endDate, occurrence.allDay) }), occurrence.recurrenceRule && _jsxs("div", { className: "text-text-muted", children: ["Repeats ", describeRecurrence(occurrence.recurrenceRule)] })] }), _jsx("p", { className: "text-xs text-text-muted", children: "This time is busy. The event\u2019s details are private, so only its time is shown." })] }), _jsx("div", { className: "flex px-5 pb-4", children: _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto ml-auto", onClick: onClose, children: "Close" }) })] }));
104
+ }
105
+ const permissions = guestPermissionsOf(occurrence);
106
+ // The organizer hid the guest list: this mailbox's own copy lists only itself (the server sends nothing more), and that is all that is shown.
107
+ const hiddenList = isInvited && !permissions.guestsCanSeeGuestList;
108
+ const allGuests = occurrence.attendees.filter((a) => !a.isOrganizer);
109
+ const guests = hiddenList ? allGuests.filter((a) => !!isOwnAddress?.(a.address)) : allGuests;
110
+ const hasDescription = htmlToPlainText(occurrence.descriptionHtml) !== "" || !!occurrence.description?.trim();
111
+ // An invited reader may ask the organizer for what the organizer allows.
112
+ const askable = isInvited && myResponse !== undefined;
113
+ const canChange = askable && permissions.guestsCanModify;
114
+ const canInvite = askable && permissions.guestsCanInviteOthers;
115
+ const organizerName = occurrence.organizer.displayName;
116
+ const eventZone = occurrence.timezone;
117
+ const answers = [
118
+ { response: "accepted", label: "Accept" },
119
+ { response: "tentative", label: "Tentative" },
120
+ { response: "declined", label: "Decline" },
121
+ ];
122
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-start gap-3 px-5 pt-4", children: [_jsx("span", { "aria-hidden": "true", className: "w-3.5 h-3.5 rounded-full shrink-0 mt-2", style: { backgroundColor: calendarColor } }), _jsx("h2", { className: "flex-1 min-w-0 text-xl font-semibold break-words", children: occurrence.title || "(no title)" }), _jsx("button", { type: "button", "aria-label": "Close details", onClick: onClose, className: "w-8 h-8 shrink-0 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: _jsx(HiOutlineXMark, { size: 20, "aria-hidden": "true" }) })] }), _jsxs("div", { className: "flex flex-col gap-3 px-5 pt-3 pb-4 text-sm", children: [error && _jsx(Alert, { children: error }), _jsxs(DetailRow, { icon: _jsx(HiOutlineClock, { size: 18 }), children: [_jsx("div", { children: formatStoredWhen(occurrence.startDate, occurrence.endDate, occurrence.allDay) }), occurrence.recurrenceRule && _jsxs("div", { className: "text-text-muted", children: ["Repeats ", describeRecurrence(occurrence.recurrenceRule)] }), !occurrence.allDay && eventZone && eventZone !== deviceTimeZone() && (_jsxs("div", { className: "text-text-muted", children: ["Event time zone: ", eventZone.replace(/_/g, " ")] }))] }), occurrence.location && (_jsx(DetailRow, { icon: _jsx(HiOutlineMapPin, { size: 18 }), children: _jsx("div", { children: occurrence.location }) })), hasDescription && (_jsx(DetailRow, { icon: _jsx(HiOutlineBars3BottomLeft, { size: 18 }), children: _jsx(EventDescriptionView, { html: occurrence.descriptionHtml, text: occurrence.description }) })), videoMeetingUid && (_jsx(DetailRow, { icon: _jsx(HiOutlineVideoCamera, { size: 18 }), children: _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: !joinUrl, onClick: () => window.open(joinUrl, "_blank", "noopener,noreferrer"), children: "Join video call" }), !joinUrl && (_jsx("span", { className: "text-xs text-text-muted", children: organizerJoinUrl === undefined ? "Loading the join link…" : "This meeting’s join link isn’t available." }))] }) })), _jsxs(DetailRow, { icon: _jsx(HiOutlineUser, { size: 18 }), children: [_jsx("div", { children: organizerName || occurrence.organizer.address }), _jsxs("div", { className: "text-text-muted", children: [organizerName ? `${occurrence.organizer.address} · ` : "", "Organizer"] })] }), (guests.length > 0 || hiddenList) && (_jsxs(DetailRow, { icon: _jsx(HiOutlineUserGroup, { size: 18 }), children: [_jsx("div", { className: "font-medium mb-1", children: hiddenList ? "The organizer has hidden the guest list" : guests.length === 1 ? "1 guest" : `${guests.length} guests` }), _jsx("ul", { className: "flex flex-col gap-1", children: guests.map((guest, i) => (_jsxs("li", { className: "flex items-baseline justify-between gap-3", children: [_jsxs("span", { className: "min-w-0 truncate", children: [guest.displayName || guest.address, ROLE_LABEL[guest.role] && _jsxs("span", { className: "text-text-muted", children: [" (", ROLE_LABEL[guest.role], ")"] })] }), _jsx("span", { className: "shrink-0 text-xs text-text-muted", children: RESPONSE_STATUS_LABEL[guest.responseStatus] })] }, i))) }), !isInvited && _jsx("div", { className: "text-xs text-text-muted mt-1", children: describeGuestPermissions(permissions) })] })), _jsxs(DetailRow, { icon: _jsx(HiOutlineBriefcase, { size: 18 }), children: [_jsx("div", { children: BUSY_STATUS_LABEL[occurrence.busyStatus] }), _jsx("div", { className: "text-text-muted", children: VISIBILITY_LABEL[visibilityOf(occurrence)] }), occurrence.autoReplyEnabled && (_jsx("div", { className: "text-text-muted", children: "Sends an automatic reply while this event is happening" }))] }), _jsx(DetailRow, { icon: _jsx(HiOutlineBell, { size: 18 }), children: _jsx("div", { children: describeReminder(occurrence.reminderMinutesBeforeStart) }) }), calendarName && (_jsxs("div", { className: "flex items-center gap-3 text-text-muted", children: [_jsx("span", { "aria-hidden": "true", className: "w-3 h-3 ml-[3px] rounded-full shrink-0", style: { backgroundColor: calendarColor } }), _jsx("span", { className: "truncate", children: calendarName })] })), isInvited && (_jsxs("p", { className: "text-xs text-text-muted", children: ["You were invited to this event. Only the organizer can change its details", canChange || canInvite ? ", but you can ask them to." : "."] }))] }), requesting && (_jsx("div", { className: "mx-5 mb-3 p-3 rounded-md bg-surface-alt", children: _jsx(RequestChangeForm, { subject: {
123
+ uid: occurrence.uid,
124
+ title: occurrence.title,
125
+ location: occurrence.location,
126
+ startDate: occurrence.startDate,
127
+ endDate: occurrence.endDate,
128
+ allDay: occurrence.allDay,
129
+ recurring: occurrence.isRecurringOccurrence,
130
+ description: occurrence.description,
131
+ descriptionHtml: occurrence.descriptionHtml,
132
+ guestAddresses: occurrence.attendees.map((a) => a.address),
133
+ }, canChange: canChange, canInvite: canInvite, onSent: () => setRequesting(false), onCancel: () => setRequesting(false) }) })), myResponse !== undefined && (_jsxs("div", { className: "flex flex-col gap-2 mx-5 mb-3 p-3 rounded-md bg-surface-alt", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium", children: "Your response" }), _jsx("div", { className: "flex gap-2", children: answers.map(({ response, label }) => {
134
+ const current = myResponse === response;
135
+ return (_jsxs(Button, { type: "button", variant: "secondary", className: ["!w-auto", response === "declined" ? "text-danger" : "", current ? "!bg-primary/10 !border-primary" : ""].join(" "), "aria-pressed": current, disabled: busy, onClick: () => void handleRespond(response), children: [current && _jsx(HiOutlineCheck, { size: 14, "aria-hidden": "true" }), label] }, response));
136
+ }) })] }), _jsx("p", { className: "text-xs text-text-muted", children: "Other attendees\u2019 responses may take a few minutes to update." })] })), _jsxs("div", { className: "flex flex-wrap items-center gap-3 px-5 pb-4", children: [!isInvited && (_jsx(Button, { type: "button", className: "!w-auto", disabled: busy, onClick: onModify, children: "Modify" })), (canChange || canInvite) && !requesting && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: () => setRequesting(true), children: canChange ? "Request a change" : "Add guests" })), !occurrence.isRecurringOccurrence ? (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("series"), children: "Delete" })) : !confirmingDelete ? (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => setConfirmingDelete(true), children: "Delete" })) : (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("occurrence"), children: "Delete this event" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("series"), children: "Delete series" })] })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto ml-auto", onClick: onClose, children: "Close" })] })] }));
137
+ }
@@ -0,0 +1,59 @@
1
+ import React, { MutableRefObject } from "react";
2
+ import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
3
+ import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
4
+ import { QuickCreateConfig } from "./QuickCreateFaces.js";
5
+ /**
6
+ * The Location text this modal writes when it mints a video meeting for an event, and the one value it will
7
+ * clear again when video conferencing is turned back off (see `applyVideoConferencing()`).
8
+ *
9
+ * Deliberately generic and identical for everyone: each attendee's own personal join link is substituted into
10
+ * their own copy of the invitation server-side (`@rapidmx/restapi`'s `MeetingSchedulingJob`), so no link -
11
+ * personal or otherwise - may be stored on the shared event itself, where every attendee would read the same
12
+ * one.
13
+ */
14
+ export declare const VIDEO_LOCATION_PLACEHOLDER = "Video call \u2014 link in this invitation";
15
+ export interface EventEditorProps {
16
+ /** The quick-create popover's face of the form, or the full form's (only a new event, which has `quickCreate`, has the quick one). Both show the same values. */
17
+ layout: "quick" | "expanded";
18
+ /** "More options": the quick face asks to become the full one. */
19
+ onExpand: () => void;
20
+ /** Leave without saving (nothing was stored). */
21
+ onCancel: () => void;
22
+ /** Something was stored: the dialog closes and the calendar reloads. */
23
+ onSaved: () => void;
24
+ /** A series save could not move every occurrence edited on its own with it (`detachedOccurrenceSyncFailed`). */
25
+ onSyncWarning: () => void;
26
+ /** Kept up to date with whether the form holds anything a stray click outside it would throw away. */
27
+ dirtyRef: MutableRefObject<boolean>;
28
+ mailboxUid: string;
29
+ folderUid: string;
30
+ calendars?: {
31
+ uid: string;
32
+ name: string;
33
+ }[];
34
+ mailboxOptions?: {
35
+ mailbox: Mailbox;
36
+ calendars: {
37
+ uid: string;
38
+ name: string;
39
+ }[];
40
+ }[];
41
+ /** Each calendar's colour, keyed by folder uid. */
42
+ folderColors?: Record<string, string>;
43
+ organizerAddress: string;
44
+ /** `null` when creating a new event. */
45
+ occurrence: CalendarOccurrence | null;
46
+ initialStart?: Date;
47
+ initialEnd?: Date;
48
+ /** A new event starting as an all-day one (a day cell of the month view was clicked). */
49
+ initialAllDay?: boolean;
50
+ /** A new event's tabs (Event, Task, Appointment schedule): with it the form is drawn by `QuickCreateFaces`; without it, as the full card. */
51
+ quickCreate?: QuickCreateConfig;
52
+ }
53
+ /**
54
+ * The event form and everything it does: create, and edit (one occurrence, or the entire series, of a recurring event - the choice
55
+ * Outlook's own convention offers for the same ambiguity, see `calendarMutations.ts`), plus video conferencing. It owns the form's
56
+ * one set of values and draws them as either the quick-create popover or the full card (`layout`), so growing the one into the other
57
+ * keeps what was typed.
58
+ */
59
+ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyncWarning, dirtyRef, mailboxUid, folderUid, calendars, mailboxOptions, folderColors, organizerAddress, occurrence, initialStart, initialEnd, initialAllDay, quickCreate, }: EventEditorProps): React.JSX.Element;