@rapidmx/web-client 0.4.0 → 0.5.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.
- package/apps/admin/audit-log/index.tsx +35 -8
- package/apps/admin/data-requests/index.tsx +480 -485
- package/apps/admin/distribution-lists/[uid].tsx +19 -1
- package/apps/admin/escrow-scopes/[uid].tsx +348 -237
- package/apps/admin/escrow-scopes/new/index.tsx +8 -2
- package/apps/admin/index.tsx +19 -1
- package/apps/admin/ingest-queue/index.tsx +44 -5
- package/apps/admin/mailboxes/[uid].tsx +211 -184
- package/apps/admin/quarantine/index.tsx +46 -14
- package/apps/admin/transport-rules/[uid].tsx +30 -1
- package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
- package/apps/admin/transport-rules/index.tsx +24 -6
- package/apps/admin/transport-rules/new/index.tsx +30 -1
- package/apps/escrow/_layout.tsx +2 -3
- package/apps/escrow/audit-log/index.tsx +196 -168
- package/apps/escrow/matters/[uid].tsx +621 -521
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +3 -1
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
- package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
- package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
- package/apps/shared/components/admin/signOut.ts +79 -0
- package/apps/shared/components/admin/usePagedList.tsx +129 -0
- package/apps/shared/components/calendar/EventModal.tsx +717 -549
- package/apps/shared/components/calendar/MonthView.tsx +173 -139
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
- package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
- package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
- package/apps/shared/components/calendar/allDay.ts +124 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
- package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
- package/apps/shared/components/contacts/ContactForm.tsx +377 -348
- package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
- package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
- package/apps/shared/components/contacts/contactKeys.ts +78 -0
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
- package/apps/shared/components/layout/AppShell.tsx +87 -15
- package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
- package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
- package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
- package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
- package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +12 -2
- package/apps/shared/components/mail/pinnedSigners.ts +124 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -0
- package/apps/shared/components/mail/writableMailboxes.ts +101 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
- package/apps/shared/mail/listAllPages.ts +39 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
- package/apps/shared/search/localIndexBuilder.ts +481 -179
- package/apps/shared/search/localIndexRpcClient.ts +228 -28
- package/apps/shared/search/localIndexSchema.ts +15 -6
- package/apps/shared/search/localIndexStorage.ts +98 -0
- package/apps/shared/search/localIndexVFS.ts +32 -1
- package/apps/shared/search/localIndexWorker.ts +648 -186
- package/apps/www/calendar/index.tsx +438 -417
- package/apps/www/contacts/[uid].tsx +21 -2
- package/apps/www/contacts/index.tsx +116 -12
- package/apps/www/index.tsx +1314 -1041
- package/apps/www/messages/[uid].tsx +18 -6
- package/apps/www/settings/auto-reply/index.tsx +134 -129
- package/apps/www/settings/booking-types/[uid].tsx +306 -300
- package/apps/www/settings/encryption/index.tsx +739 -216
- package/apps/www/settings/filters/[uid].tsx +171 -155
- package/apps/www/settings/filters/new/index.tsx +8 -1
- package/apps/www/settings/focused-inbox/index.tsx +150 -148
- package/apps/www/settings/privacy/index.tsx +494 -436
- package/apps/www/settings/read-receipts/index.tsx +148 -125
- package/apps/www/settings/sharing/index.tsx +17 -14
- package/apps/www/tasks/index.tsx +638 -579
- package/dist/apps/admin/audit-log/index.js +38 -8
- package/dist/apps/admin/data-requests/index.js +52 -72
- package/dist/apps/admin/distribution-lists/[uid].js +1 -1
- package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
- package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
- package/dist/apps/admin/index.js +4 -1
- package/dist/apps/admin/ingest-queue/index.js +24 -6
- package/dist/apps/admin/mailboxes/[uid].js +20 -4
- package/dist/apps/admin/quarantine/index.js +20 -11
- package/dist/apps/admin/transport-rules/[uid].js +18 -2
- package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
- package/dist/apps/admin/transport-rules/index.js +22 -6
- package/dist/apps/admin/transport-rules/new/index.js +18 -2
- package/dist/apps/escrow/_layout.js +3 -3
- package/dist/apps/escrow/audit-log/index.js +26 -1
- package/dist/apps/escrow/matters/[uid].js +77 -42
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.js +3 -1
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
- package/dist/apps/shared/components/admin/signOut.js +75 -0
- package/dist/apps/shared/components/admin/usePagedList.js +99 -0
- package/dist/apps/shared/components/calendar/EventModal.js +142 -21
- package/dist/apps/shared/components/calendar/MonthView.js +12 -5
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
- package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
- package/dist/apps/shared/components/calendar/allDay.js +111 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
- package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
- package/dist/apps/shared/components/layout/AppShell.js +83 -14
- package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
- package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +12 -3
- package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
- package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
- package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
- package/dist/apps/shared/mail/listAllPages.js +25 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
- package/dist/apps/shared/search/localIndexBuilder.js +292 -37
- package/dist/apps/shared/search/localIndexRpcClient.js +188 -25
- package/dist/apps/shared/search/localIndexSchema.js +12 -6
- package/dist/apps/shared/search/localIndexStorage.js +86 -0
- package/dist/apps/shared/search/localIndexVFS.js +68 -40
- package/dist/apps/shared/search/localIndexWorker.js +519 -173
- package/dist/apps/www/calendar/index.js +28 -10
- package/dist/apps/www/contacts/[uid].js +13 -2
- package/dist/apps/www/contacts/index.js +80 -10
- package/dist/apps/www/index.js +339 -108
- package/dist/apps/www/messages/[uid].js +18 -6
- package/dist/apps/www/settings/auto-reply/index.js +9 -4
- package/dist/apps/www/settings/booking-types/[uid].js +17 -12
- package/dist/apps/www/settings/encryption/index.js +514 -97
- package/dist/apps/www/settings/filters/[uid].js +22 -7
- package/dist/apps/www/settings/filters/new/index.js +8 -2
- package/dist/apps/www/settings/focused-inbox/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +57 -22
- package/dist/apps/www/settings/read-receipts/index.js +10 -3
- package/dist/apps/www/settings/sharing/index.js +7 -11
- package/dist/apps/www/tasks/index.js +50 -10
- package/package.json +2 -2
|
@@ -1,300 +1,306 @@
|
|
|
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 {
|
|
8
|
-
BookingAvailabilityWindow,
|
|
9
|
-
BookingType,
|
|
10
|
-
deleteBookingType,
|
|
11
|
-
getBookingType,
|
|
12
|
-
updateBookingType,
|
|
13
|
-
} from "@rapidmx/react-shared/booking/bookingApi.js";
|
|
14
|
-
import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
15
|
-
import AvailabilityEditor from "../../../shared/components/booking/AvailabilityEditor.js";
|
|
16
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
17
|
-
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
18
|
-
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
19
|
-
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
20
|
-
|
|
21
|
-
const INPUT_CLASS =
|
|
22
|
-
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
23
|
-
|
|
24
|
-
export type BookingTypeDetailPageProps = Omit<SettingsShellProps, "active"> & { params: { uid: string } };
|
|
25
|
-
|
|
26
|
-
export default function BookingTypeDetailPage(props: BookingTypeDetailPageProps) {
|
|
27
|
-
return (
|
|
28
|
-
<SettingsShell {...props} active="booking-types">
|
|
29
|
-
<BookingTypeDetailContent uid={props.params.uid} />
|
|
30
|
-
</SettingsShell>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function BookingTypeDetailContent({ uid }: { uid: string }) {
|
|
35
|
-
const { mailboxUid } = useSettingsShell();
|
|
36
|
-
const [bookingType, setBookingType] = useState<BookingType | null>(null);
|
|
37
|
-
const [name, setName] = useState("");
|
|
38
|
-
const [description, setDescription] = useState("");
|
|
39
|
-
const [hostDisplayName, setHostDisplayName] = useState("");
|
|
40
|
-
const [durationMinutes, setDurationMinutes] = useState(30);
|
|
41
|
-
const [timezone, setTimezone] = useState("");
|
|
42
|
-
const [availability, setAvailability] = useState<BookingAvailabilityWindow[]>([]);
|
|
43
|
-
const [minimumNoticeMinutes, setMinimumNoticeMinutes] = useState(60);
|
|
44
|
-
const [bookingWindowDays, setBookingWindowDays] = useState(30);
|
|
45
|
-
const [requiresApproval, setRequiresApproval] = useState(false);
|
|
46
|
-
const [enabled, setEnabled] = useState(true);
|
|
47
|
-
const [loading, setLoading] = useState(true);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
className="
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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 {
|
|
8
|
+
BookingAvailabilityWindow,
|
|
9
|
+
BookingType,
|
|
10
|
+
deleteBookingType,
|
|
11
|
+
getBookingType,
|
|
12
|
+
updateBookingType,
|
|
13
|
+
} from "@rapidmx/react-shared/booking/bookingApi.js";
|
|
14
|
+
import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
15
|
+
import AvailabilityEditor from "../../../shared/components/booking/AvailabilityEditor.js";
|
|
16
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
17
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
18
|
+
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
19
|
+
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
20
|
+
|
|
21
|
+
const INPUT_CLASS =
|
|
22
|
+
"w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
23
|
+
|
|
24
|
+
export type BookingTypeDetailPageProps = Omit<SettingsShellProps, "active"> & { params: { uid: string } };
|
|
25
|
+
|
|
26
|
+
export default function BookingTypeDetailPage(props: BookingTypeDetailPageProps) {
|
|
27
|
+
return (
|
|
28
|
+
<SettingsShell {...props} active="booking-types">
|
|
29
|
+
<BookingTypeDetailContent uid={props.params.uid} />
|
|
30
|
+
</SettingsShell>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function BookingTypeDetailContent({ uid }: { uid: string }) {
|
|
35
|
+
const { mailboxUid } = useSettingsShell();
|
|
36
|
+
const [bookingType, setBookingType] = useState<BookingType | null>(null);
|
|
37
|
+
const [name, setName] = useState("");
|
|
38
|
+
const [description, setDescription] = useState("");
|
|
39
|
+
const [hostDisplayName, setHostDisplayName] = useState("");
|
|
40
|
+
const [durationMinutes, setDurationMinutes] = useState(30);
|
|
41
|
+
const [timezone, setTimezone] = useState("");
|
|
42
|
+
const [availability, setAvailability] = useState<BookingAvailabilityWindow[]>([]);
|
|
43
|
+
const [minimumNoticeMinutes, setMinimumNoticeMinutes] = useState(60);
|
|
44
|
+
const [bookingWindowDays, setBookingWindowDays] = useState(30);
|
|
45
|
+
const [requiresApproval, setRequiresApproval] = useState(false);
|
|
46
|
+
const [enabled, setEnabled] = useState(true);
|
|
47
|
+
const [loading, setLoading] = useState(true);
|
|
48
|
+
// Split so a failed save keeps the form (and the user's edits) on screen - only a failed *load* has
|
|
49
|
+
// nothing to show but the error.
|
|
50
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
51
|
+
const [saveError, setSaveError] = useState<string | null>(null);
|
|
52
|
+
const [saving, setSaving] = useState(false);
|
|
53
|
+
const [saved, setSaved] = useState(false);
|
|
54
|
+
const [copied, setCopied] = useState(false);
|
|
55
|
+
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
56
|
+
const [deleting, setDeleting] = useState(false);
|
|
57
|
+
const [deleteError, setDeleteError] = useState<string | null>(null);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setLoading(true);
|
|
61
|
+
setLoadError(null);
|
|
62
|
+
getBookingType(uid)
|
|
63
|
+
.then((bt) => {
|
|
64
|
+
setBookingType(bt);
|
|
65
|
+
setName(bt.name);
|
|
66
|
+
setDescription(bt.description ?? "");
|
|
67
|
+
setHostDisplayName(bt.hostDisplayName);
|
|
68
|
+
setDurationMinutes(bt.durationMinutes);
|
|
69
|
+
setTimezone(bt.timezone);
|
|
70
|
+
setAvailability(bt.availability);
|
|
71
|
+
setMinimumNoticeMinutes(bt.minimumNoticeMinutes);
|
|
72
|
+
setBookingWindowDays(bt.bookingWindowDays);
|
|
73
|
+
setRequiresApproval(bt.requiresApproval);
|
|
74
|
+
setEnabled(bt.enabled);
|
|
75
|
+
})
|
|
76
|
+
.catch((err) => setLoadError(err instanceof ApiRequestError ? err.message : "Could not load this booking link."))
|
|
77
|
+
.finally(() => setLoading(false));
|
|
78
|
+
}, [uid]);
|
|
79
|
+
|
|
80
|
+
async function handleSubmit(e: FormEvent) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
setSaveError(null);
|
|
83
|
+
setSaved(false);
|
|
84
|
+
setSaving(true);
|
|
85
|
+
try {
|
|
86
|
+
const updated = await updateBookingType({
|
|
87
|
+
uid: bookingType!.uid,
|
|
88
|
+
version: bookingType!.version,
|
|
89
|
+
name,
|
|
90
|
+
// `null` (not omitted) clears a saved description - an omitted field is left unchanged by the
|
|
91
|
+
// server's partial update. `UpdateBookingTypeInput` doesn't declare `null` yet, hence the cast.
|
|
92
|
+
description: description.trim() || (null as unknown as undefined),
|
|
93
|
+
hostDisplayName,
|
|
94
|
+
durationMinutes,
|
|
95
|
+
timezone,
|
|
96
|
+
availability,
|
|
97
|
+
minimumNoticeMinutes,
|
|
98
|
+
bookingWindowDays,
|
|
99
|
+
requiresApproval,
|
|
100
|
+
enabled,
|
|
101
|
+
});
|
|
102
|
+
setBookingType(updated);
|
|
103
|
+
setSaved(true);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
setSaveError(err instanceof ApiRequestError ? err.message : "Could not save this booking link.");
|
|
106
|
+
} finally {
|
|
107
|
+
setSaving(false);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function handleCopy(value: string) {
|
|
112
|
+
try {
|
|
113
|
+
await navigator.clipboard.writeText(value);
|
|
114
|
+
setCopied(true);
|
|
115
|
+
setTimeout(() => setCopied(false), 2000);
|
|
116
|
+
} catch {
|
|
117
|
+
// Clipboard access can be denied by the browser — the value is still selectable/copyable by hand.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handleDelete() {
|
|
122
|
+
setDeleting(true);
|
|
123
|
+
setDeleteError(null);
|
|
124
|
+
try {
|
|
125
|
+
await deleteBookingType(bookingType!.uid, bookingType!.version);
|
|
126
|
+
window.location.href = `/settings/booking-types?mailboxUid=${encodeURIComponent(mailboxUid!)}`;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this booking link.");
|
|
129
|
+
setDeleting(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (loading) {
|
|
134
|
+
return <p className="p-6 text-sm text-text-muted">Loading…</p>;
|
|
135
|
+
}
|
|
136
|
+
if (loadError || !bookingType) {
|
|
137
|
+
// `loadError` is always set whenever `bookingType` is falsy here: the load effect above destructures
|
|
138
|
+
// every field off the resolved value directly (`bt.name`, `bt.hostDisplayName`, ...), so a
|
|
139
|
+
// successful-but-empty response throws into `.catch()` — which sets `loadError` — before this
|
|
140
|
+
// component ever renders past the `loading` guard above. `!bookingType` alone (with `loadError` still
|
|
141
|
+
// `null`) is therefore unreachable, unlike e.g. `messages/[uid].tsx`'s identical-looking check,
|
|
142
|
+
// which stores its fetched value whole rather than destructuring it.
|
|
143
|
+
return (
|
|
144
|
+
<div className="p-6">
|
|
145
|
+
<Alert>{loadError!}</Alert>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// `bookingType` only ever becomes non-null via the client-only fetch above, so by the time this line
|
|
151
|
+
// runs `window` is always defined — the `!bookingType` early return just above covers every real SSR
|
|
152
|
+
// pass, where `bookingType` is still `null`.
|
|
153
|
+
const publicUrl = `${window.location.origin}/book/${encodeURIComponent(bookingType.slug)}`;
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="flex-1 min-w-0 overflow-y-auto p-6">
|
|
157
|
+
<div className="max-w-2xl">
|
|
158
|
+
<div className="flex items-start justify-between gap-4 mb-1">
|
|
159
|
+
<div>
|
|
160
|
+
<a
|
|
161
|
+
href={`/settings/booking-types?mailboxUid=${encodeURIComponent(mailboxUid!)}`}
|
|
162
|
+
className="text-sm text-primary-dark hover:underline"
|
|
163
|
+
>
|
|
164
|
+
← All booking links
|
|
165
|
+
</a>
|
|
166
|
+
<h1 className="text-xl font-bold tracking-tight mt-1">{bookingType.name}</h1>
|
|
167
|
+
</div>
|
|
168
|
+
<Button
|
|
169
|
+
type="button"
|
|
170
|
+
variant="secondary"
|
|
171
|
+
className="!w-auto shrink-0 !border-danger !text-danger hover:!border-danger hover:!text-danger"
|
|
172
|
+
onClick={() => setConfirmingDelete(true)}
|
|
173
|
+
>
|
|
174
|
+
Delete
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className="flex items-center gap-2 mb-5">
|
|
179
|
+
<code className="flex-1 text-xs bg-surface-alt border border-border rounded-sm py-2 px-3 overflow-x-auto whitespace-nowrap">
|
|
180
|
+
{publicUrl}
|
|
181
|
+
</code>
|
|
182
|
+
<Button type="button" variant="secondary" className="!w-auto shrink-0" onClick={() => handleCopy(publicUrl)}>
|
|
183
|
+
{copied ? "Copied" : "Copy"}
|
|
184
|
+
</Button>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
{saveError && <Alert>{saveError}</Alert>}
|
|
188
|
+
{saved && !saveError && <div className="mb-4 text-sm text-success font-medium">Saved.</div>}
|
|
189
|
+
|
|
190
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-1">
|
|
191
|
+
<label className="flex items-center gap-2 text-sm mb-3">
|
|
192
|
+
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
|
|
193
|
+
Enabled (publicly bookable)
|
|
194
|
+
</label>
|
|
195
|
+
<FormField label="Name" htmlFor="name">
|
|
196
|
+
<input id="name" type="text" className={INPUT_CLASS} value={name} onChange={(e) => setName(e.target.value)} />
|
|
197
|
+
</FormField>
|
|
198
|
+
<FormField label="Description (optional)" htmlFor="description">
|
|
199
|
+
<textarea
|
|
200
|
+
id="description"
|
|
201
|
+
className={INPUT_CLASS}
|
|
202
|
+
rows={2}
|
|
203
|
+
value={description}
|
|
204
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
205
|
+
/>
|
|
206
|
+
</FormField>
|
|
207
|
+
<FormField label="Host name shown to visitors" htmlFor="hostDisplayName">
|
|
208
|
+
<input
|
|
209
|
+
id="hostDisplayName"
|
|
210
|
+
type="text"
|
|
211
|
+
className={INPUT_CLASS}
|
|
212
|
+
value={hostDisplayName}
|
|
213
|
+
onChange={(e) => setHostDisplayName(e.target.value)}
|
|
214
|
+
/>
|
|
215
|
+
</FormField>
|
|
216
|
+
<div className="grid grid-cols-2 gap-3">
|
|
217
|
+
<FormField label="Duration (minutes)" htmlFor="durationMinutes">
|
|
218
|
+
<input
|
|
219
|
+
id="durationMinutes"
|
|
220
|
+
type="number"
|
|
221
|
+
min={1}
|
|
222
|
+
className={INPUT_CLASS}
|
|
223
|
+
value={durationMinutes}
|
|
224
|
+
onChange={(e) => setDurationMinutes(Number(e.target.value))}
|
|
225
|
+
/>
|
|
226
|
+
</FormField>
|
|
227
|
+
<FormField label="Timezone" htmlFor="timezone">
|
|
228
|
+
<input
|
|
229
|
+
id="timezone"
|
|
230
|
+
type="text"
|
|
231
|
+
className={INPUT_CLASS}
|
|
232
|
+
value={timezone}
|
|
233
|
+
onChange={(e) => setTimezone(e.target.value)}
|
|
234
|
+
/>
|
|
235
|
+
</FormField>
|
|
236
|
+
</div>
|
|
237
|
+
<div className="grid grid-cols-2 gap-3">
|
|
238
|
+
<FormField label="Minimum notice (minutes)" htmlFor="minimumNoticeMinutes">
|
|
239
|
+
<input
|
|
240
|
+
id="minimumNoticeMinutes"
|
|
241
|
+
type="number"
|
|
242
|
+
min={0}
|
|
243
|
+
className={INPUT_CLASS}
|
|
244
|
+
value={minimumNoticeMinutes}
|
|
245
|
+
onChange={(e) => setMinimumNoticeMinutes(Number(e.target.value))}
|
|
246
|
+
/>
|
|
247
|
+
</FormField>
|
|
248
|
+
<FormField label="Booking window (days ahead)" htmlFor="bookingWindowDays">
|
|
249
|
+
<input
|
|
250
|
+
id="bookingWindowDays"
|
|
251
|
+
type="number"
|
|
252
|
+
min={1}
|
|
253
|
+
className={INPUT_CLASS}
|
|
254
|
+
value={bookingWindowDays}
|
|
255
|
+
onChange={(e) => setBookingWindowDays(Number(e.target.value))}
|
|
256
|
+
/>
|
|
257
|
+
</FormField>
|
|
258
|
+
</div>
|
|
259
|
+
<label className="flex items-center gap-2 text-sm my-3">
|
|
260
|
+
<input type="checkbox" checked={requiresApproval} onChange={(e) => setRequiresApproval(e.target.checked)} />
|
|
261
|
+
Require my approval before confirming a booking
|
|
262
|
+
</label>
|
|
263
|
+
|
|
264
|
+
<div className="mb-4">
|
|
265
|
+
<span className="block text-sm font-semibold mb-1.5 text-text">Weekly availability</span>
|
|
266
|
+
<AvailabilityEditor value={availability} onChange={setAvailability} />
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div>
|
|
270
|
+
<Button type="submit" loading={saving} disabled={saving} className="!w-auto">
|
|
271
|
+
Save
|
|
272
|
+
</Button>
|
|
273
|
+
</div>
|
|
274
|
+
</form>
|
|
275
|
+
|
|
276
|
+
<Modal open={confirmingDelete} onClose={() => setConfirmingDelete(false)} title="Delete booking link">
|
|
277
|
+
<p className="text-sm mb-5">
|
|
278
|
+
Are you sure you want to delete <strong>{bookingType.name}</strong>? Its public link will stop
|
|
279
|
+
working immediately. This cannot be undone.
|
|
280
|
+
</p>
|
|
281
|
+
{deleteError && <Alert>{deleteError}</Alert>}
|
|
282
|
+
<div className="flex gap-3 justify-end mt-5">
|
|
283
|
+
<Button
|
|
284
|
+
type="button"
|
|
285
|
+
variant="secondary"
|
|
286
|
+
className="!w-auto"
|
|
287
|
+
disabled={deleting}
|
|
288
|
+
onClick={() => setConfirmingDelete(false)}
|
|
289
|
+
>
|
|
290
|
+
Cancel
|
|
291
|
+
</Button>
|
|
292
|
+
<Button
|
|
293
|
+
type="button"
|
|
294
|
+
className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger"
|
|
295
|
+
loading={deleting}
|
|
296
|
+
disabled={deleting}
|
|
297
|
+
onClick={handleDelete}
|
|
298
|
+
>
|
|
299
|
+
Delete
|
|
300
|
+
</Button>
|
|
301
|
+
</div>
|
|
302
|
+
</Modal>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
}
|