@blazeo.com/appointment-client 1.0.6 → 1.0.7

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 (60) hide show
  1. package/blazeo.com-appointment-client-1.0.7.tgz +0 -0
  2. package/dist/calendar/buildUnifiedCalendarView.d.ts +8 -0
  3. package/dist/calendar/buildUnifiedCalendarView.js +20 -5
  4. package/dist/calendar/fetchCalendarDetails.d.ts +8 -40
  5. package/dist/calendar/fetchCalendarDetails.js +116 -47
  6. package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +1 -10
  7. package/dist/calendar/fetchCalendarWithOpeningHours.js +34 -15
  8. package/dist/calendar/getAllParticipantOpeningHours.d.ts +4 -1
  9. package/dist/calendar/getAllParticipantOpeningHours.js +6 -1
  10. package/dist/calendar/getOpeningHours.d.ts +4 -1
  11. package/dist/calendar/getOpeningHours.js +2 -2
  12. package/dist/calendar/getParticipantOpeningHours.js +9 -4
  13. package/dist/calendar/getParticipants.d.ts +4 -1
  14. package/dist/calendar/getParticipants.js +6 -1
  15. package/dist/calendar/mapToDesiredResponse.d.ts +70 -0
  16. package/dist/calendar/mapToDesiredResponse.js +99 -0
  17. package/dist/config/applyBlazeoDefaults.js +3 -2
  18. package/dist/config/blazeoClientDefaults.js +2 -2
  19. package/dist/config/ensureBlazeoHttpReady.d.ts +17 -0
  20. package/dist/config/ensureBlazeoHttpReady.js +31 -0
  21. package/dist/config/initializeAppointmentClient.d.ts +4 -0
  22. package/dist/config/initializeAppointmentClient.js +9 -3
  23. package/dist/config/syncBlazeoConnection.d.ts +6 -0
  24. package/dist/config/syncBlazeoConnection.js +18 -0
  25. package/dist/index.d.ts +4 -1
  26. package/dist/index.js +3 -1
  27. package/package.json +1 -1
  28. package/sample/.env.example +5 -0
  29. package/sample/package-lock.json +1 -1
  30. package/sample/src/AllParticipantOpeningHoursTab.jsx +13 -4
  31. package/sample/src/App2.jsx +22 -2
  32. package/sample/src/AvailabilityTab.jsx +8 -3
  33. package/sample/src/BlazeoConnectionSettings.jsx +16 -15
  34. package/sample/src/CreateCalendarTab.jsx +23 -6
  35. package/sample/src/EventTab.jsx +31 -8
  36. package/sample/src/FetchCalendarTab.jsx +70 -44
  37. package/sample/src/OpeningHoursTab.jsx +13 -4
  38. package/sample/src/ParticipantInfoTab.jsx +8 -3
  39. package/sample/src/ParticipantOpeningHoursTab.jsx +17 -7
  40. package/sample/src/ParticipantTab.jsx +13 -4
  41. package/sample/src/blazeoBootstrap.js +30 -0
  42. package/sample/src/blazeoDemoError.js +14 -0
  43. package/sample/src/blazeoPushConnection.js +23 -0
  44. package/sample/src/main.jsx +3 -3
  45. package/sample/vite.config.js +19 -5
  46. package/src/calendar/buildUnifiedCalendarView.ts +28 -5
  47. package/src/calendar/fetchCalendarDetails.ts +316 -226
  48. package/src/calendar/fetchCalendarWithOpeningHours.ts +130 -99
  49. package/src/calendar/getAllParticipantOpeningHours.ts +9 -1
  50. package/src/calendar/getOpeningHours.ts +2 -2
  51. package/src/calendar/getParticipantOpeningHours.ts +14 -5
  52. package/src/calendar/getParticipants.ts +9 -1
  53. package/src/calendar/mapToDesiredResponse.ts +104 -0
  54. package/src/config/applyBlazeoDefaults.ts +3 -2
  55. package/src/config/blazeoClientDefaults.ts +2 -2
  56. package/src/config/ensureBlazeoHttpReady.ts +41 -0
  57. package/src/config/initializeAppointmentClient.ts +9 -3
  58. package/src/config/syncBlazeoConnection.ts +19 -0
  59. package/src/index.ts +7 -1
  60. package/blazeo.com-appointment-client-1.0.6.tgz +0 -0
@@ -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.7",
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=
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "..": {
19
19
  "name": "@blazeo.com/appointment-client",
20
- "version": "1.0.5",
20
+ "version": "1.0.6",
21
21
  "dependencies": {
22
22
  "@blazeo.com/calendar-client": "^1.0.18",
23
23
  "mobx": "^6.13.7",
@@ -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 (
@@ -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
  }