@blazeo.com/appointment-client 1.0.3 → 1.0.5
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/blazeo.com-appointment-client-1.0.5.tgz +0 -0
- package/dist/calendar/fetchCalendarDetails.d.ts +18 -0
- package/dist/calendar/fetchCalendarDetails.js +51 -0
- package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +21 -0
- package/dist/calendar/fetchCalendarWithOpeningHours.js +75 -0
- package/dist/config/initializeAppointmentClient.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +22 -39
- package/sample/index.html +13 -0
- package/sample/package-lock.json +1706 -0
- package/sample/package.json +19 -0
- package/sample/src/App2.jsx +148 -0
- package/sample/src/AvailabilityTab.jsx +83 -0
- package/sample/src/BlazeoConnectionSettings.jsx +118 -0
- package/sample/src/CalendarTab.jsx +37 -0
- package/sample/src/CreateCalendarTab.jsx +147 -0
- package/sample/src/EventTab.jsx +372 -0
- package/sample/src/FetchCalendarTab.jsx +545 -0
- package/sample/src/ParticipantTab.jsx +102 -0
- package/sample/src/main.jsx +19 -0
- package/sample/src/style.css +681 -0
- package/sample/src/vite-env.d.ts +12 -0
- package/sample/vite.config.js +47 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
cancelAppointmentEventAsync,
|
|
4
|
+
createAppointmentEventAsync,
|
|
5
|
+
EventModel,
|
|
6
|
+
rescheduleAppointmentEventAsync,
|
|
7
|
+
} from "appointment-client";
|
|
8
|
+
import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
|
|
9
|
+
import {
|
|
10
|
+
configureBlazeoFromEffective,
|
|
11
|
+
useBlazeoConnection,
|
|
12
|
+
} from "./BlazeoConnectionSettings.jsx";
|
|
13
|
+
|
|
14
|
+
function getExampleCreatePayload() {
|
|
15
|
+
const start = new Date();
|
|
16
|
+
start.setHours(10, 0, 0, 0);
|
|
17
|
+
const end = new Date(start);
|
|
18
|
+
end.setHours(10, 30, 0, 0);
|
|
19
|
+
return {
|
|
20
|
+
thirdPartyCalendarId: "your-calendar-id",
|
|
21
|
+
participantId: "00000000-0000-0000-0000-000000000000",
|
|
22
|
+
title: "Sample appointment",
|
|
23
|
+
description: "Created via appointment-client sample",
|
|
24
|
+
startDate: start.toISOString(),
|
|
25
|
+
endDate: end.toISOString(),
|
|
26
|
+
email: "visitor@example.com",
|
|
27
|
+
visitorName: "Visitor",
|
|
28
|
+
timeZone: "Pakistan Standard Time",
|
|
29
|
+
rescheduleUrl: "https://example.com/reschedule",
|
|
30
|
+
cancelUrl: "https://example.com/cancel",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getExampleReschedulePayload() {
|
|
35
|
+
const start = new Date();
|
|
36
|
+
start.setDate(start.getDate() + 1);
|
|
37
|
+
start.setHours(14, 0, 0, 0);
|
|
38
|
+
const end = new Date(start);
|
|
39
|
+
end.setHours(14, 45, 0, 0);
|
|
40
|
+
return {
|
|
41
|
+
thirdPartyAppointmentId: "existing-blazeo-event-id",
|
|
42
|
+
thirdPartyCalendarId: "your-calendar-id",
|
|
43
|
+
participantId: "00000000-0000-0000-0000-000000000000",
|
|
44
|
+
title: "Rescheduled title",
|
|
45
|
+
notes: "Reschedule body",
|
|
46
|
+
startDate: start.toISOString(),
|
|
47
|
+
endDate: end.toISOString(),
|
|
48
|
+
email: "visitor@example.com",
|
|
49
|
+
timeZone: "Pakistan Standard Time",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resultToJson(result) {
|
|
54
|
+
if (!result) return "";
|
|
55
|
+
if (result.ok && result.event && isStateTreeNode(result.event)) {
|
|
56
|
+
return JSON.stringify(
|
|
57
|
+
{
|
|
58
|
+
ok: true,
|
|
59
|
+
eventSnapshot: getSnapshot(result.event),
|
|
60
|
+
apiResponse: result.apiResponse ?? null,
|
|
61
|
+
},
|
|
62
|
+
null,
|
|
63
|
+
2
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return JSON.stringify(result, null, 2);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function safeJsonParse(text, fallback) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(text);
|
|
72
|
+
} catch {
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function EventTab() {
|
|
78
|
+
const { effective } = useBlazeoConnection();
|
|
79
|
+
const [offsetMinutes, setOffsetMinutes] = useState(-new Date().getTimezoneOffset());
|
|
80
|
+
const [createJson, setCreateJson] = useState(() =>
|
|
81
|
+
JSON.stringify(getExampleCreatePayload(), null, 2)
|
|
82
|
+
);
|
|
83
|
+
const [rescheduleJson, setRescheduleJson] = useState(() =>
|
|
84
|
+
JSON.stringify(getExampleReschedulePayload(), null, 2)
|
|
85
|
+
);
|
|
86
|
+
const [searchCompanyKey, setSearchCompanyKey] = useState("");
|
|
87
|
+
const [searchFrom, setSearchFrom] = useState(() => new Date().toISOString().slice(0, 10));
|
|
88
|
+
const [searchTo, setSearchTo] = useState(() => {
|
|
89
|
+
const d = new Date();
|
|
90
|
+
d.setDate(d.getDate() + 7);
|
|
91
|
+
return d.toISOString().slice(0, 10);
|
|
92
|
+
});
|
|
93
|
+
const [searchFiltersJson, setSearchFiltersJson] = useState(() =>
|
|
94
|
+
JSON.stringify(
|
|
95
|
+
{
|
|
96
|
+
calendarId: "",
|
|
97
|
+
participantId: "",
|
|
98
|
+
leadId: "",
|
|
99
|
+
visitorName: "",
|
|
100
|
+
visitorEmail: "",
|
|
101
|
+
visitorPhone: "",
|
|
102
|
+
title: "",
|
|
103
|
+
search: "",
|
|
104
|
+
attendeeStatus: "",
|
|
105
|
+
eventSource: "",
|
|
106
|
+
sort: "",
|
|
107
|
+
sortOrder: "desc",
|
|
108
|
+
page: 1,
|
|
109
|
+
page_size: 25,
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
2
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
const [cancelEventId, setCancelEventId] = useState("");
|
|
116
|
+
const [busy, setBusy] = useState(false);
|
|
117
|
+
const [error, setError] = useState("");
|
|
118
|
+
const [output, setOutput] = useState("");
|
|
119
|
+
|
|
120
|
+
const opts = useMemo(() => ({ offsetMinutes: Number(offsetMinutes) || 0 }), [offsetMinutes]);
|
|
121
|
+
|
|
122
|
+
function ensureBase() {
|
|
123
|
+
if (!effective.baseUrl) {
|
|
124
|
+
setError("Set Base URL in the connection card above.");
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function handleCreate(e) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
setError("");
|
|
133
|
+
setOutput("");
|
|
134
|
+
if (!ensureBase()) return;
|
|
135
|
+
configureBlazeoFromEffective(effective);
|
|
136
|
+
let payload;
|
|
137
|
+
try {
|
|
138
|
+
payload = JSON.parse(createJson);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
setError(`Create JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
setBusy(true);
|
|
144
|
+
try {
|
|
145
|
+
const result = await createAppointmentEventAsync(payload, opts);
|
|
146
|
+
setOutput(resultToJson(result));
|
|
147
|
+
if (!result.ok) setError(result.error);
|
|
148
|
+
} finally {
|
|
149
|
+
setBusy(false);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function handleReschedule(e) {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
setError("");
|
|
156
|
+
setOutput("");
|
|
157
|
+
if (!ensureBase()) return;
|
|
158
|
+
configureBlazeoFromEffective(effective);
|
|
159
|
+
let payload;
|
|
160
|
+
try {
|
|
161
|
+
payload = JSON.parse(rescheduleJson);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
setError(`Reschedule JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
setBusy(true);
|
|
167
|
+
try {
|
|
168
|
+
const result = await rescheduleAppointmentEventAsync(payload, opts);
|
|
169
|
+
setOutput(resultToJson(result));
|
|
170
|
+
if (!result.ok) setError(result.error);
|
|
171
|
+
} finally {
|
|
172
|
+
setBusy(false);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function handleCancel(e) {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
setError("");
|
|
179
|
+
setOutput("");
|
|
180
|
+
const id = cancelEventId.trim();
|
|
181
|
+
if (!id) return setError("Enter Blazeo event id to cancel.");
|
|
182
|
+
if (!ensureBase()) return;
|
|
183
|
+
configureBlazeoFromEffective(effective);
|
|
184
|
+
setBusy(true);
|
|
185
|
+
try {
|
|
186
|
+
const result = await cancelAppointmentEventAsync(id, {});
|
|
187
|
+
setOutput(JSON.stringify(result, null, 2));
|
|
188
|
+
if (!result.ok) setError(result.error);
|
|
189
|
+
} finally {
|
|
190
|
+
setBusy(false);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function handleSearchByDateRange(e) {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
setError("");
|
|
197
|
+
setOutput("");
|
|
198
|
+
const companyKey = searchCompanyKey.trim();
|
|
199
|
+
if (!companyKey) return setError("Enter company key.");
|
|
200
|
+
if (!searchFrom) return setError("Pick start date.");
|
|
201
|
+
if (!searchTo) return setError("Pick end date.");
|
|
202
|
+
if (!ensureBase()) return;
|
|
203
|
+
configureBlazeoFromEffective(effective);
|
|
204
|
+
|
|
205
|
+
const optsFromJson = safeJsonParse(searchFiltersJson, {});
|
|
206
|
+
const startDateFrom = new Date(`${searchFrom}T00:00:00.000Z`).toISOString();
|
|
207
|
+
const startDateTo = new Date(`${searchTo}T23:59:59.999Z`).toISOString();
|
|
208
|
+
|
|
209
|
+
setBusy(true);
|
|
210
|
+
try {
|
|
211
|
+
const res = await EventModel.getByDateRangeWithFilters(
|
|
212
|
+
companyKey,
|
|
213
|
+
startDateFrom,
|
|
214
|
+
startDateTo,
|
|
215
|
+
optsFromJson
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const events = (res?.events ?? []).map((e) =>
|
|
219
|
+
isStateTreeNode(e) ? getSnapshot(e) : (e?.toJSON?.() ?? e)
|
|
220
|
+
);
|
|
221
|
+
const totalCount = res?.totalCount ?? events.length;
|
|
222
|
+
setOutput(JSON.stringify({ totalCount, events }, null, 2));
|
|
223
|
+
} catch (err) {
|
|
224
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
225
|
+
} finally {
|
|
226
|
+
setBusy(false);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<>
|
|
232
|
+
<div className="card">
|
|
233
|
+
<h2>Appointment events</h2>
|
|
234
|
+
<p className="muted small">
|
|
235
|
+
Create/reschedule/cancel Blazeo events via <code>appointment-client</code>.
|
|
236
|
+
</p>
|
|
237
|
+
<label className="form__label">
|
|
238
|
+
<span>Offset minutes</span>
|
|
239
|
+
<input
|
|
240
|
+
type="number"
|
|
241
|
+
className="form__input"
|
|
242
|
+
value={offsetMinutes}
|
|
243
|
+
onChange={(e) => setOffsetMinutes(e.target.value)}
|
|
244
|
+
/>
|
|
245
|
+
</label>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div className="card">
|
|
249
|
+
<h2>Search events (date range + filters)</h2>
|
|
250
|
+
<p className="muted small">
|
|
251
|
+
Calls <code>EventModel.getByDateRangeWithFilters</code> →
|
|
252
|
+
<code> GET /event/search/daterange/get</code> (company scope). Offset header comes from the{" "}
|
|
253
|
+
<code>offset</code> field above.
|
|
254
|
+
</p>
|
|
255
|
+
<form onSubmit={handleSearchByDateRange} className="form">
|
|
256
|
+
<label className="form__label">
|
|
257
|
+
<span>Company key</span>
|
|
258
|
+
<input
|
|
259
|
+
className="form__input"
|
|
260
|
+
value={searchCompanyKey}
|
|
261
|
+
onChange={(e) => setSearchCompanyKey(e.target.value)}
|
|
262
|
+
placeholder="company_key"
|
|
263
|
+
autoComplete="off"
|
|
264
|
+
/>
|
|
265
|
+
</label>
|
|
266
|
+
<div className="connection-card__row">
|
|
267
|
+
<label className="form__label">
|
|
268
|
+
<span>Start date (from)</span>
|
|
269
|
+
<input
|
|
270
|
+
type="date"
|
|
271
|
+
className="form__input"
|
|
272
|
+
value={searchFrom}
|
|
273
|
+
onChange={(e) => setSearchFrom(e.target.value)}
|
|
274
|
+
/>
|
|
275
|
+
</label>
|
|
276
|
+
<label className="form__label">
|
|
277
|
+
<span>Start date (to)</span>
|
|
278
|
+
<input
|
|
279
|
+
type="date"
|
|
280
|
+
className="form__input"
|
|
281
|
+
value={searchTo}
|
|
282
|
+
onChange={(e) => setSearchTo(e.target.value)}
|
|
283
|
+
/>
|
|
284
|
+
</label>
|
|
285
|
+
</div>
|
|
286
|
+
<label className="form__label">
|
|
287
|
+
<span>Filters (JSON)</span>
|
|
288
|
+
<textarea
|
|
289
|
+
className="form__textarea"
|
|
290
|
+
value={searchFiltersJson}
|
|
291
|
+
onChange={(e) => setSearchFiltersJson(e.target.value)}
|
|
292
|
+
spellCheck={false}
|
|
293
|
+
rows={10}
|
|
294
|
+
/>
|
|
295
|
+
</label>
|
|
296
|
+
<button type="submit" className="btn btn--secondary" disabled={busy}>
|
|
297
|
+
{busy ? "Loading…" : "Search"}
|
|
298
|
+
</button>
|
|
299
|
+
</form>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div className="card">
|
|
303
|
+
<h2>Create event</h2>
|
|
304
|
+
<form onSubmit={handleCreate} className="form">
|
|
305
|
+
<label className="form__label">
|
|
306
|
+
<span>Payload (JSON)</span>
|
|
307
|
+
<textarea
|
|
308
|
+
className="form__textarea"
|
|
309
|
+
value={createJson}
|
|
310
|
+
onChange={(e) => setCreateJson(e.target.value)}
|
|
311
|
+
spellCheck={false}
|
|
312
|
+
rows={14}
|
|
313
|
+
/>
|
|
314
|
+
</label>
|
|
315
|
+
<button type="submit" className="btn btn--primary" disabled={busy}>
|
|
316
|
+
{busy ? "Working…" : "Create"}
|
|
317
|
+
</button>
|
|
318
|
+
</form>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div className="card">
|
|
322
|
+
<h2>Reschedule event</h2>
|
|
323
|
+
<form onSubmit={handleReschedule} className="form">
|
|
324
|
+
<label className="form__label">
|
|
325
|
+
<span>Payload (JSON)</span>
|
|
326
|
+
<textarea
|
|
327
|
+
className="form__textarea"
|
|
328
|
+
value={rescheduleJson}
|
|
329
|
+
onChange={(e) => setRescheduleJson(e.target.value)}
|
|
330
|
+
spellCheck={false}
|
|
331
|
+
rows={14}
|
|
332
|
+
/>
|
|
333
|
+
</label>
|
|
334
|
+
<button type="submit" className="btn btn--primary" disabled={busy}>
|
|
335
|
+
{busy ? "Working…" : "Reschedule"}
|
|
336
|
+
</button>
|
|
337
|
+
</form>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<div className="card">
|
|
341
|
+
<h2>Cancel event</h2>
|
|
342
|
+
<form onSubmit={handleCancel} className="form">
|
|
343
|
+
<label className="form__label">
|
|
344
|
+
<span>Event id</span>
|
|
345
|
+
<input
|
|
346
|
+
className="form__input"
|
|
347
|
+
value={cancelEventId}
|
|
348
|
+
onChange={(e) => setCancelEventId(e.target.value)}
|
|
349
|
+
/>
|
|
350
|
+
</label>
|
|
351
|
+
<button type="submit" className="btn btn--secondary" disabled={busy}>
|
|
352
|
+
{busy ? "Working…" : "Cancel"}
|
|
353
|
+
</button>
|
|
354
|
+
</form>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
{error ? (
|
|
358
|
+
<div className="card card--error" role="alert">
|
|
359
|
+
<h2>Error</h2>
|
|
360
|
+
<pre className="pre-block">{error}</pre>
|
|
361
|
+
</div>
|
|
362
|
+
) : null}
|
|
363
|
+
|
|
364
|
+
{output ? (
|
|
365
|
+
<div className="card card--success">
|
|
366
|
+
<h2>Result</h2>
|
|
367
|
+
<pre className="pre-block">{output}</pre>
|
|
368
|
+
</div>
|
|
369
|
+
) : null}
|
|
370
|
+
</>
|
|
371
|
+
);
|
|
372
|
+
}
|