@blazeo.com/appointment-client 1.0.6 → 1.0.8

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 (62) hide show
  1. package/Appointment-Client/.gitattributes +2 -0
  2. package/blazeo.com-appointment-client-1.0.8.tgz +0 -0
  3. package/dist/calendar/buildUnifiedCalendarView.d.ts +8 -0
  4. package/dist/calendar/buildUnifiedCalendarView.js +29 -8
  5. package/dist/calendar/fetchCalendarDetails.d.ts +8 -40
  6. package/dist/calendar/fetchCalendarDetails.js +117 -47
  7. package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +1 -10
  8. package/dist/calendar/fetchCalendarWithOpeningHours.js +34 -15
  9. package/dist/calendar/getAllParticipantOpeningHours.d.ts +4 -1
  10. package/dist/calendar/getAllParticipantOpeningHours.js +6 -1
  11. package/dist/calendar/getOpeningHours.d.ts +4 -1
  12. package/dist/calendar/getOpeningHours.js +2 -2
  13. package/dist/calendar/getParticipantOpeningHours.js +9 -4
  14. package/dist/calendar/getParticipants.d.ts +4 -1
  15. package/dist/calendar/getParticipants.js +6 -1
  16. package/dist/calendar/mapToDesiredResponse.d.ts +70 -0
  17. package/dist/calendar/mapToDesiredResponse.js +99 -0
  18. package/dist/config/applyBlazeoDefaults.js +3 -2
  19. package/dist/config/blazeoClientDefaults.js +2 -2
  20. package/dist/config/ensureBlazeoHttpReady.d.ts +17 -0
  21. package/dist/config/ensureBlazeoHttpReady.js +31 -0
  22. package/dist/config/initializeAppointmentClient.d.ts +4 -0
  23. package/dist/config/initializeAppointmentClient.js +9 -3
  24. package/dist/config/syncBlazeoConnection.d.ts +6 -0
  25. package/dist/config/syncBlazeoConnection.js +18 -0
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.js +3 -1
  28. package/package.json +1 -1
  29. package/sample/.env.example +5 -0
  30. package/sample/package-lock.json +70 -1
  31. package/sample/package.json +1 -0
  32. package/sample/src/AllParticipantOpeningHoursTab.jsx +13 -4
  33. package/sample/src/App2.jsx +22 -2
  34. package/sample/src/AvailabilityTab.jsx +8 -3
  35. package/sample/src/BlazeoConnectionSettings.jsx +16 -15
  36. package/sample/src/CreateCalendarTab.jsx +23 -6
  37. package/sample/src/EventTab.jsx +31 -8
  38. package/sample/src/FetchCalendarTab.jsx +94 -70
  39. package/sample/src/OpeningHoursTab.jsx +13 -4
  40. package/sample/src/ParticipantInfoTab.jsx +8 -3
  41. package/sample/src/ParticipantOpeningHoursTab.jsx +17 -7
  42. package/sample/src/ParticipantTab.jsx +13 -4
  43. package/sample/src/blazeoBootstrap.js +30 -0
  44. package/sample/src/blazeoDemoError.js +14 -0
  45. package/sample/src/blazeoPushConnection.js +23 -0
  46. package/sample/src/main.jsx +3 -3
  47. package/sample/vite.config.js +19 -5
  48. package/src/calendar/buildUnifiedCalendarView.ts +35 -7
  49. package/src/calendar/fetchCalendarDetails.ts +318 -226
  50. package/src/calendar/fetchCalendarWithOpeningHours.ts +130 -99
  51. package/src/calendar/getAllParticipantOpeningHours.ts +9 -1
  52. package/src/calendar/getOpeningHours.ts +2 -2
  53. package/src/calendar/getParticipantOpeningHours.ts +14 -5
  54. package/src/calendar/getParticipants.ts +9 -1
  55. package/src/calendar/mapToDesiredResponse.ts +104 -0
  56. package/src/config/applyBlazeoDefaults.ts +3 -2
  57. package/src/config/blazeoClientDefaults.ts +2 -2
  58. package/src/config/ensureBlazeoHttpReady.ts +41 -0
  59. package/src/config/initializeAppointmentClient.ts +9 -3
  60. package/src/config/syncBlazeoConnection.ts +19 -0
  61. package/src/index.ts +7 -1
  62. package/blazeo.com-appointment-client-1.0.6.tgz +0 -0
@@ -1,7 +1,8 @@
1
1
  import { useMemo, useState } from "react";
2
- import { createCalendarAsync, createCalendarWithRelationsAsync } from "appointment-client";
2
+ import { createCalendarAsync, createCalendarWithRelationsAsync, ensureBlazeoHttpReady } from "appointment-client";
3
3
  import { getSnapshot } from "mobx-state-tree";
4
4
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
5
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
5
6
 
6
7
  /** Demo payload aligned with `CalendarBOInput` / server `CalendarBO`. */
7
8
  export function getExampleCalendarBOInput() {
@@ -38,7 +39,7 @@ export function getExampleCalendarBOInput() {
38
39
  }
39
40
 
40
41
  export function CreateCalendarTab() {
41
- const { effective } = useBlazeoConnection();
42
+ const { effective, connectionOpts } = useBlazeoConnection();
42
43
  const [localOnly, setLocalOnly] = useState(false);
43
44
  const [saveRelations, setSaveRelations] = useState(true);
44
45
  const [busy, setBusy] = useState(false);
@@ -70,7 +71,13 @@ export function CreateCalendarTab() {
70
71
  setError("Set Base URL in the connection card above.");
71
72
  return;
72
73
  }
73
- if (!localOnly) configureBlazeoFromEffective(effective);
74
+ if (!localOnly) {
75
+ configureBlazeoFromEffective(effective);
76
+ ensureBlazeoHttpReady({
77
+ baseUrl: effective.baseUrl,
78
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
79
+ });
80
+ }
74
81
 
75
82
  const hasRelations = (payload.members?.length ?? 0) > 0 || (payload.openingHours?.length ?? 0) > 0;
76
83
  const useRelations = saveRelations && hasRelations && !localOnly;
@@ -78,10 +85,20 @@ export function CreateCalendarTab() {
78
85
  setBusy(true);
79
86
  try {
80
87
  const result = useRelations
81
- ? await createCalendarWithRelationsAsync(payload, { localOnly })
82
- : await createCalendarAsync(payload, { localOnly });
88
+ ? await createCalendarWithRelationsAsync(payload, {
89
+ localOnly,
90
+ ...connectionOpts,
91
+ baseUrl: effective.baseUrl,
92
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
93
+ })
94
+ : await createCalendarAsync(payload, {
95
+ localOnly,
96
+ ...connectionOpts,
97
+ baseUrl: effective.baseUrl,
98
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
99
+ });
83
100
  setOutput(JSON.stringify(result.ok ? getSnapshot(result.calendar) : result, null, 2));
84
- if (!result.ok) setError(result.error);
101
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
85
102
  } finally {
86
103
  setBusy(false);
87
104
  }
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  cancelAppointmentEventAsync,
4
4
  createAppointmentEventAsync,
5
+ ensureBlazeoHttpReady,
5
6
  EventModel,
6
7
  rescheduleAppointmentEventAsync,
7
8
  } from "appointment-client";
@@ -10,6 +11,7 @@ import {
10
11
  configureBlazeoFromEffective,
11
12
  useBlazeoConnection,
12
13
  } from "./BlazeoConnectionSettings.jsx";
14
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
13
15
 
14
16
  function getExampleCreatePayload() {
15
17
  const start = new Date();
@@ -75,7 +77,7 @@ function safeJsonParse(text, fallback) {
75
77
  }
76
78
 
77
79
  export function EventTab() {
78
- const { effective } = useBlazeoConnection();
80
+ const { effective, connectionOpts } = useBlazeoConnection();
79
81
  const [offsetMinutes, setOffsetMinutes] = useState(-new Date().getTimezoneOffset());
80
82
  const [createJson, setCreateJson] = useState(() =>
81
83
  JSON.stringify(getExampleCreatePayload(), null, 2)
@@ -118,6 +120,7 @@ export function EventTab() {
118
120
  const [output, setOutput] = useState("");
119
121
 
120
122
  const opts = useMemo(() => ({ offsetMinutes: Number(offsetMinutes) || 0 }), [offsetMinutes]);
123
+ const eventOpts = useMemo(() => ({ ...opts, ...connectionOpts }), [opts, connectionOpts]);
121
124
 
122
125
  function ensureBase() {
123
126
  if (!effective.baseUrl) {
@@ -133,6 +136,10 @@ export function EventTab() {
133
136
  setOutput("");
134
137
  if (!ensureBase()) return;
135
138
  configureBlazeoFromEffective(effective);
139
+ ensureBlazeoHttpReady({
140
+ baseUrl: effective.baseUrl,
141
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
142
+ });
136
143
  let payload;
137
144
  try {
138
145
  payload = JSON.parse(createJson);
@@ -142,9 +149,9 @@ export function EventTab() {
142
149
  }
143
150
  setBusy(true);
144
151
  try {
145
- const result = await createAppointmentEventAsync(payload, opts);
152
+ const result = await createAppointmentEventAsync(payload, eventOpts);
146
153
  setOutput(resultToJson(result));
147
- if (!result.ok) setError(result.error);
154
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
148
155
  } finally {
149
156
  setBusy(false);
150
157
  }
@@ -156,6 +163,10 @@ export function EventTab() {
156
163
  setOutput("");
157
164
  if (!ensureBase()) return;
158
165
  configureBlazeoFromEffective(effective);
166
+ ensureBlazeoHttpReady({
167
+ baseUrl: effective.baseUrl,
168
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
169
+ });
159
170
  let payload;
160
171
  try {
161
172
  payload = JSON.parse(rescheduleJson);
@@ -165,9 +176,9 @@ export function EventTab() {
165
176
  }
166
177
  setBusy(true);
167
178
  try {
168
- const result = await rescheduleAppointmentEventAsync(payload, opts);
179
+ const result = await rescheduleAppointmentEventAsync(payload, eventOpts);
169
180
  setOutput(resultToJson(result));
170
- if (!result.ok) setError(result.error);
181
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
171
182
  } finally {
172
183
  setBusy(false);
173
184
  }
@@ -181,11 +192,19 @@ export function EventTab() {
181
192
  if (!id) return setError("Enter Blazeo event id to cancel.");
182
193
  if (!ensureBase()) return;
183
194
  configureBlazeoFromEffective(effective);
195
+ ensureBlazeoHttpReady({
196
+ baseUrl: effective.baseUrl,
197
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
198
+ });
184
199
  setBusy(true);
185
200
  try {
186
- const result = await cancelAppointmentEventAsync(id, {});
201
+ const result = await cancelAppointmentEventAsync(id, {
202
+ ...connectionOpts,
203
+ baseUrl: effective.baseUrl,
204
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
205
+ });
187
206
  setOutput(JSON.stringify(result, null, 2));
188
- if (!result.ok) setError(result.error);
207
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
189
208
  } finally {
190
209
  setBusy(false);
191
210
  }
@@ -201,6 +220,10 @@ export function EventTab() {
201
220
  if (!searchTo) return setError("Pick end date.");
202
221
  if (!ensureBase()) return;
203
222
  configureBlazeoFromEffective(effective);
223
+ ensureBlazeoHttpReady({
224
+ baseUrl: effective.baseUrl,
225
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
226
+ });
204
227
 
205
228
  const optsFromJson = safeJsonParse(searchFiltersJson, {});
206
229
  const startDateFrom = new Date(`${searchFrom}T00:00:00.000Z`).toISOString();
@@ -221,7 +244,7 @@ export function EventTab() {
221
244
  const totalCount = res?.totalCount ?? events.length;
222
245
  setOutput(JSON.stringify({ totalCount, events }, null, 2));
223
246
  } catch (err) {
224
- setError(err instanceof Error ? err.message : String(err));
247
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
225
248
  } finally {
226
249
  setBusy(false);
227
250
  }
@@ -2,11 +2,16 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  CalendarModel,
4
4
  deleteCalendarAsync,
5
+ ensureBlazeoHttpReady,
5
6
  fetchCalendarDetails,
6
7
  updateCalendarAsync,
7
8
  } from "appointment-client";
8
9
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
9
- import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
10
+ import {
11
+ configureBlazeoFromEffective,
12
+ useBlazeoConnection,
13
+ } from "./BlazeoConnectionSettings.jsx";
14
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
10
15
  import { getExampleCalendarBOInput } from "./CreateCalendarTab.jsx";
11
16
 
12
17
  function pick(row, ...keys) {
@@ -32,7 +37,7 @@ function explainFetchFailure(err, configuredBaseUrl) {
32
37
  msg === "Failed to fetch" ||
33
38
  msg === "Load failed" ||
34
39
  (err instanceof TypeError && (/fetch/i.test(msg) || /network/i.test(msg)));
35
- if (!isNetwork) return msg;
40
+ if (!isNetwork) return mapBlazeoDemoError(msg);
36
41
 
37
42
  const isRemote =
38
43
  configuredBaseUrl &&
@@ -54,6 +59,7 @@ function explainFetchFailure(err, configuredBaseUrl) {
54
59
 
55
60
  /** Opening hours list from `calendarView`, embedded `calendar.openingHours`, or legacy `openingHours`. */
56
61
  function pickOpeningHoursListFromBundle(parsed) {
62
+ if (Array.isArray(parsed?.openingHours)) return parsed.openingHours;
57
63
  const fromView = parsed?.calendarView?.openingHours;
58
64
  if (Array.isArray(fromView) && fromView.length > 0) return fromView;
59
65
  const fromCal = parsed?.calendar?.openingHours;
@@ -177,7 +183,7 @@ function calendarSnapshotToUpdatePayload(snap) {
177
183
  }
178
184
 
179
185
  export function FetchCalendarTab() {
180
- const { effective } = useBlazeoConnection();
186
+ const { effective, connectionOpts } = useBlazeoConnection();
181
187
  const [calendarId, setCalendarId] = useState("");
182
188
  const [companyKey, setCompanyKey] = useState("");
183
189
  const [busy, setBusy] = useState(false);
@@ -216,52 +222,48 @@ export function FetchCalendarTab() {
216
222
  }
217
223
  if (!ensureBaseConfigured()) return;
218
224
  configureBlazeoFromEffective(effective);
225
+ ensureBlazeoHttpReady({
226
+ baseUrl: effective.baseUrl,
227
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
228
+ });
219
229
 
220
230
  setBusy(true);
221
231
  try {
222
- const details = await fetchCalendarDetails(id);
232
+ const details = await fetchCalendarDetails(id, {
233
+ ...connectionOpts,
234
+ baseUrl: effective.baseUrl,
235
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
236
+ });
237
+
238
+ const meta = details?._meta ?? details?.meta;
239
+ if (meta && !meta.ok && meta.reason === "missing_base_url") {
240
+ setError(mapBlazeoDemoError(meta.detail ?? ""));
241
+ return;
242
+ }
223
243
 
224
- if (details.cal == null) {
244
+ if (!details) {
245
+ ensureBlazeoHttpReady({
246
+ baseUrl: effective.baseUrl,
247
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
248
+ });
225
249
  const raw = await CalendarModel.getRaw(id);
226
250
  setNote("CalendarModel.get returned null. Showing CalendarModel.getRaw only.");
227
251
  setOutput(toDisplayJson(raw));
228
252
  return;
229
253
  }
230
254
 
231
- const snap = getSnapshot(details.cal);
232
- setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
233
-
234
- const payload = {
235
- calendarView: details.calendarView,
236
- calendar: details.calendar,
237
- openingHours: details.openingHours,
238
- allParticipantOpeningHours: details.allParticipantOpeningHours,
239
- openingHoursApiResponse: details.participantOpeningHoursResponse ?? null,
240
- participants: (details.participants ?? []).map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p)),
241
- participantsInfo: Array.isArray(details.participantsInfo)
242
- ? details.participantsInfo.map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p))
243
- : details.participantsInfo ?? null,
244
- __openingHoursMeta: {
245
- fromCalendarGet: details.fromCalendarGet,
246
- fromParticipantApi: details.fromParticipantApi,
247
- calendarViewUsedAllParticipantOpeningHours: details.meta?.calendarViewUsedAllParticipantOpeningHours,
248
- embeddedCount: details.embeddedFromGet?.length ?? 0,
249
- resolvedCount: details.openingHours?.length ?? 0,
250
- },
251
- meta: details.meta,
252
- };
253
-
254
- setOutput(toDisplayJson(payload));
255
- setNote(
256
- (details.calendarView
257
- ? "Use `calendarView`: one object (calendar + nested participants with openingHours). "
258
- : "") +
259
- (details.fromCalendarGet
260
- ? "Opening hours: embedded on GET /Calendar/Get; participants from GET /Calendar/Participant/All. Single bundle in output."
261
- : details.fromParticipantApi
262
- ? "Opening hours: GET /Calendar/Participant/OpeningHours/Get; participants from GET /Calendar/Participant/All. Single bundle in output."
263
- : "Calendar loaded; opening hours empty from both embed + participant API; participants from GET /Calendar/Participant/All.")
264
- );
255
+ const snap = details._cal ? getSnapshot(details._cal) : null;
256
+ if (snap) {
257
+ setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
258
+ }
259
+
260
+ // If it's the new flat response, just use it directly for output.
261
+ setOutput(toDisplayJson(details));
262
+
263
+ // We can still try to extract meta for the UI if needed
264
+ if (meta) {
265
+ setNote(`Source: ${meta.calendarViewUsedAllParticipantOpeningHours ? "AllParticipantOpeningHours" : "Embedded/ParticipantApi"}`);
266
+ }
265
267
  } catch (err) {
266
268
  setError(explainFetchFailure(err, effective.baseUrl));
267
269
  } finally {
@@ -282,6 +284,10 @@ export function FetchCalendarTab() {
282
284
  }
283
285
  if (!ensureBaseConfigured()) return;
284
286
  configureBlazeoFromEffective(effective);
287
+ ensureBlazeoHttpReady({
288
+ baseUrl: effective.baseUrl,
289
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
290
+ });
285
291
 
286
292
  setBusy(true);
287
293
  try {
@@ -298,19 +304,14 @@ export function FetchCalendarTab() {
298
304
  const id = c.calendarId ?? String(c.id ?? "");
299
305
  if (!id) return { calendar: getSnapshot(c), openingHours: [], meta: { error: "no id" } };
300
306
  try {
301
- const b = await fetchCalendarDetails(id);
302
- return {
303
- calendarView: b.calendarView,
304
- calendar: b.calendar ?? getSnapshot(c),
305
- openingHours: b.openingHours,
306
- participants: (b.participants ?? []).map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p)),
307
- __openingHoursMeta: {
308
- fromCalendarGet: b.fromCalendarGet,
309
- fromParticipantApi: b.fromParticipantApi,
310
- calendarViewUsedAllParticipantOpeningHours: b.meta?.calendarViewUsedAllParticipantOpeningHours,
311
- },
312
- meta: b.meta,
313
- };
307
+ // fetchCalendarDetails now returns the flat unified view directly
308
+ const b = await fetchCalendarDetails(id, {
309
+ ...connectionOpts,
310
+ baseUrl: effective.baseUrl,
311
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
312
+ });
313
+ // b IS the unified calendarView: members/openingHours/participants at top level
314
+ return b ?? { calendar: getSnapshot(c), openingHours: [], meta: { error: "null response" } };
314
315
  } catch (err) {
315
316
  return {
316
317
  calendar: getSnapshot(c),
@@ -369,6 +370,10 @@ export function FetchCalendarTab() {
369
370
  setMutateOutput("");
370
371
  if (!ensureBaseConfigured()) return;
371
372
  configureBlazeoFromEffective(effective);
373
+ ensureBlazeoHttpReady({
374
+ baseUrl: effective.baseUrl,
375
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
376
+ });
372
377
  let payload;
373
378
  try {
374
379
  payload = JSON.parse(updateJson);
@@ -378,7 +383,11 @@ export function FetchCalendarTab() {
378
383
  }
379
384
  setBusy(true);
380
385
  try {
381
- const result = await updateCalendarAsync(payload, {});
386
+ const result = await updateCalendarAsync(payload, {
387
+ ...connectionOpts,
388
+ baseUrl: effective.baseUrl,
389
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
390
+ });
382
391
  if (result.ok) {
383
392
  setMutateNote("updateCalendarAsync → POST /Calendar/Event/Update");
384
393
  setMutateOutput(
@@ -392,7 +401,7 @@ export function FetchCalendarTab() {
392
401
  )
393
402
  );
394
403
  } else {
395
- setError(result.error);
404
+ setError(mapBlazeoDemoError(result.error));
396
405
  if (result.apiResponse != null) {
397
406
  setMutateOutput(JSON.stringify(result.apiResponse, null, 2));
398
407
  }
@@ -414,6 +423,10 @@ export function FetchCalendarTab() {
414
423
  }
415
424
  if (!ensureBaseConfigured()) return;
416
425
  configureBlazeoFromEffective(effective);
426
+ ensureBlazeoHttpReady({
427
+ baseUrl: effective.baseUrl,
428
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
429
+ });
417
430
  if (
418
431
  !window.confirm(
419
432
  `Delete calendar "${id}"?\n\nThis calls GET /Calendar/Remove (cannot be undone on the server).`
@@ -423,12 +436,16 @@ export function FetchCalendarTab() {
423
436
  }
424
437
  setBusy(true);
425
438
  try {
426
- const result = await deleteCalendarAsync(id, {});
439
+ const result = await deleteCalendarAsync(id, {
440
+ ...connectionOpts,
441
+ baseUrl: effective.baseUrl,
442
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
443
+ });
427
444
  if (result.ok) {
428
445
  setMutateNote("deleteCalendarAsync → GET /Calendar/Remove");
429
446
  setMutateOutput(JSON.stringify({ calendarId: id, apiResponse: result.apiResponse ?? null }, null, 2));
430
447
  } else {
431
- setError(result.error);
448
+ setError(mapBlazeoDemoError(result.error));
432
449
  if (result.apiResponse != null) {
433
450
  setMutateOutput(JSON.stringify(result.apiResponse, null, 2));
434
451
  }
@@ -443,29 +460,29 @@ export function FetchCalendarTab() {
443
460
  <div className="card">
444
461
  <h2>Fetch calendar · calendarView</h2>
445
462
  <p className="muted small">
446
- Runs <code>fetchCalendarDetails(calendarId)</code>. JSON field <code>calendarView</code> is first: one object with
447
- calendar snapshot fields + <code>members</code> + <code>openingHours</code>, and a **new** <code>participants</code> array
448
- where each participant has their own <code>openingHours</code> nested inside.
463
+ Runs <code>fetchCalendarDetails(calendarId)</code>. The JSON shown below <strong>is</strong> the unified calendar view:
464
+ one object with calendar snapshot fields + <code>members</code> + <code>openingHours</code>, plus a{" "}
465
+ <code>participants</code> array where each participant may include nested <code>openingHours</code>. (
466
+ <code>fetchCalendarBundle(calendarId)</code> returns the same shape.)
449
467
  </p>
450
468
  <p className="muted small">
451
469
  Uses <code>fetchCalendarDetails</code>: legacy <code>openingHours</code> prefers embed on{" "}
452
470
  <code>CalendarModel.getRaw</code>, else <code>getParticipantOpeningHours</code>.{" "}
453
- <code>calendarView.openingHours</code> prefers <code>getAllParticipantOpeningHours</code> (
471
+ Unified <code>openingHours</code> prefers <code>getAllParticipantOpeningHours</code> (
454
472
  <code>GET /Calendar/Participant/OpeningHours/All/Get</code>) when the API returns rows. Members combine{" "}
455
473
  <code>CalendarModel.getParticipants</code> + <code>CalendarModel.getParticipantsInfo</code> (each member may
456
474
  include <code>participantInfo</code>).
457
475
  </p>
458
476
  <p className="muted small">
459
- <strong>Single object in code:</strong> <code>fetchCalendarBundle(calendarId)</code> after{" "}
460
- <code>initializeAppointmentClient(&#123; baseUrl, consumer &#125;)</code> same unified shape as{" "}
461
- <code>calendarView</code> below. This tab runs <code>fetchCalendarDetails</code> so extra arrays stay visible.
477
+ <strong>In code:</strong> <code>fetchCalendarBundle(calendarId)</code> (after{" "}
478
+ <code>initializeAppointmentClient(&#123; baseUrl, consumer &#125;)</code>) is an alias for the same unified fetch;
479
+ use either alongside explicit <code>baseUrl</code> / <code>consumer</code> options when needed.
462
480
  </p>
463
481
  <p className="muted small">
464
482
  <strong>DevTools Network:</strong> Each fetch fires <code>/Calendar/Get</code> <strong>twice</strong> (
465
483
  <code>CalendarModel.get</code> + <code>getRaw</code>). Other calls use different URLs — filter by{" "}
466
- <code>Participant</code>, <code>OpeningHours</code>, or <code>GetInfo</code>. Those power{" "}
467
- <code>calendarView</code>. If you only see <code>Calendar/Get</code> yet the UI JSON has members/hours,
468
- widen the Network filter (&quot;All&quot;) or disable search; if <code>calendarView</code> is empty/missing fields,
484
+ <code>Participant</code>, <code>OpeningHours</code>, or <code>GetInfo</code>. Those power the unified view. If you only see <code>Calendar/Get</code> yet the UI JSON has members/hours,
485
+ widen the Network filter (&quot;All&quot;) or disable search; if the unified object is empty/missing fields,
469
486
  check the <strong>Console</strong> for errors on the participant/opening-hours requests.
470
487
  </p>
471
488
  <p className="muted small">
@@ -541,11 +558,18 @@ export function FetchCalendarTab() {
541
558
  <div className="card">
542
559
  <h2>Fetch calendars by company</h2>
543
560
  <p className="muted small">
544
- Calls <code>CalendarModel.getByCompany</code> → <code>GET /Calendar/All</code>. If the UI shows{" "}
545
- <strong>Failed to fetch</strong> while Base URL points at Azure/production, that is usually{" "}
561
+ Step 1: <code>CalendarModel.getByCompany</code> → <code>GET /Calendar/All</code> (company key calendar
562
+ list). Step 2: for each calendar id, this tab runs the same{" "}
563
+ <code>fetchCalendarDetails(calendarId)</code> pipeline as <strong>Fetch calendar · calendarView</strong>, so
564
+ the JSON below is an <strong>array</strong> of unified objects (members, openingHours, participants — same
565
+ shape as a single fetch). If an id is missing from the list row, that entry falls back to the raw snapshot
566
+ only.
567
+ </p>
568
+ <p className="muted small">
569
+ If the UI shows <strong>Failed to fetch</strong> while Base URL points at Azure/production, that is usually{" "}
546
570
  <strong>CORS</strong>: enable proxy via <code>VITE_DEV_PROXY_TARGET</code> in{" "}
547
- <code>sample/.env.development</code> and Base URL <code>http://localhost:5173/blazeo-api</code>{" "}
548
- (restart dev server).
571
+ <code>sample/.env.development</code> and Base URL <code>http://localhost:5173/blazeo-api</code> (restart dev
572
+ server).
549
573
  </p>
550
574
  <form onSubmit={handleFetchByCompany} className="form">
551
575
  <label className="form__label">
@@ -1,12 +1,13 @@
1
1
  import { useState } from "react";
2
- import { fetchCalendarWithOpeningHours } from "appointment-client";
2
+ import { ensureBlazeoHttpReady, fetchCalendarWithOpeningHours } from "appointment-client";
3
3
  import {
4
4
  configureBlazeoFromEffective,
5
5
  useBlazeoConnection,
6
6
  } from "./BlazeoConnectionSettings.jsx";
7
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
7
8
 
8
9
  export function OpeningHoursTab() {
9
- const { effective } = useBlazeoConnection();
10
+ const { effective, connectionOpts } = useBlazeoConnection();
10
11
  const [calendarId, setCalendarId] = useState("");
11
12
  const [busy, setBusy] = useState(false);
12
13
  const [error, setError] = useState("");
@@ -26,12 +27,20 @@ export function OpeningHoursTab() {
26
27
  return;
27
28
  }
28
29
  configureBlazeoFromEffective(effective);
30
+ ensureBlazeoHttpReady({
31
+ baseUrl: effective.baseUrl,
32
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
33
+ });
29
34
  setBusy(true);
30
35
  try {
31
- const res = await fetchCalendarWithOpeningHours(id);
36
+ const res = await fetchCalendarWithOpeningHours(id, {
37
+ ...connectionOpts,
38
+ baseUrl: effective.baseUrl,
39
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
40
+ });
32
41
  setOutput(JSON.stringify(res, null, 2));
33
42
  } catch (err) {
34
- setError(err instanceof Error ? err.message : String(err));
43
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
35
44
  } finally {
36
45
  setBusy(false);
37
46
  }
@@ -1,9 +1,10 @@
1
1
  import { useState } from "react";
2
- import { CalendarParticipantModel } from "appointment-client";
2
+ import { CalendarParticipantModel, ensureBlazeoHttpReady } from "appointment-client";
3
3
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
4
5
 
5
6
  export function ParticipantInfoTab() {
6
- const { effective } = useBlazeoConnection();
7
+ const { effective, connectionOpts } = useBlazeoConnection();
7
8
  const [calendarId, setCalendarId] = useState("");
8
9
  const [busy, setBusy] = useState(false);
9
10
  const [error, setError] = useState("");
@@ -18,12 +19,16 @@ export function ParticipantInfoTab() {
18
19
  if (!effective.baseUrl) return setError("Set Base URL in the connection card above.");
19
20
 
20
21
  configureBlazeoFromEffective(effective);
22
+ ensureBlazeoHttpReady({
23
+ baseUrl: effective.baseUrl,
24
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
25
+ });
21
26
  setBusy(true);
22
27
  try {
23
28
  const info = await CalendarParticipantModel.getInfoByCalendar(id);
24
29
  setOutput(JSON.stringify({ calendarId: id, count: Array.isArray(info) ? info.length : 0, info }, null, 2));
25
30
  } catch (err) {
26
- setError(err instanceof Error ? err.message : String(err));
31
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
27
32
  } finally {
28
33
  setBusy(false);
29
34
  }
@@ -1,9 +1,10 @@
1
1
  import { useState } from "react";
2
- import { getParticipantOpeningHours } from "appointment-client";
2
+ import { ensureBlazeoHttpReady, getParticipantOpeningHours } from "appointment-client";
3
3
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
4
5
 
5
6
  export function ParticipantOpeningHoursTab() {
6
- const { effective } = useBlazeoConnection();
7
+ const { effective, connectionOpts } = useBlazeoConnection();
7
8
  const [calendarId, setCalendarId] = useState("");
8
9
  const [busy, setBusy] = useState(false);
9
10
  const [error, setError] = useState("");
@@ -18,13 +19,22 @@ export function ParticipantOpeningHoursTab() {
18
19
  if (!effective.baseUrl) return setError("Set Base URL in the connection card above.");
19
20
 
20
21
  configureBlazeoFromEffective(effective);
22
+ ensureBlazeoHttpReady({
23
+ baseUrl: effective.baseUrl,
24
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
25
+ });
21
26
  setBusy(true);
22
27
  try {
23
- debugger;
24
- const result = await getParticipantOpeningHours(id);
25
-
28
+ const result = await getParticipantOpeningHours(id, {
29
+ ...connectionOpts,
30
+ baseUrl: effective.baseUrl,
31
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
32
+ });
33
+
26
34
  if (!result.meta.ok) {
27
- setError(result.meta.error || `Failed to fetch: ${result.meta.reason}`);
35
+ setError(
36
+ mapBlazeoDemoError(result.meta.error || `Failed to fetch: ${result.meta.reason}`)
37
+ );
28
38
  if (result.raw) setOutput(JSON.stringify(result.raw, null, 2));
29
39
  return;
30
40
  }
@@ -37,7 +47,7 @@ export function ParticipantOpeningHoursTab() {
37
47
  meta: result.meta
38
48
  }, null, 2));
39
49
  } catch (err) {
40
- setError(err instanceof Error ? err.message : String(err));
50
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
41
51
  } finally {
42
52
  setBusy(false);
43
53
  }
@@ -1,10 +1,11 @@
1
1
  import { useMemo, useState } from "react";
2
- import { getExampleParticipants, getParticipants } from "appointment-client";
2
+ import { ensureBlazeoHttpReady, getExampleParticipants, getParticipants } from "appointment-client";
3
3
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
4
4
  import {
5
5
  configureBlazeoFromEffective,
6
6
  useBlazeoConnection,
7
7
  } from "./BlazeoConnectionSettings.jsx";
8
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
8
9
 
9
10
  function toDisplayJson(value) {
10
11
  if (value == null) return JSON.stringify(value, null, 2);
@@ -20,7 +21,7 @@ function toDisplayJson(value) {
20
21
  }
21
22
 
22
23
  export function ParticipantTab() {
23
- const { effective } = useBlazeoConnection();
24
+ const { effective, connectionOpts } = useBlazeoConnection();
24
25
  const example = useMemo(() => getExampleParticipants(), []);
25
26
  const [calendarId, setCalendarId] = useState("");
26
27
  const [busy, setBusy] = useState(false);
@@ -41,12 +42,20 @@ export function ParticipantTab() {
41
42
  return;
42
43
  }
43
44
  configureBlazeoFromEffective(effective);
45
+ ensureBlazeoHttpReady({
46
+ baseUrl: effective.baseUrl,
47
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
48
+ });
44
49
  setBusy(true);
45
50
  try {
46
- const res = await getParticipants(id);
51
+ const res = await getParticipants(id, {
52
+ ...connectionOpts,
53
+ baseUrl: effective.baseUrl,
54
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
55
+ });
47
56
  setOutput(toDisplayJson(res));
48
57
  } catch (err) {
49
- setError(err instanceof Error ? err.message : String(err));
58
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
50
59
  } finally {
51
60
  setBusy(false);
52
61
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Runs once before React mounts. Order:
3
+ * 1. `blazeoClientDefaults.ts` (via applyBlazeoClientConfig) — optional file defaults
4
+ * 2. `VITE_BLAZEO_BASE_URL` / `VITE_BLAZEO_CONSUMER` — overrides when set
5
+ *
6
+ * Uses {@link pushBlazeoConnection} so calendar-client is configured the same way as the UI card.
7
+ */
8
+ import { applyBlazeoClientConfig } from "appointment-client";
9
+ import { pushBlazeoConnection } from "./blazeoPushConnection.js";
10
+
11
+ function normalizeBase(u) {
12
+ const t = (u ?? "").trim();
13
+ if (!t) return "";
14
+ return t.replace(/\/+$/, "");
15
+ }
16
+
17
+ export function bootstrapBlazeoClient() {
18
+ debugger;
19
+ applyBlazeoClientConfig();
20
+
21
+ const envBase = normalizeBase(import.meta.env.VITE_BLAZEO_BASE_URL ?? "");
22
+ const envConsumer = (import.meta.env.VITE_BLAZEO_CONSUMER ?? "").trim();
23
+
24
+ if (!envBase) return;
25
+
26
+ pushBlazeoConnection({
27
+ baseUrl: envBase,
28
+ ...(envConsumer ? { consumer: envConsumer } : {}),
29
+ });
30
+ }