@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
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Maps a raw Blazeo API calendar payload (often PascalCase) to the specific camelCase
3
+ * shape requested by the user, including typenames and normalized fields.
4
+ */
5
+ export declare function mapToDesiredCalendarResponse(payload: any, openingHours?: any[], members?: any[]): {
6
+ id: number | null;
7
+ durationUnit: number | null;
8
+ minimumBookingNotice: number | null;
9
+ minimumBookingNoticeUnit: number | null;
10
+ minimumCancelationNotice: number | null;
11
+ minimumCancelationNoticeUnit: number | null;
12
+ futureLimit: number | null;
13
+ futureLimitUnit: number | null;
14
+ bufferTime: number | null;
15
+ bufferTimeUnit: number | null;
16
+ calendarLink: any;
17
+ uuid: any;
18
+ location: any;
19
+ bookingPageTitle: any;
20
+ reminderChannelStatuses: {
21
+ id: any;
22
+ calendarId: any;
23
+ channelId: any;
24
+ status: boolean;
25
+ appointmentReminders: any;
26
+ __typename: string;
27
+ }[];
28
+ members: {
29
+ id: any;
30
+ name: any;
31
+ email: any;
32
+ status: any;
33
+ __typename: string;
34
+ }[];
35
+ createdOn: any;
36
+ modifiedOn: any;
37
+ name: any;
38
+ timeZoneId: any;
39
+ description: any;
40
+ assignmentType: number | null;
41
+ duration: number | null;
42
+ bookingLimit: number | null;
43
+ calendarJson: any;
44
+ isThirdPartySaved: boolean;
45
+ themeId: number | null;
46
+ theme: {
47
+ id: any;
48
+ color: any;
49
+ logoUrl: any;
50
+ __typename: string;
51
+ } | null;
52
+ openingHours: {
53
+ id: any;
54
+ createdOn: any;
55
+ modifiedOn: any;
56
+ member: any;
57
+ openingHourId: any;
58
+ calendarId: any;
59
+ participantId: any;
60
+ days: any;
61
+ startHour: any;
62
+ startMinute: any;
63
+ endHour: any;
64
+ endMinute: any;
65
+ off: boolean;
66
+ __typename: string;
67
+ }[];
68
+ appointmentUserDefinedFields: any;
69
+ __typename: string;
70
+ } | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Maps a raw Blazeo API calendar payload (often PascalCase) to the specific camelCase
3
+ * shape requested by the user, including typenames and normalized fields.
4
+ */
5
+ export function mapToDesiredCalendarResponse(payload, openingHours = [], members = []) {
6
+ if (!payload)
7
+ return null;
8
+ const pick = (obj, ...keys) => {
9
+ for (const k of keys) {
10
+ if (obj[k] !== undefined && obj[k] !== null)
11
+ return obj[k];
12
+ }
13
+ return undefined;
14
+ };
15
+ const n = (v) => (v != null && v !== "" ? Number(v) : null);
16
+ // Map members with typename
17
+ const mappedMembers = members.map(m => ({
18
+ id: m.id ?? pick(m, "id", "Id"),
19
+ name: m.name ?? pick(m, "name", "Name", "alias", "Alias"),
20
+ email: m.email ?? pick(m, "email", "Email") ?? null,
21
+ status: m.status ?? pick(m, "status", "Status") ?? 0,
22
+ __typename: "Member"
23
+ }));
24
+ // Map opening hours with typename and raw fields
25
+ const mappedOpeningHours = openingHours.map(oh => ({
26
+ id: pick(oh, "id", "Id") ?? 0,
27
+ createdOn: pick(oh, "createdOn", "CreatedOn", "created_on") ?? "0001-01-01T00:00:00.000Z",
28
+ modifiedOn: pick(oh, "modifiedOn", "ModifiedOn", "modified_on") ?? "0001-01-01T00:00:00.000Z",
29
+ member: pick(oh, "member", "Member"),
30
+ openingHourId: pick(oh, "openingHourId", "OpeningHourId", "opening_hour_id") ?? "",
31
+ calendarId: pick(oh, "calendarId", "CalendarId", "calendar_id") ?? "",
32
+ participantId: pick(oh, "participantId", "ParticipantId", "participant_id") ?? "",
33
+ days: oh.days ?? [],
34
+ startHour: oh.startHour ?? pick(oh, "startHour", "StartHour") ?? 0,
35
+ startMinute: oh.startMinute ?? pick(oh, "startMinute", "StartMinute") ?? 0,
36
+ endHour: oh.endHour ?? pick(oh, "endHour", "EndHour") ?? 0,
37
+ endMinute: oh.endMinute ?? pick(oh, "endMinute", "EndMinute") ?? 0,
38
+ off: !!(oh.off ?? pick(oh, "off", "Off")),
39
+ __typename: "OpeningHour"
40
+ }));
41
+ // Map theme
42
+ const rawTheme = pick(payload, "theme", "Theme");
43
+ const theme = rawTheme ? {
44
+ id: pick(rawTheme, "id", "Id"),
45
+ color: pick(rawTheme, "color", "Color"),
46
+ logoUrl: pick(rawTheme, "logoUrl", "LogoUrl") ?? null,
47
+ __typename: "Theme"
48
+ } : null;
49
+ // Map reminders
50
+ const rawReminders = pick(payload, "reminderChannelStatuses", "ReminderChannelStatuses") ?? [];
51
+ const reminderChannelStatuses = Array.isArray(rawReminders) ? rawReminders.map((r) => ({
52
+ id: pick(r, "id", "Id"),
53
+ calendarId: pick(r, "calendarId", "CalendarId"),
54
+ channelId: pick(r, "channelId", "ChannelId"),
55
+ status: !!pick(r, "status", "Status"),
56
+ appointmentReminders: (pick(r, "appointmentReminders", "AppointmentReminders") ?? []).map((ar) => ({
57
+ id: pick(ar, "id", "Id"),
58
+ reminderChannelStatusId: pick(ar, "reminderChannelStatusId", "ReminderChannelStatusId"),
59
+ recipientType: pick(ar, "recipientType", "RecipientType"),
60
+ beforeEventTime: pick(ar, "beforeEventTime", "BeforeEventTime"),
61
+ unit: pick(ar, "unit", "Unit"),
62
+ __typename: "AppointmentReminder"
63
+ })),
64
+ __typename: "ReminderChannelStatus"
65
+ })) : [];
66
+ return {
67
+ id: n(pick(payload, "id", "Id")),
68
+ durationUnit: n(pick(payload, "durationUnit", "DurationUnit")),
69
+ minimumBookingNotice: n(pick(payload, "minimumBookingNotice", "MinimumBookingNotice")),
70
+ minimumBookingNoticeUnit: n(pick(payload, "minimumBookingNoticeUnit", "MinimumBookingNoticeUnit")),
71
+ minimumCancelationNotice: n(pick(payload, "minimumCancelationNotice", "MinimumCancelationNotice")),
72
+ minimumCancelationNoticeUnit: n(pick(payload, "minimumCancelationNoticeUnit", "MinimumCancelationNoticeUnit")),
73
+ futureLimit: n(pick(payload, "futureLimit", "FutureLimit")),
74
+ futureLimitUnit: n(pick(payload, "futureLimitUnit", "FutureLimitUnit")),
75
+ bufferTime: n(pick(payload, "bufferTime", "BufferTime")),
76
+ bufferTimeUnit: n(pick(payload, "bufferTimeUnit", "BufferTimeUnit")),
77
+ calendarLink: pick(payload, "calendarLink", "CalendarLink"),
78
+ uuid: pick(payload, "uuid", "Uuid", "calendarId", "CalendarId"),
79
+ location: pick(payload, "location", "Location") ?? "",
80
+ bookingPageTitle: pick(payload, "bookingPageTitle", "BookingPageTitle") ?? null,
81
+ reminderChannelStatuses,
82
+ members: mappedMembers,
83
+ createdOn: pick(payload, "createdOn", "CreatedOn") ?? "0001-01-01T00:00:00.000Z",
84
+ modifiedOn: pick(payload, "modifiedOn", "ModifiedOn") ?? "0001-01-01T00:00:00.000Z",
85
+ name: pick(payload, "name", "Name"),
86
+ timeZoneId: pick(payload, "timeZoneId", "TimeZoneId"),
87
+ description: pick(payload, "description", "Description") ?? "",
88
+ assignmentType: n(pick(payload, "assignmentType", "AssignmentType", "assignmentMethod", "AssignmentMethod")),
89
+ duration: n(pick(payload, "duration", "Duration")),
90
+ bookingLimit: n(pick(payload, "bookingLimit", "BookingLimit")),
91
+ calendarJson: pick(payload, "calendarJson", "CalendarJson") ?? null,
92
+ isThirdPartySaved: !!pick(payload, "isThirdPartySaved", "IsThirdPartySaved"),
93
+ themeId: n(pick(payload, "themeId", "ThemeId")),
94
+ theme,
95
+ openingHours: mappedOpeningHours,
96
+ appointmentUserDefinedFields: pick(payload, "appointmentUserDefinedFields", "AppointmentUserDefinedFields") ?? [],
97
+ __typename: "Calendar"
98
+ };
99
+ }
@@ -1,12 +1,13 @@
1
- import { configure } from "@blazeo.com/calendar-client";
2
1
  import { blazeoClientConfig } from "./blazeoClientDefaults.js";
2
+ import { syncBlazeoConnection } from "./syncBlazeoConnection.js";
3
3
  /** Push {@link blazeoClientConfig} into `@blazeo.com/calendar-client` (global store). Call explicitly if you use file defaults; otherwise use {@link initializeAppointmentClient}. */
4
4
  export function applyBlazeoClientConfig() {
5
+ debugger;
5
6
  const baseUrl = blazeoClientConfig.baseUrl?.trim().replace(/\/+$/, "");
6
7
  if (!baseUrl)
7
8
  return;
8
9
  const consumer = blazeoClientConfig.consumer?.trim();
9
- configure({
10
+ syncBlazeoConnection({
10
11
  baseUrl,
11
12
  ...(consumer ? { consumer } : {}),
12
13
  });
@@ -6,6 +6,6 @@
6
6
  * @example baseUrl: "https://apptscheduling.azurewebsites.net"
7
7
  */
8
8
  export const blazeoClientConfig = {
9
- baseUrl: "",
10
- consumer: "",
9
+ baseUrl: "https://apptscheduling.azurewebsites.net",
10
+ consumer: "smarthub",
11
11
  };
@@ -0,0 +1,17 @@
1
+ export type EnsureBlazeoHttpOptions = {
2
+ baseUrl?: string;
3
+ consumer?: string;
4
+ };
5
+ /**
6
+ * Ensures global Blazeo `configure({ baseUrl })` runs before any `CalendarModel` / `EventModel` HTTP.
7
+ * Uses the same resolution as {@link resolveBlazeoConnection}: explicit args, existing `getConfig()`,
8
+ * then `blazeoClientDefaults` — so file defaults apply even if the host never called `configure`.
9
+ */
10
+ export declare function ensureBlazeoHttpReady(options?: EnsureBlazeoHttpOptions): {
11
+ ok: true;
12
+ baseUrl: string;
13
+ consumer?: string;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ };
@@ -0,0 +1,31 @@
1
+ import { configure } from "@blazeo.com/calendar-client";
2
+ import { resolveBlazeoConnection } from "../calendar/createCalendar.js";
3
+ /**
4
+ * Ensures global Blazeo `configure({ baseUrl })` runs before any `CalendarModel` / `EventModel` HTTP.
5
+ * Uses the same resolution as {@link resolveBlazeoConnection}: explicit args, existing `getConfig()`,
6
+ * then `blazeoClientDefaults` — so file defaults apply even if the host never called `configure`.
7
+ */
8
+ export function ensureBlazeoHttpReady(options = {}) {
9
+ // Hard-prefer explicit args (call-site can bypass any module-resolution mismatch).
10
+ const explicitBase = options.baseUrl?.trim().replace(/\/+$/, "");
11
+ const explicitConsumer = options.consumer?.trim() || undefined;
12
+ if (explicitBase) {
13
+ configure({
14
+ baseUrl: explicitBase,
15
+ ...(explicitConsumer ? { consumer: explicitConsumer } : {}),
16
+ });
17
+ return { ok: true, baseUrl: explicitBase, ...(explicitConsumer ? { consumer: explicitConsumer } : {}) };
18
+ }
19
+ const { baseUrl, consumer } = resolveBlazeoConnection(options);
20
+ if (!baseUrl) {
21
+ return {
22
+ ok: false,
23
+ error: "Blazeo base URL is not set. Call initializeAppointmentClient({ baseUrl }) or configure({ baseUrl }) at app startup, set blazeoClientConfig.baseUrl, or pass baseUrl when calling fetch APIs.",
24
+ };
25
+ }
26
+ configure({
27
+ baseUrl,
28
+ ...(consumer ? { consumer } : {}),
29
+ });
30
+ return { ok: true, baseUrl, ...(consumer ? { consumer } : {}) };
31
+ }
@@ -3,5 +3,9 @@ export interface AppointmentClientConfig {
3
3
  consumer?: string;
4
4
  fetch?: typeof fetch;
5
5
  }
6
+ /**
7
+ * Applies Blazeo connection (same as {@link syncBlazeoConnection}) and marks the client as configured
8
+ * when a non-empty `baseUrl` was written to `@blazeo.com/calendar-client`.
9
+ */
6
10
  export declare function initializeAppointmentClient(config: AppointmentClientConfig): void;
7
11
  export declare function isAppointmentClientConfigured(): boolean;
@@ -1,8 +1,14 @@
1
- import { configure, getConfig } from "@blazeo.com/calendar-client";
1
+ import { getConfig } from "@blazeo.com/calendar-client";
2
+ import { syncBlazeoConnection } from "./syncBlazeoConnection.js";
2
3
  let isConfigured = false;
4
+ /**
5
+ * Applies Blazeo connection (same as {@link syncBlazeoConnection}) and marks the client as configured
6
+ * when a non-empty `baseUrl` was written to `@blazeo.com/calendar-client`.
7
+ */
3
8
  export function initializeAppointmentClient(config) {
4
- configure(config);
5
- isConfigured = true;
9
+ if (syncBlazeoConnection(config)) {
10
+ isConfigured = true;
11
+ }
6
12
  }
7
13
  export function isAppointmentClientConfigured() {
8
14
  return isConfigured || getConfig() !== null;
@@ -0,0 +1,6 @@
1
+ import type { AppointmentClientConfig } from "./initializeAppointmentClient.js";
2
+ /**
3
+ * Writes `baseUrl` / `consumer` into the global `@blazeo.com/calendar-client` `configure()` store.
4
+ * Returns whether anything was applied (skipped when `baseUrl` is empty after trim).
5
+ */
6
+ export declare function syncBlazeoConnection(config: AppointmentClientConfig): boolean;
@@ -0,0 +1,18 @@
1
+ import { configure } from "@blazeo.com/calendar-client";
2
+ /**
3
+ * Writes `baseUrl` / `consumer` into the global `@blazeo.com/calendar-client` `configure()` store.
4
+ * Returns whether anything was applied (skipped when `baseUrl` is empty after trim).
5
+ */
6
+ export function syncBlazeoConnection(config) {
7
+ const baseUrl = config.baseUrl?.trim().replace(/\/+$/, "");
8
+ if (!baseUrl)
9
+ return false;
10
+ configure({
11
+ baseUrl,
12
+ ...(config.consumer != null && String(config.consumer).trim() !== ""
13
+ ? { consumer: String(config.consumer).trim() }
14
+ : {}),
15
+ ...(config.fetch ? { fetch: config.fetch } : {}),
16
+ });
17
+ return true;
18
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export { initializeAppointmentClient, isAppointmentClientConfigured } from "./config/initializeAppointmentClient.js";
1
+ export { initializeAppointmentClient, isAppointmentClientConfigured, } from "./config/initializeAppointmentClient.js";
2
+ export { syncBlazeoConnection } from "./config/syncBlazeoConnection.js";
3
+ export { ensureBlazeoHttpReady } from "./config/ensureBlazeoHttpReady.js";
4
+ export type { EnsureBlazeoHttpOptions } from "./config/ensureBlazeoHttpReady.js";
2
5
  export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
3
6
  export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
4
7
  export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { getExampleSlots } from "./exampleData.js";
2
- export { initializeAppointmentClient, isAppointmentClientConfigured } from "./config/initializeAppointmentClient.js";
2
+ export { initializeAppointmentClient, isAppointmentClientConfigured, } from "./config/initializeAppointmentClient.js";
3
+ export { syncBlazeoConnection } from "./config/syncBlazeoConnection.js";
4
+ export { ensureBlazeoHttpReady } from "./config/ensureBlazeoHttpReady.js";
3
5
  export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
4
6
  export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
5
7
  export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazeo.com/appointment-client",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,5 @@
1
+ # Copy to `.env` or `.env.local` in this folder and set your API host (no trailing slash).
2
+ # Vite exposes only vars prefixed with VITE_ to the browser.
3
+ VITE_BLAZEO_BASE_URL=https://your-blazeo-api.example.com
4
+ # Optional, if your tenant uses it:
5
+ # VITE_BLAZEO_CONSUMER=
@@ -6,6 +6,7 @@
6
6
  "": {
7
7
  "name": "appointment-client-sample",
8
8
  "dependencies": {
9
+ "@blazeo.com/appointment-client": "file:../blazeo.com-appointment-client-1.0.7.tgz",
9
10
  "appointment-client": "file:..",
10
11
  "react": "^19.0.0",
11
12
  "react-dom": "^19.0.0"
@@ -17,7 +18,7 @@
17
18
  },
18
19
  "..": {
19
20
  "name": "@blazeo.com/appointment-client",
20
- "version": "1.0.5",
21
+ "version": "1.0.7",
21
22
  "dependencies": {
22
23
  "@blazeo.com/calendar-client": "^1.0.18",
23
24
  "mobx": "^6.13.7",
@@ -319,6 +320,38 @@
319
320
  "node": ">=6.9.0"
320
321
  }
321
322
  },
323
+ "node_modules/@blazeo.com/appointment-client": {
324
+ "version": "1.0.7",
325
+ "resolved": "file:../blazeo.com-appointment-client-1.0.7.tgz",
326
+ "integrity": "sha512-b2E5TIdxvNJLH3oEvCQngDhzQ08iwXNolPcok3emAlGipO0f8iGDij5hnVUntt68c2Lr3IPz3cvr/BcUY6MofQ==",
327
+ "dependencies": {
328
+ "@blazeo.com/calendar-client": "^1.0.18",
329
+ "mobx": "^6.13.7",
330
+ "mobx-state-tree": "^7.0.2"
331
+ }
332
+ },
333
+ "node_modules/@blazeo.com/calendar-client": {
334
+ "version": "1.0.18",
335
+ "resolved": "https://registry.npmjs.org/@blazeo.com/calendar-client/-/calendar-client-1.0.18.tgz",
336
+ "integrity": "sha512-wHqOZKUAH4JDgsdwaktEG68H5YfArOW3LEouCXznIza4YqNrlkfdJEyqiwNf6KjY2nELi+s2tFM22/1MKaWf5Q==",
337
+ "license": "UNLICENSED",
338
+ "dependencies": {
339
+ "mobx": "^6.10.0",
340
+ "mobx-state-tree": "^5.4.0"
341
+ },
342
+ "engines": {
343
+ "node": ">=18"
344
+ }
345
+ },
346
+ "node_modules/@blazeo.com/calendar-client/node_modules/mobx-state-tree": {
347
+ "version": "5.4.2",
348
+ "resolved": "https://registry.npmjs.org/mobx-state-tree/-/mobx-state-tree-5.4.2.tgz",
349
+ "integrity": "sha512-SGXAh2KCBQbWVcxeQbZEr5pchTgcfNZmGVRL2a2Me+pSMH98bZWXD6EOuuijbTGbc0hOoOsbab3JdwJyr+fW7Q==",
350
+ "license": "MIT",
351
+ "peerDependencies": {
352
+ "mobx": "^6.3.0"
353
+ }
354
+ },
322
355
  "node_modules/@esbuild/aix-ppc64": {
323
356
  "version": "0.25.12",
324
357
  "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -1425,6 +1458,28 @@
1425
1458
  "yallist": "^3.0.2"
1426
1459
  }
1427
1460
  },
1461
+ "node_modules/mobx": {
1462
+ "version": "6.15.3",
1463
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.15.3.tgz",
1464
+ "integrity": "sha512-6+ZSYDs5zgH5CdGfEU2q2Lsa5PztVryL1ys7kAImTU25n2A9LAMj/yneVsQpd03MfwMLDQF+7kakJR9Z+cQxSw==",
1465
+ "license": "MIT",
1466
+ "funding": {
1467
+ "type": "opencollective",
1468
+ "url": "https://opencollective.com/mobx"
1469
+ }
1470
+ },
1471
+ "node_modules/mobx-state-tree": {
1472
+ "version": "7.2.0",
1473
+ "resolved": "https://registry.npmjs.org/mobx-state-tree/-/mobx-state-tree-7.2.0.tgz",
1474
+ "integrity": "sha512-o46IsMI/lHITYCmkbQgNOCDT8M9Hd2WAgOlHt42hCg+XUZP21obF5ui5Vv9epIsQV8Wpp/GWNHu1YFhTx6iq0g==",
1475
+ "license": "MIT",
1476
+ "dependencies": {
1477
+ "ts-essentials": "^9.4.1"
1478
+ },
1479
+ "peerDependencies": {
1480
+ "mobx": "^6.3.0"
1481
+ }
1482
+ },
1428
1483
  "node_modules/ms": {
1429
1484
  "version": "2.1.3",
1430
1485
  "dev": true,
@@ -1598,6 +1653,20 @@
1598
1653
  "url": "https://github.com/sponsors/SuperchupuDev"
1599
1654
  }
1600
1655
  },
1656
+ "node_modules/ts-essentials": {
1657
+ "version": "9.4.2",
1658
+ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.2.tgz",
1659
+ "integrity": "sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==",
1660
+ "license": "MIT",
1661
+ "peerDependencies": {
1662
+ "typescript": ">=4.1.0"
1663
+ },
1664
+ "peerDependenciesMeta": {
1665
+ "typescript": {
1666
+ "optional": true
1667
+ }
1668
+ }
1669
+ },
1601
1670
  "node_modules/update-browserslist-db": {
1602
1671
  "version": "1.2.3",
1603
1672
  "dev": true,
@@ -10,6 +10,7 @@
10
10
  "participants:info": "node ./scripts/getInfoByCalendar.mjs"
11
11
  },
12
12
  "dependencies": {
13
+ "@blazeo.com/appointment-client": "file:../blazeo.com-appointment-client-1.0.7.tgz",
13
14
  "appointment-client": "file:..",
14
15
  "react": "^19.0.0",
15
16
  "react-dom": "^19.0.0"
@@ -1,9 +1,10 @@
1
1
  import { useState } from "react";
2
- import { getAllParticipantOpeningHours } from "appointment-client";
2
+ import { ensureBlazeoHttpReady, getAllParticipantOpeningHours } from "appointment-client";
3
3
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
4
5
 
5
6
  export function AllParticipantOpeningHoursTab() {
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,20 @@ export function AllParticipantOpeningHoursTab() {
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
- const res = await getAllParticipantOpeningHours(id);
28
+ const res = await getAllParticipantOpeningHours(id, {
29
+ ...connectionOpts,
30
+ baseUrl: effective.baseUrl,
31
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
32
+ });
24
33
  setOutput(JSON.stringify(res, null, 2));
25
34
  } catch (err) {
26
- setError(err instanceof Error ? err.message : String(err));
35
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
27
36
  } finally {
28
37
  setBusy(false);
29
38
  }
@@ -3,6 +3,7 @@ import {
3
3
  BlazeoConnectionProvider,
4
4
  useBlazeoConnection,
5
5
  } from "./BlazeoConnectionSettings.jsx";
6
+ import { ensureBlazeoHttpReady, getConfig } from "appointment-client";
6
7
  import { CalendarTab } from "./CalendarTab.jsx";
7
8
  import { EventTab } from "./EventTab.jsx";
8
9
  import { ParticipantTab } from "./ParticipantTab.jsx";
@@ -35,14 +36,23 @@ function ConnectionSettingsCard() {
35
36
  setBaseUrlInput,
36
37
  setConsumerInput,
37
38
  effective,
39
+ connectionOpts,
38
40
  } = useBlazeoConnection();
39
41
 
42
+ const ready = ensureBlazeoHttpReady({
43
+ baseUrl: effective.baseUrl,
44
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
45
+ });
46
+ const cfg = getConfig?.() ?? null;
47
+
40
48
  return (
41
49
  <div className="card connection-card">
42
50
  <h2>Blazeo connection</h2>
43
51
  <p className="muted small">
44
52
  Values are saved in <code>localStorage</code>. Empty fields fall back to{" "}
45
- <code>appointment-client/src/config/blazeoClientDefaults.ts</code>.
53
+ <code>appointment-client/src/config/blazeoClientDefaults.ts</code>. All tabs use the <strong>effective</strong> URL
54
+ here and run <code>pushBlazeoConnection</code> + <code>ensureBlazeoHttpReady</code> so calendar APIs receive{" "}
55
+ <code>baseUrl</code> from this card.
46
56
  </p>
47
57
  <p className="muted small">
48
58
  Effective:{" "}
@@ -54,6 +64,12 @@ function ConnectionSettingsCard() {
54
64
  </>
55
65
  ) : null}
56
66
  </p>
67
+ <p className="muted small">
68
+ Debug: <code>connectionOpts</code> → <code>{JSON.stringify(connectionOpts)}</code> ·{" "}
69
+ <code>ensureBlazeoHttpReady</code> →{" "}
70
+ <code>{ready.ok ? "ok" : "missing_base_url"}</code> · <code>getConfig().baseUrl</code> →{" "}
71
+ <code>{cfg?.baseUrl ?? "(null)"}</code>
72
+ </p>
57
73
  <div className="connection-card__row">
58
74
  <label className="form__label">
59
75
  <span>Base URL</span>
@@ -89,7 +105,11 @@ function AppShell() {
89
105
  <main className="page">
90
106
  <header className="header">
91
107
  <h1>appointment-client</h1>
92
- <p className="muted">Browser sample — use tab <strong>Fetch · calendarView</strong> for the unified <code>calendarView</code> object.</p>
108
+ <p className="muted">
109
+ Browser sample — set <strong>Base URL</strong> in <strong>Blazeo connection</strong> below first; every tab uses
110
+ those values for <code>configure</code> + API calls. Tab <strong>Fetch · calendarView</strong> shows the unified{" "}
111
+ <code>calendarView</code> object.
112
+ </p>
93
113
  </header>
94
114
 
95
115
  <ConnectionSettingsCard />
@@ -1,6 +1,7 @@
1
1
  import { useMemo, useState } from "react";
2
- import { EventModel } from "appointment-client";
2
+ import { ensureBlazeoHttpReady, EventModel } from "appointment-client";
3
3
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
4
5
 
5
6
  function parseYmd(ymd) {
6
7
  const t = (ymd ?? "").trim();
@@ -10,7 +11,7 @@ function parseYmd(ymd) {
10
11
  }
11
12
 
12
13
  export function AvailabilityTab() {
13
- const { effective } = useBlazeoConnection();
14
+ const { effective, connectionOpts } = useBlazeoConnection();
14
15
  const [calendarId, setCalendarId] = useState("");
15
16
  const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10));
16
17
  const [offsetMinutes, setOffsetMinutes] = useState(-new Date().getTimezoneOffset());
@@ -30,13 +31,17 @@ export function AvailabilityTab() {
30
31
  if (!parts) return setError("Pick a valid date.");
31
32
  if (!effective.baseUrl) return setError("Set Base URL above.");
32
33
  configureBlazeoFromEffective(effective);
34
+ ensureBlazeoHttpReady({
35
+ baseUrl: effective.baseUrl,
36
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
37
+ });
33
38
 
34
39
  setBusy(true);
35
40
  try {
36
41
  const list = await EventModel.getAvailability(id, parts.y, parts.m, parts.d, opts);
37
42
  setOutput(JSON.stringify(list.map((n) => n.toJSON?.() ?? n), null, 2));
38
43
  } catch (err) {
39
- setError(err instanceof Error ? err.message : String(err));
44
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
40
45
  } finally {
41
46
  setBusy(false);
42
47
  }
@@ -6,8 +6,8 @@ import {
6
6
  useMemo,
7
7
  useState,
8
8
  } from "react";
9
- import { blazeoClientConfig, initializeAppointmentClient } from "appointment-client";
10
- import { configure as configureCalendarClient } from "@blazeo.com/calendar-client";
9
+ import { blazeoClientConfig } from "appointment-client";
10
+ import { pushBlazeoConnection } from "./blazeoPushConnection.js";
11
11
 
12
12
  const STORAGE_BASE = "appointment-client-sample:blazeoBaseUrl";
13
13
  const STORAGE_CONSUMER = "appointment-client-sample:blazeoConsumer";
@@ -46,18 +46,10 @@ export function mergeBlazeoUiWithFile(uiBaseUrl, uiConsumer) {
46
46
 
47
47
  /**
48
48
  * Re-apply global Blazeo `configure` from the effective connection card state.
49
- * Call at the start of any handler that uses `CalendarModel` / `EventModel` HTTP so
50
- * `getParticipantOpeningHours` (instance env) and static helpers always see the same
51
- * `baseUrl`.
49
+ * Pushes into both calendar-client entrypoints ({@link pushBlazeoConnection}).
52
50
  */
53
51
  export function configureBlazeoFromEffective(effective) {
54
- const baseUrl = normalizeBase(effective?.baseUrl ?? "");
55
- if (!baseUrl) return;
56
- const consumer = (effective?.consumer ?? "").trim() || undefined;
57
- // Configure appointment-client (which configures its internal calendar-client instance)
58
- initializeAppointmentClient({ baseUrl, ...(consumer ? { consumer } : {}) });
59
- // Also configure the calendar-client instance that the sample (and Vite alias) may resolve.
60
- configureCalendarClient({ baseUrl, ...(consumer ? { consumer } : {}) });
52
+ pushBlazeoConnection(effective);
61
53
  }
62
54
 
63
55
  const BlazeoConnectionContext = createContext(null);
@@ -71,6 +63,15 @@ export function BlazeoConnectionProvider({ children }) {
71
63
  [baseUrlInput, consumerInput]
72
64
  );
73
65
 
66
+ /** Pass into `fetchCalendarDetails`, `createCalendarAsync`, etc. (explicit `baseUrl` for `ensureBlazeoHttpReady`). */
67
+ const connectionOpts = useMemo(
68
+ () => ({
69
+ ...(effective.baseUrl ? { baseUrl: effective.baseUrl } : {}),
70
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
71
+ }),
72
+ [effective.baseUrl, effective.consumer]
73
+ );
74
+
74
75
  useEffect(() => {
75
76
  writeStored(STORAGE_BASE, baseUrlInput.trim());
76
77
  }, [baseUrlInput]);
@@ -87,8 +88,7 @@ export function BlazeoConnectionProvider({ children }) {
87
88
  useLayoutEffect(() => {
88
89
  const { baseUrl, consumer } = mergeBlazeoUiWithFile(baseUrlInput, consumerInput);
89
90
  if (!baseUrl) return;
90
- initializeAppointmentClient({ baseUrl, ...(consumer ? { consumer } : {}) });
91
- configureCalendarClient({ baseUrl, ...(consumer ? { consumer } : {}) });
91
+ pushBlazeoConnection({ baseUrl, consumer });
92
92
  }, [baseUrlInput, consumerInput]);
93
93
 
94
94
  const value = useMemo(
@@ -98,8 +98,9 @@ export function BlazeoConnectionProvider({ children }) {
98
98
  setBaseUrlInput,
99
99
  setConsumerInput,
100
100
  effective,
101
+ connectionOpts,
101
102
  }),
102
- [baseUrlInput, consumerInput, effective]
103
+ [baseUrlInput, consumerInput, effective, connectionOpts]
103
104
  );
104
105
 
105
106
  return (