@cosmoops/connect-sdk 0.37.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cjs/client/components/availability-form.js +103 -0
  3. package/dist/cjs/client/components/create-workspace-form.js +66 -0
  4. package/dist/cjs/client/components/event-type-form.js +156 -0
  5. package/dist/cjs/client/components/user-menu.js +3 -24
  6. package/dist/cjs/client/components/workspace-switcher.js +42 -9
  7. package/dist/cjs/client/index.js +31 -3
  8. package/dist/cjs/client/portal-links.js +38 -0
  9. package/dist/cjs/client/use-scheduling.js +104 -8
  10. package/dist/cjs/client/use-workspaces.js +41 -0
  11. package/dist/cjs/server/client.js +1 -1
  12. package/dist/cjs/server/handler.js +181 -2
  13. package/dist/cjs/server/http.js +13 -1
  14. package/dist/cjs/server/resources/ai.js +6 -0
  15. package/dist/cjs/server/resources/scheduling.js +129 -0
  16. package/dist/cjs/server/resources/visitor.js +6 -0
  17. package/dist/cjs/server/resources/workspaces.js +30 -11
  18. package/dist/esm/client/components/availability-form.js +100 -0
  19. package/dist/esm/client/components/create-workspace-form.js +63 -0
  20. package/dist/esm/client/components/event-type-form.js +153 -0
  21. package/dist/esm/client/components/user-menu.js +3 -24
  22. package/dist/esm/client/components/workspace-switcher.js +42 -9
  23. package/dist/esm/client/index.js +17 -1
  24. package/dist/esm/client/portal-links.js +34 -0
  25. package/dist/esm/client/use-scheduling.js +98 -8
  26. package/dist/esm/client/use-workspaces.js +38 -0
  27. package/dist/esm/server/client.js +1 -1
  28. package/dist/esm/server/handler.js +181 -2
  29. package/dist/esm/server/http.js +12 -1
  30. package/dist/esm/server/resources/ai.js +7 -1
  31. package/dist/esm/server/resources/scheduling.js +129 -0
  32. package/dist/esm/server/resources/visitor.js +7 -1
  33. package/dist/esm/server/resources/workspaces.js +30 -11
  34. package/dist/types/client/components/availability-form.d.ts +33 -0
  35. package/dist/types/client/components/create-workspace-form.d.ts +32 -0
  36. package/dist/types/client/components/event-type-form.d.ts +44 -0
  37. package/dist/types/client/components/workspace-switcher.d.ts +9 -9
  38. package/dist/types/client/index.d.ts +10 -1
  39. package/dist/types/client/portal-links.d.ts +22 -0
  40. package/dist/types/client/use-scheduling.d.ts +93 -1
  41. package/dist/types/client/use-workspaces.d.ts +27 -0
  42. package/dist/types/server/client.d.ts +1 -1
  43. package/dist/types/server/http.d.ts +35 -0
  44. package/dist/types/server/resources/scheduling.d.ts +131 -1
  45. package/dist/types/server/resources/workspaces.d.ts +28 -0
  46. package/dist/types/types.d.ts +60 -0
  47. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.useConnectPush = exports.useActivePath = exports.ConnectSideNav = exports.ConnectSidebarNav = exports.RAIL_EXPANDED = exports.RAIL_COLLAPSED = exports.sidebarRowClass = exports.useConnectShell = exports.ConnectShellProvider = exports.ConnectSidebarRow = exports.ConnectSidebarFooter = exports.ConnectSidebarBody = exports.ConnectSidebarHeader = exports.ConnectSidebar = exports.AppShell = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectProfileModal = exports.ConnectUserButton = exports.ConnectOneTap = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.ConnectGrievanceNotice = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectIntegrations = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectCompanyProfile = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectPlatformAccess = exports.useConnectAudience = exports.useConnectWorkspaceType = exports.useConnectUser = exports.useConnectOptional = exports.useConnect = exports.ConnectProvider = void 0;
5
- exports.ConnectFilterTabs = exports.ConnectCursorPagination = exports.ConnectConfirmDialog = exports.ConnectLinkButton = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.ConnectPlatformGate = exports.ConnectWorkspaceTypeGate = exports.useConnectFeature = exports.ConnectFeatureGate = exports.useConnectAccessGate = exports.ConnectAccessGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectIntegrationsModal = exports.AI_TONE_DOT = exports.AI_TONE_CHIP = exports.AI_EMPTY_VALUE = exports.aiStatusTone = exports.isNumericAiFormat = exports.formatAiValue = exports.AiSources = exports.AiSteps = exports.AiDetails = exports.AiTimeline = exports.AiMetrics = exports.AiChart = exports.AiTable = exports.ConnectAskAi = exports.ConnectAppsGrid = exports.visitorSlotOfferFrom = exports.readVisitorStream = exports.ConnectVisitorChat = exports.isLinkableConnectScreen = exports.matchConnectScreen = exports.filterNavForAudience = exports.gateExemptPaths = exports.isNavItemActive = exports.resolveActivePath = exports.normalizePath = exports.flattenNav = exports.normalizeNav = void 0;
6
- exports.zonedInstant = exports.dayColumns = exports.layoutBookings = exports.useBookingActions = exports.useBookings = exports.useAvailableSlots = exports.useBookableEventTypes = exports.ConnectBookingDetail = exports.ConnectBookingDialog = exports.ConnectBookingCalendar = exports.useConnectWorkspaceMembers = exports.useConnectWorkspaces = exports.ConnectWorkspaceSwitcher = exports.resolvePlanGroup = exports.groupPlansByTier = exports.isFreePlan = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.checkoutSubscriptionId = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = void 0;
4
+ exports.ConnectSideNav = exports.ConnectSidebarNav = exports.RAIL_EXPANDED = exports.RAIL_COLLAPSED = exports.sidebarRowClass = exports.useConnectShell = exports.ConnectShellProvider = exports.ConnectSidebarRow = exports.ConnectSidebarFooter = exports.ConnectSidebarBody = exports.ConnectSidebarHeader = exports.ConnectSidebar = exports.AppShell = exports.useConnectPortalUrl = exports.connectPortalUrl = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectProfileModal = exports.ConnectUserButton = exports.ConnectOneTap = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.ConnectGrievanceNotice = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectIntegrations = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectCompanyProfile = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectPlatformAccess = exports.useConnectAudience = exports.useConnectWorkspaceType = exports.useConnectUser = exports.useConnectOptional = exports.useConnect = exports.ConnectProvider = void 0;
5
+ exports.ConnectConfirmDialog = exports.ConnectLinkButton = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.ConnectPlatformGate = exports.ConnectWorkspaceTypeGate = exports.useConnectFeature = exports.ConnectFeatureGate = exports.useConnectAccessGate = exports.ConnectAccessGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectIntegrationsModal = exports.AI_TONE_DOT = exports.AI_TONE_CHIP = exports.AI_EMPTY_VALUE = exports.aiStatusTone = exports.isNumericAiFormat = exports.formatAiValue = exports.AiSources = exports.AiSteps = exports.AiDetails = exports.AiTimeline = exports.AiMetrics = exports.AiChart = exports.AiTable = exports.ConnectAskAi = exports.ConnectAppsGrid = exports.visitorSlotOfferFrom = exports.readVisitorStream = exports.ConnectVisitorChat = exports.isLinkableConnectScreen = exports.matchConnectScreen = exports.filterNavForAudience = exports.gateExemptPaths = exports.isNavItemActive = exports.resolveActivePath = exports.normalizePath = exports.flattenNav = exports.normalizeNav = exports.useConnectPush = exports.useActivePath = void 0;
6
+ exports.zonedInstant = exports.dayColumns = exports.layoutBookings = exports.useBookingActions = exports.useBookings = exports.useAvailableSlots = exports.useUpdateEventType = exports.useCreateEventType = exports.useManageableEventTypes = exports.useUpdateAvailabilitySchedule = exports.useCreateAvailabilitySchedule = exports.useAvailabilitySchedules = exports.useBookableEventTypes = exports.ConnectEventTypeFormModal = exports.ConnectAvailabilityFormModal = exports.ConnectBookingDetail = exports.ConnectBookingDialog = exports.ConnectBookingCalendar = exports.useCreateWorkspace = exports.ConnectCreateWorkspaceModal = exports.useConnectWorkspaceMembers = exports.useConnectWorkspaces = exports.ConnectWorkspaceSwitcher = exports.resolvePlanGroup = exports.groupPlansByTier = exports.isFreePlan = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.checkoutSubscriptionId = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectFilterTabs = exports.ConnectCursorPagination = void 0;
7
7
  /**
8
8
  * Browser half of `@cosmoops/connect-sdk` - React components and hooks.
9
9
  *
@@ -69,6 +69,13 @@ Object.defineProperty(exports, "ConnectSignOutButton", { enumerable: true, get:
69
69
  // `ConnectUserMenu` was the only thing that could render it.
70
70
  var account_sessions_js_1 = require("./components/account-sessions.js");
71
71
  Object.defineProperty(exports, "ConnectAccountSessions", { enumerable: true, get: function () { return account_sessions_js_1.ConnectAccountSessions; } });
72
+ // A link to *any* Connect portal page that offers a "Back to <your app>" way
73
+ // home - what `ConnectProfileButton` builds for `/profile` internally, made
74
+ // reusable for a host linking out to a different Connect page (e.g.
75
+ // `/calendar/settings`) on its own.
76
+ var portal_links_js_1 = require("./portal-links.js");
77
+ Object.defineProperty(exports, "connectPortalUrl", { enumerable: true, get: function () { return portal_links_js_1.connectPortalUrl; } });
78
+ Object.defineProperty(exports, "useConnectPortalUrl", { enumerable: true, get: function () { return portal_links_js_1.useConnectPortalUrl; } });
72
79
  // The frame the rest of these components live inside. Exported next to the
73
80
  // pieces it composes rather than in a section of its own - an integration
74
81
  // reaching for a sidebar wants to find the launcher and the account menu in
@@ -257,13 +264,34 @@ Object.defineProperty(exports, "ConnectWorkspaceSwitcher", { enumerable: true, g
257
264
  var provider_js_2 = require("./provider.js");
258
265
  Object.defineProperty(exports, "useConnectWorkspaces", { enumerable: true, get: function () { return provider_js_2.useConnectWorkspaces; } });
259
266
  Object.defineProperty(exports, "useConnectWorkspaceMembers", { enumerable: true, get: function () { return provider_js_2.useConnectWorkspaceMembers; } });
267
+ // The write half of the workspace switcher - opening another workspace under
268
+ // the signed-in user's own tenant, from inside a host application rather
269
+ // than a link-out to Connect's own "Create workspace" button.
270
+ var create_workspace_form_js_1 = require("./components/create-workspace-form.js");
271
+ Object.defineProperty(exports, "ConnectCreateWorkspaceModal", { enumerable: true, get: function () { return create_workspace_form_js_1.ConnectCreateWorkspaceModal; } });
272
+ var use_workspaces_js_1 = require("./use-workspaces.js");
273
+ Object.defineProperty(exports, "useCreateWorkspace", { enumerable: true, get: function () { return use_workspaces_js_1.useCreateWorkspace; } });
260
274
  var booking_calendar_js_1 = require("./components/booking-calendar.js");
261
275
  Object.defineProperty(exports, "ConnectBookingCalendar", { enumerable: true, get: function () { return booking_calendar_js_1.ConnectBookingCalendar; } });
262
276
  var booking_dialogs_js_1 = require("./components/booking-dialogs.js");
263
277
  Object.defineProperty(exports, "ConnectBookingDialog", { enumerable: true, get: function () { return booking_dialogs_js_1.ConnectBookingDialog; } });
264
278
  Object.defineProperty(exports, "ConnectBookingDetail", { enumerable: true, get: function () { return booking_dialogs_js_1.ConnectBookingDetail; } });
279
+ // The write half of scheduling - a workspace's own first-time setup, not a
280
+ // partner integration's booking flow. `ConnectAvailabilityFormModal` and
281
+ // `ConnectEventTypeFormModal` give a host application a real inline setup
282
+ // step instead of a link-out to Connect's own Calendar settings.
283
+ var availability_form_js_1 = require("./components/availability-form.js");
284
+ Object.defineProperty(exports, "ConnectAvailabilityFormModal", { enumerable: true, get: function () { return availability_form_js_1.ConnectAvailabilityFormModal; } });
285
+ var event_type_form_js_1 = require("./components/event-type-form.js");
286
+ Object.defineProperty(exports, "ConnectEventTypeFormModal", { enumerable: true, get: function () { return event_type_form_js_1.ConnectEventTypeFormModal; } });
265
287
  var use_scheduling_js_1 = require("./use-scheduling.js");
266
288
  Object.defineProperty(exports, "useBookableEventTypes", { enumerable: true, get: function () { return use_scheduling_js_1.useBookableEventTypes; } });
289
+ Object.defineProperty(exports, "useAvailabilitySchedules", { enumerable: true, get: function () { return use_scheduling_js_1.useAvailabilitySchedules; } });
290
+ Object.defineProperty(exports, "useCreateAvailabilitySchedule", { enumerable: true, get: function () { return use_scheduling_js_1.useCreateAvailabilitySchedule; } });
291
+ Object.defineProperty(exports, "useUpdateAvailabilitySchedule", { enumerable: true, get: function () { return use_scheduling_js_1.useUpdateAvailabilitySchedule; } });
292
+ Object.defineProperty(exports, "useManageableEventTypes", { enumerable: true, get: function () { return use_scheduling_js_1.useManageableEventTypes; } });
293
+ Object.defineProperty(exports, "useCreateEventType", { enumerable: true, get: function () { return use_scheduling_js_1.useCreateEventType; } });
294
+ Object.defineProperty(exports, "useUpdateEventType", { enumerable: true, get: function () { return use_scheduling_js_1.useUpdateEventType; } });
267
295
  Object.defineProperty(exports, "useAvailableSlots", { enumerable: true, get: function () { return use_scheduling_js_1.useAvailableSlots; } });
268
296
  Object.defineProperty(exports, "useBookings", { enumerable: true, get: function () { return use_scheduling_js_1.useBookings; } });
269
297
  Object.defineProperty(exports, "useBookingActions", { enumerable: true, get: function () { return use_scheduling_js_1.useBookingActions; } });
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.connectPortalUrl = connectPortalUrl;
5
+ exports.useConnectPortalUrl = useConnectPortalUrl;
6
+ const provider_js_1 = require("./provider.js");
7
+ /**
8
+ * Any Connect portal page, with the context it needs to offer a way back
9
+ * here - `connectPortalUrl(appUrl, clientId, "/profile")`,
10
+ * `connectPortalUrl(appUrl, clientId, "/calendar/settings")`, and so on.
11
+ *
12
+ * Connect answers any of these paths for a signed-in user on the bare URL -
13
+ * these two params only add the "Back to <your app>" banner
14
+ * (`ApplicationReturnBar` on Connect's side). It validates `return_to`
15
+ * against this `client_id`'s registered redirect origins and silently drops
16
+ * the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
17
+ * pairing degrades to what linking there did before rather than erroring.
18
+ *
19
+ * `window.location.href` rather than a configured home URL: the point is to
20
+ * come back to the page they left, and only the browser knows which one that
21
+ * is.
22
+ */
23
+ function connectPortalUrl(appUrl, clientId, path) {
24
+ const url = new URL(path, appUrl);
25
+ url.searchParams.set("client_id", clientId);
26
+ if (typeof window !== "undefined") {
27
+ url.searchParams.set("return_to", window.location.href);
28
+ }
29
+ return url.toString();
30
+ }
31
+ /**
32
+ * `connectPortalUrl`, with `appUrl`/`clientId` read from the provider instead
33
+ * of threaded through by every caller.
34
+ */
35
+ function useConnectPortalUrl(path) {
36
+ const { appUrl, clientId } = (0, provider_js_1.useConnect)();
37
+ return connectPortalUrl(appUrl, clientId, path);
38
+ }
@@ -2,6 +2,12 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.useBookableEventTypes = useBookableEventTypes;
5
+ exports.useAvailabilitySchedules = useAvailabilitySchedules;
6
+ exports.useManageableEventTypes = useManageableEventTypes;
7
+ exports.useCreateAvailabilitySchedule = useCreateAvailabilitySchedule;
8
+ exports.useUpdateAvailabilitySchedule = useUpdateAvailabilitySchedule;
9
+ exports.useCreateEventType = useCreateEventType;
10
+ exports.useUpdateEventType = useUpdateEventType;
5
11
  exports.useAvailableSlots = useAvailableSlots;
6
12
  exports.useBookings = useBookings;
7
13
  exports.useBookingActions = useBookingActions;
@@ -21,6 +27,82 @@ function useBookableEventTypes() {
21
27
  const { data, isLoading, error } = (0, provider_js_1.useConnectResource)("/scheduling/event-types");
22
28
  return { eventTypes: data?.eventTypes ?? [], isLoading, error };
23
29
  }
30
+ /**
31
+ * The working-hours templates an appointment type can point at.
32
+ *
33
+ * Read this before offering a "set up availability" step - a workspace that
34
+ * already has one does not need a second "Working hours" from your UI.
35
+ */
36
+ function useAvailabilitySchedules() {
37
+ const { data, isLoading, error } = (0, provider_js_1.useConnectResource)("/scheduling/schedules");
38
+ return { schedules: data?.schedules ?? [], isLoading, error };
39
+ }
40
+ /**
41
+ * The richer read `ConnectEventTypeFormModal`'s edit mode needs - everything
42
+ * `useBookableEventTypes`'s booking catalogue deliberately withholds. See
43
+ * the bridge route's own doc comment for why this is a separate read rather
44
+ * than a wider response on that one. Includes inactive rows.
45
+ */
46
+ function useManageableEventTypes() {
47
+ const { data, isLoading, error, refetch } = (0, provider_js_1.useConnectResource)("/scheduling/event-types/manage");
48
+ return { eventTypes: data?.eventTypes ?? [], isLoading, error, refetch };
49
+ }
50
+ /**
51
+ * Gives a workspace its first working hours.
52
+ *
53
+ * **Not optimistic** - same posture as `useBookingActions`: this is one of
54
+ * the two writes a host's first-run setup makes, and a modal that closes on
55
+ * a failed create is worse than one that stays open with the reason shown.
56
+ */
57
+ function useCreateAvailabilitySchedule() {
58
+ const { run, isPending, error, clearError } = useResourceAction();
59
+ return {
60
+ isPending,
61
+ error,
62
+ clearError,
63
+ createSchedule: (input) => run("/scheduling/schedules", input),
64
+ };
65
+ }
66
+ /** Edits an existing availability schedule. Every field optional - send only what changed. */
67
+ function useUpdateAvailabilitySchedule() {
68
+ const { run, isPending, error, clearError } = useResourceAction();
69
+ return {
70
+ isPending,
71
+ error,
72
+ clearError,
73
+ updateSchedule: (input) => run("/scheduling/schedules/update", input),
74
+ };
75
+ }
76
+ /**
77
+ * Creates the first thing a workspace can be booked for.
78
+ *
79
+ * Host is a specific member, never a team - there is no groups read in this
80
+ * bridge, so a round-robin team host stays a Console-only capability
81
+ * reachable by following "Manage in Connect".
82
+ */
83
+ function useCreateEventType() {
84
+ const { run, isPending, error, clearError } = useResourceAction();
85
+ return {
86
+ isPending,
87
+ error,
88
+ clearError,
89
+ createEventType: (input) => run("/scheduling/event-types", input),
90
+ };
91
+ }
92
+ /**
93
+ * Edits an existing appointment type. Every field optional - send only what
94
+ * changed. `hostMemberId` omitted leaves the host untouched, including a
95
+ * *group* host this surface cannot represent.
96
+ */
97
+ function useUpdateEventType() {
98
+ const { run, isPending, error, clearError } = useResourceAction();
99
+ return {
100
+ isPending,
101
+ error,
102
+ clearError,
103
+ updateEventType: (input) => run("/scheduling/event-types/update", input),
104
+ };
105
+ }
24
106
  /**
25
107
  * Free times for one appointment type.
26
108
  *
@@ -74,15 +156,16 @@ function useBookings(query) {
74
156
  };
75
157
  }
76
158
  /**
77
- * Book, move and cancel.
159
+ * The isPending/error bookkeeping every write in this file shares, factored
160
+ * out once `useCreateAvailabilitySchedule` and `useCreateEventType` needed
161
+ * the identical shape `useBookingActions` already had.
78
162
  *
79
- * **None of these is optimistic**, and that is not an oversight. Whether a
80
- * slot is still free is a question only the server can answer, inside the
81
- * transaction that decides it - two people can be holding the same minute
82
- * right now. Painting a booking as made and withdrawing it is exactly the lie
83
- * the two-phase hold exists to prevent.
163
+ * **Never optimistic**, and that is not an oversight for any caller of this:
164
+ * whether a slot is free, or a schedule name collides, is a question only the
165
+ * server can answer. Painting the result before it comes back is exactly the
166
+ * lie a two-phase hold (or a modal that closes early) would tell.
84
167
  */
85
- function useBookingActions() {
168
+ function useResourceAction() {
86
169
  const { mutateResource } = (0, provider_js_1.useConnect)();
87
170
  const [isPending, setPending] = (0, react_1.useState)(false);
88
171
  const [error, setError] = (0, react_1.useState)(null);
@@ -102,10 +185,23 @@ function useBookingActions() {
102
185
  setPending(false);
103
186
  }
104
187
  }, [mutateResource]);
188
+ return { isPending, error, clearError: () => setError(null), run };
189
+ }
190
+ /**
191
+ * Book, move and cancel.
192
+ *
193
+ * **None of these is optimistic**, and that is not an oversight. Whether a
194
+ * slot is still free is a question only the server can answer, inside the
195
+ * transaction that decides it - two people can be holding the same minute
196
+ * right now. Painting a booking as made and withdrawing it is exactly the lie
197
+ * the two-phase hold exists to prevent.
198
+ */
199
+ function useBookingActions() {
200
+ const { run, isPending, error, clearError } = useResourceAction();
105
201
  return {
106
202
  isPending,
107
203
  error,
108
- clearError: () => setError(null),
204
+ clearError,
109
205
  createBooking: (input) => run("/scheduling/bookings", input),
110
206
  rescheduleBooking: (input) => run("/scheduling/bookings/reschedule", input).then((result) => result?.booking ?? null),
111
207
  cancelBooking: (input) => run("/scheduling/bookings/cancel", input),
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useCreateWorkspace = useCreateWorkspace;
5
+ const react_1 = require("react");
6
+ const provider_js_1 = require("./provider.js");
7
+ /**
8
+ * Opens another workspace under the tenant the signed-in user already owns.
9
+ *
10
+ * **Not optimistic**, same posture as every write in `use-scheduling.ts`:
11
+ * whether the tenant's plan still has room for another workspace is a
12
+ * question only the server can answer, and a modal that closes before that
13
+ * answer comes back is the lie a two-phase write exists to avoid.
14
+ *
15
+ * The result is a `UserWorkspace` row with `isActive`/`subscribed` both
16
+ * `false` - creating does not switch the session to it, and the workspace has
17
+ * no subscription yet. Pair with `<ConnectWorkspaceSwitcher>`'s own
18
+ * `mutateResource("/workspaces/switch", ...)` call to move into it, guarded
19
+ * on `inAudience` the same way that component's `selectable()` already is.
20
+ */
21
+ function useCreateWorkspace() {
22
+ const { mutateResource } = (0, provider_js_1.useConnect)();
23
+ const [isPending, setPending] = (0, react_1.useState)(false);
24
+ const [error, setError] = (0, react_1.useState)(null);
25
+ const createWorkspace = (0, react_1.useCallback)(async (input) => {
26
+ setPending(true);
27
+ setError(null);
28
+ try {
29
+ const result = await mutateResource("/workspaces", input);
30
+ return result.workspace;
31
+ }
32
+ catch (cause) {
33
+ setError(cause instanceof Error ? cause.message : "Could not create that workspace.");
34
+ return null;
35
+ }
36
+ finally {
37
+ setPending(false);
38
+ }
39
+ }, [mutateResource]);
40
+ return { isPending, error, clearError: () => setError(null), createWorkspace };
41
+ }
@@ -179,4 +179,4 @@ class ConnectClient {
179
179
  }
180
180
  exports.ConnectClient = ConnectClient;
181
181
  /** Kept in step with `package.json` by `sdk.test.ts`. */
182
- exports.SDK_VERSION = "0.37.0";
182
+ exports.SDK_VERSION = "0.40.0";
@@ -741,8 +741,8 @@ function createConnectHandler(options) {
741
741
  */
742
742
  "STORE": async (request) => storeRoute(request),
743
743
  /**
744
- * The workspace switcher's three reads, forwarded with the caller's own
745
- * session token.
744
+ * The workspace switcher's reads and writes, forwarded with the caller's
745
+ * own session token.
746
746
  *
747
747
  * `POST /workspaces/switch` is the only bridge route that *changes the
748
748
  * caller's session*, so it writes the new token back through the same
@@ -754,6 +754,14 @@ function createConnectHandler(options) {
754
754
  * A partner with no `session.write` cannot re-persist a rotated token, so
755
755
  * switching is simply absent for them rather than half-working - the same
756
756
  * treatment `/signin` and `/callback` already get.
757
+ *
758
+ * `POST /workspaces` opens another workspace under the caller's own
759
+ * tenant and does not rotate anything - it does not switch the session,
760
+ * same as Connect's own "Create workspace" button. Every guard
761
+ * (ownership, the plan's workspace allowance, the business-email-domain
762
+ * rule) lives server-side in `CreateWorkspaceUseCase`; this route is a
763
+ * plain forward with the caller's session token, exactly like the reads
764
+ * beside it.
757
765
  */
758
766
  /*
759
767
  * Push notifications - the browser half, and deliberately only that half.
@@ -810,6 +818,20 @@ function createConnectHandler(options) {
810
818
  return json(await client.push.revokeDevice(body.id, { asUser: token }));
811
819
  },
812
820
  "GET /workspaces": async (request) => json({ workspaces: await client.workspaces.list({ asUser: await requireToken(request) }) }),
821
+ "POST /workspaces": async (request) => {
822
+ const token = await requireToken(request);
823
+ const body = (await request.json().catch(() => null));
824
+ if (typeof body?.name !== "string" || (body.type !== "individual" && body.type !== "business")) {
825
+ throw new errors_js_1.ConnectError({
826
+ status: 400,
827
+ code: "validation_error",
828
+ message: "name and type are required",
829
+ endpoint: "POST /workspaces",
830
+ });
831
+ }
832
+ const workspace = await client.workspaces.create({ name: body.name, type: body.type }, { asUser: token });
833
+ return json({ workspace });
834
+ },
813
835
  "GET /workspaces/members": async (request) => json({ members: await client.workspaces.members({ asUser: await requireToken(request) }) }),
814
836
  "POST /workspaces/switch": async (request) => {
815
837
  const token = await requireToken(request);
@@ -853,6 +875,163 @@ function createConnectHandler(options) {
853
875
  const asUser = await requireToken(request);
854
876
  return json({ eventTypes: await client.scheduling.listEventTypes(workspaceId, { asUser }) });
855
877
  },
878
+ /*
879
+ * Creates the first thing a workspace can be booked for - a browser-side
880
+ * counterpart to `POST /scheduling/event-types` above, for a host's own
881
+ * inline setup UI (`ConnectEventTypeFormModal`). Host is a specific
882
+ * member, never a team - there is no groups read in this bridge, so a
883
+ * round-robin team host stays a Console-only capability.
884
+ */
885
+ "POST /scheduling/event-types": async (request) => {
886
+ const { workspaceId } = await requireWorkspace(request);
887
+ const asUser = await requireToken(request);
888
+ const body = (await request.json().catch(() => null));
889
+ if (typeof body?.name !== "string" ||
890
+ typeof body.durationMinutes !== "number" ||
891
+ typeof body.hostMemberId !== "string" ||
892
+ typeof body.scheduleId !== "string" ||
893
+ typeof body.location?.kind !== "string") {
894
+ throw new errors_js_1.ConnectError({
895
+ status: 400,
896
+ code: "validation_error",
897
+ message: "name, durationMinutes, hostMemberId, scheduleId and location are required",
898
+ endpoint: "/scheduling/event-types",
899
+ });
900
+ }
901
+ return json(await client.scheduling.createEventType({
902
+ workspaceId,
903
+ name: body.name,
904
+ description: typeof body.description === "string" ? body.description : null,
905
+ durationMinutes: body.durationMinutes,
906
+ bufferBeforeMinutes: typeof body.bufferBeforeMinutes === "number" ? body.bufferBeforeMinutes : undefined,
907
+ bufferAfterMinutes: typeof body.bufferAfterMinutes === "number" ? body.bufferAfterMinutes : undefined,
908
+ hostMemberId: body.hostMemberId,
909
+ scheduleId: body.scheduleId,
910
+ location: {
911
+ kind: body.location.kind,
912
+ value: typeof body.location.value === "string" ? body.location.value : null,
913
+ },
914
+ publicBookingEnabled: typeof body.publicBookingEnabled === "boolean" ? body.publicBookingEnabled : undefined,
915
+ asUser,
916
+ }), 201);
917
+ },
918
+ /*
919
+ * The richer read `ConnectEventTypeFormModal`'s edit mode needs - see
920
+ * `listManageableEventTypes`'s own doc comment for why this is a separate
921
+ * route from the catalogue above rather than a wider response on it.
922
+ */
923
+ "GET /scheduling/event-types/manage": async (request) => {
924
+ const { workspaceId } = await requireWorkspace(request);
925
+ const asUser = await requireToken(request);
926
+ return json({
927
+ eventTypes: await client.scheduling.listManageableEventTypes(workspaceId, { asUser }),
928
+ });
929
+ },
930
+ /*
931
+ * A fixed path with the id in the body, not `/scheduling/event-types/{id}` -
932
+ * this table is matched by exact string, the same reason the booking
933
+ * actions below take their id this way rather than as a path segment.
934
+ */
935
+ "POST /scheduling/event-types/update": async (request) => {
936
+ const { workspaceId } = await requireWorkspace(request);
937
+ const asUser = await requireToken(request);
938
+ const body = (await request.json().catch(() => null));
939
+ if (typeof body?.eventTypeId !== "string") {
940
+ throw new errors_js_1.ConnectError({
941
+ status: 400,
942
+ code: "validation_error",
943
+ message: "eventTypeId is required",
944
+ endpoint: "/scheduling/event-types/update",
945
+ });
946
+ }
947
+ return json(await client.scheduling.updateEventType({
948
+ workspaceId,
949
+ eventTypeId: body.eventTypeId,
950
+ name: typeof body.name === "string" ? body.name : undefined,
951
+ description: typeof body.description === "string" || body.description === null
952
+ ? body.description
953
+ : undefined,
954
+ durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : undefined,
955
+ bufferBeforeMinutes: typeof body.bufferBeforeMinutes === "number" ? body.bufferBeforeMinutes : undefined,
956
+ bufferAfterMinutes: typeof body.bufferAfterMinutes === "number" ? body.bufferAfterMinutes : undefined,
957
+ hostMemberId: typeof body.hostMemberId === "string" ? body.hostMemberId : undefined,
958
+ scheduleId: typeof body.scheduleId === "string" ? body.scheduleId : undefined,
959
+ location: body.location && typeof body.location.kind === "string"
960
+ ? {
961
+ kind: body.location.kind,
962
+ value: typeof body.location.value === "string" ? body.location.value : null,
963
+ }
964
+ : undefined,
965
+ publicBookingEnabled: typeof body.publicBookingEnabled === "boolean" ? body.publicBookingEnabled : undefined,
966
+ active: typeof body.active === "boolean" ? body.active : undefined,
967
+ asUser,
968
+ }));
969
+ },
970
+ /*
971
+ * The working-hours templates an appointment type can point at - read
972
+ * before a setup UI offers to create one, so it can tell "none yet" from
973
+ * "already set up".
974
+ */
975
+ "GET /scheduling/schedules": async (request) => {
976
+ const { workspaceId } = await requireWorkspace(request);
977
+ const asUser = await requireToken(request);
978
+ return json({ schedules: await client.scheduling.listSchedules(workspaceId, { asUser }) });
979
+ },
980
+ /**
981
+ * Gives a workspace its first working hours - the browser-side
982
+ * counterpart to `POST /scheduling/schedules` above, for
983
+ * `ConnectAvailabilityFormModal`.
984
+ */
985
+ "POST /scheduling/schedules": async (request) => {
986
+ const { workspaceId } = await requireWorkspace(request);
987
+ const asUser = await requireToken(request);
988
+ const body = (await request.json().catch(() => null));
989
+ if (typeof body?.name !== "string" ||
990
+ typeof body.timezone !== "string" ||
991
+ typeof body.weeklyHours !== "object" ||
992
+ body.weeklyHours === null) {
993
+ throw new errors_js_1.ConnectError({
994
+ status: 400,
995
+ code: "validation_error",
996
+ message: "name, timezone and weeklyHours are required",
997
+ endpoint: "/scheduling/schedules",
998
+ });
999
+ }
1000
+ return json(await client.scheduling.createSchedule({
1001
+ workspaceId,
1002
+ name: body.name,
1003
+ timezone: body.timezone,
1004
+ weeklyHours: body.weeklyHours,
1005
+ asUser,
1006
+ }), 201);
1007
+ },
1008
+ /*
1009
+ * A fixed path with the id in the body, same reasoning as the event
1010
+ * type's own `update` route above.
1011
+ */
1012
+ "POST /scheduling/schedules/update": async (request) => {
1013
+ const { workspaceId } = await requireWorkspace(request);
1014
+ const asUser = await requireToken(request);
1015
+ const body = (await request.json().catch(() => null));
1016
+ if (typeof body?.scheduleId !== "string") {
1017
+ throw new errors_js_1.ConnectError({
1018
+ status: 400,
1019
+ code: "validation_error",
1020
+ message: "scheduleId is required",
1021
+ endpoint: "/scheduling/schedules/update",
1022
+ });
1023
+ }
1024
+ return json(await client.scheduling.updateSchedule({
1025
+ workspaceId,
1026
+ scheduleId: body.scheduleId,
1027
+ name: typeof body.name === "string" ? body.name : undefined,
1028
+ timezone: typeof body.timezone === "string" ? body.timezone : undefined,
1029
+ weeklyHours: typeof body.weeklyHours === "object" && body.weeklyHours !== null
1030
+ ? body.weeklyHours
1031
+ : undefined,
1032
+ asUser,
1033
+ }));
1034
+ },
856
1035
  "GET /scheduling/slots": async (request) => {
857
1036
  const { workspaceId } = await requireWorkspace(request);
858
1037
  const asUser = await requireToken(request);
@@ -1,8 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GENERATION_TIMEOUT_MS = void 0;
3
4
  exports.pathSegment = pathSegment;
4
5
  exports.request = request;
5
6
  const errors_js_1 = require("../errors.js");
7
+ /**
8
+ * How long a *generating* endpoint may take to send its headers.
9
+ *
10
+ * One number, named once, and it is not a guess: it is what
11
+ * `POST /visitor/chat` and `POST /ai/chat` declare as their `maxDuration` on
12
+ * the far side. A client bound shorter than the route's own ceiling cannot be
13
+ * right - it can only abort work Connect is still allowed to be doing - and
14
+ * the two numbers drifting apart is exactly how a 20s client came to be
15
+ * fronting a 60s route.
16
+ */
17
+ exports.GENERATION_TIMEOUT_MS = 60_000;
6
18
  const RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);
7
19
  /**
8
20
  * One id, safe to interpolate into a request path.
@@ -149,7 +161,7 @@ async function request(options, spec) {
149
161
  // that has actually started to run as long as it needs. A caller that
150
162
  // wants to bound the stream itself passes `spec.signal`.
151
163
  const timeoutController = new AbortController();
152
- const timer = setTimeout(() => timeoutController.abort(), options.timeoutMs);
164
+ const timer = setTimeout(() => timeoutController.abort(), spec.timeoutMs ?? options.timeoutMs);
153
165
  const signal = spec.signal
154
166
  ? AbortSignal.any([spec.signal, timeoutController.signal])
155
167
  : timeoutController.signal;
@@ -43,6 +43,12 @@ class AiResource {
43
43
  ...(input.screenContext ? { screenContext: input.screenContext } : {}),
44
44
  },
45
45
  headers: (0, acting_as_js_1.actingAs)(input.asUser),
46
+ /*
47
+ * Matches the route's own `maxDuration`. This endpoint does not send
48
+ * headers until the model's first token, so the default 20s bound was
49
+ * aborting any turn that opened with a tool call - see `RequestSpec.timeoutMs`.
50
+ */
51
+ timeoutMs: http_js_1.GENERATION_TIMEOUT_MS,
46
52
  raw: true,
47
53
  signal: options.signal,
48
54
  });