@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.
- package/CHANGELOG.md +37 -0
- package/dist/cjs/client/components/availability-form.js +103 -0
- package/dist/cjs/client/components/create-workspace-form.js +66 -0
- package/dist/cjs/client/components/event-type-form.js +156 -0
- package/dist/cjs/client/components/user-menu.js +3 -24
- package/dist/cjs/client/components/workspace-switcher.js +42 -9
- package/dist/cjs/client/index.js +31 -3
- package/dist/cjs/client/portal-links.js +38 -0
- package/dist/cjs/client/use-scheduling.js +104 -8
- package/dist/cjs/client/use-workspaces.js +41 -0
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +181 -2
- package/dist/cjs/server/http.js +13 -1
- package/dist/cjs/server/resources/ai.js +6 -0
- package/dist/cjs/server/resources/scheduling.js +129 -0
- package/dist/cjs/server/resources/visitor.js +6 -0
- package/dist/cjs/server/resources/workspaces.js +30 -11
- package/dist/esm/client/components/availability-form.js +100 -0
- package/dist/esm/client/components/create-workspace-form.js +63 -0
- package/dist/esm/client/components/event-type-form.js +153 -0
- package/dist/esm/client/components/user-menu.js +3 -24
- package/dist/esm/client/components/workspace-switcher.js +42 -9
- package/dist/esm/client/index.js +17 -1
- package/dist/esm/client/portal-links.js +34 -0
- package/dist/esm/client/use-scheduling.js +98 -8
- package/dist/esm/client/use-workspaces.js +38 -0
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +181 -2
- package/dist/esm/server/http.js +12 -1
- package/dist/esm/server/resources/ai.js +7 -1
- package/dist/esm/server/resources/scheduling.js +129 -0
- package/dist/esm/server/resources/visitor.js +7 -1
- package/dist/esm/server/resources/workspaces.js +30 -11
- package/dist/types/client/components/availability-form.d.ts +33 -0
- package/dist/types/client/components/create-workspace-form.d.ts +32 -0
- package/dist/types/client/components/event-type-form.d.ts +44 -0
- package/dist/types/client/components/workspace-switcher.d.ts +9 -9
- package/dist/types/client/index.d.ts +10 -1
- package/dist/types/client/portal-links.d.ts +22 -0
- package/dist/types/client/use-scheduling.d.ts +93 -1
- package/dist/types/client/use-workspaces.d.ts +27 -0
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/http.d.ts +35 -0
- package/dist/types/server/resources/scheduling.d.ts +131 -1
- package/dist/types/server/resources/workspaces.d.ts +28 -0
- package/dist/types/types.d.ts +60 -0
- package/package.json +1 -1
package/dist/cjs/client/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
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
|
-
*
|
|
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
|
-
* **
|
|
80
|
-
* slot is
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
|
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
|
|
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
|
+
}
|
|
@@ -741,8 +741,8 @@ function createConnectHandler(options) {
|
|
|
741
741
|
*/
|
|
742
742
|
"STORE": async (request) => storeRoute(request),
|
|
743
743
|
/**
|
|
744
|
-
* The workspace switcher's
|
|
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);
|
package/dist/cjs/server/http.js
CHANGED
|
@@ -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
|
});
|