@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
|
@@ -31,6 +31,135 @@ export class SchedulingResource {
|
|
|
31
31
|
// `undefined` past every status check in between.
|
|
32
32
|
return result.eventTypes ?? [];
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The working-hours templates an appointment type can point at.
|
|
36
|
+
*
|
|
37
|
+
* Read this before offering to create one - a workspace that already has
|
|
38
|
+
* hours set does not need a second "Working hours" from your integration.
|
|
39
|
+
*/
|
|
40
|
+
async listSchedules(workspaceId, options = {}) {
|
|
41
|
+
const result = await request(this.transport, {
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: "/scheduling/schedules",
|
|
44
|
+
query: { workspaceId },
|
|
45
|
+
headers: actingAs(options.asUser),
|
|
46
|
+
});
|
|
47
|
+
return result.schedules ?? [];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gives a workspace its first working hours.
|
|
51
|
+
*
|
|
52
|
+
* Narrower than what the workspace's own Calendar settings can do - no date
|
|
53
|
+
* overrides, and never owned by one person (`ownerMemberId` stays `null`
|
|
54
|
+
* server-side). Anything beyond that is a Console edit, not a second write
|
|
55
|
+
* path for your integration to maintain.
|
|
56
|
+
*/
|
|
57
|
+
createSchedule(input) {
|
|
58
|
+
return request(this.transport, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
path: "/scheduling/schedules",
|
|
61
|
+
json: {
|
|
62
|
+
workspaceId: input.workspaceId,
|
|
63
|
+
name: input.name,
|
|
64
|
+
timezone: input.timezone,
|
|
65
|
+
weeklyHours: input.weeklyHours,
|
|
66
|
+
},
|
|
67
|
+
headers: actingAs(input.asUser),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The first thing a workspace can be booked for.
|
|
72
|
+
*
|
|
73
|
+
* Needs a schedule - create or pick one with `listSchedules`/`createSchedule`
|
|
74
|
+
* first. The slug for a future public booking link is derived from `name`
|
|
75
|
+
* server-side; you cannot choose one.
|
|
76
|
+
*
|
|
77
|
+
* Answers the richer *manageable* shape, not `BookableEventType` - you just
|
|
78
|
+
* created this row and are about to manage it, not book against it.
|
|
79
|
+
*/
|
|
80
|
+
createEventType(input) {
|
|
81
|
+
return request(this.transport, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
path: "/scheduling/event-types",
|
|
84
|
+
json: {
|
|
85
|
+
workspaceId: input.workspaceId,
|
|
86
|
+
name: input.name,
|
|
87
|
+
description: input.description ?? null,
|
|
88
|
+
durationMinutes: input.durationMinutes,
|
|
89
|
+
bufferBeforeMinutes: input.bufferBeforeMinutes,
|
|
90
|
+
bufferAfterMinutes: input.bufferAfterMinutes,
|
|
91
|
+
hostMemberId: input.hostMemberId,
|
|
92
|
+
scheduleId: input.scheduleId,
|
|
93
|
+
location: input.location,
|
|
94
|
+
publicBookingEnabled: input.publicBookingEnabled ?? false,
|
|
95
|
+
},
|
|
96
|
+
headers: actingAs(input.asUser),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The richer read `createEventType`/`updateEventType`'s edit mode needs -
|
|
101
|
+
* everything `listEventTypes`'s booking catalogue deliberately withholds,
|
|
102
|
+
* because this is the opposite audience: a member managing their own
|
|
103
|
+
* calendar, not a widget rendering someone else's. Includes inactive rows,
|
|
104
|
+
* since an edit screen is exactly where one gets reactivated.
|
|
105
|
+
*/
|
|
106
|
+
async listManageableEventTypes(workspaceId, options = {}) {
|
|
107
|
+
const result = await request(this.transport, {
|
|
108
|
+
method: "GET",
|
|
109
|
+
path: "/scheduling/event-types/manage",
|
|
110
|
+
query: { workspaceId },
|
|
111
|
+
headers: actingAs(options.asUser),
|
|
112
|
+
});
|
|
113
|
+
return result.eventTypes ?? [];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Edits an existing availability schedule.
|
|
117
|
+
*
|
|
118
|
+
* Narrower than what the workspace's own Calendar settings can do, the
|
|
119
|
+
* same way `createSchedule` is: `ownerMemberId` and date overrides stay
|
|
120
|
+
* untouched, since this surface never sets either.
|
|
121
|
+
*/
|
|
122
|
+
updateSchedule(input) {
|
|
123
|
+
return request(this.transport, {
|
|
124
|
+
method: "PUT",
|
|
125
|
+
path: `/scheduling/schedules/${encodeURIComponent(input.scheduleId)}`,
|
|
126
|
+
json: {
|
|
127
|
+
workspaceId: input.workspaceId,
|
|
128
|
+
name: input.name,
|
|
129
|
+
timezone: input.timezone,
|
|
130
|
+
weeklyHours: input.weeklyHours,
|
|
131
|
+
},
|
|
132
|
+
headers: actingAs(input.asUser),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Edits an existing appointment type.
|
|
137
|
+
*
|
|
138
|
+
* `hostMemberId` omitted leaves the host untouched, including a *group*
|
|
139
|
+
* host this surface cannot represent - so an edit that says nothing about
|
|
140
|
+
* the host can never accidentally overwrite a team assignment made in
|
|
141
|
+
* Connect.
|
|
142
|
+
*/
|
|
143
|
+
updateEventType(input) {
|
|
144
|
+
return request(this.transport, {
|
|
145
|
+
method: "PUT",
|
|
146
|
+
path: `/scheduling/event-types/${encodeURIComponent(input.eventTypeId)}`,
|
|
147
|
+
json: {
|
|
148
|
+
workspaceId: input.workspaceId,
|
|
149
|
+
name: input.name,
|
|
150
|
+
description: input.description,
|
|
151
|
+
durationMinutes: input.durationMinutes,
|
|
152
|
+
bufferBeforeMinutes: input.bufferBeforeMinutes,
|
|
153
|
+
bufferAfterMinutes: input.bufferAfterMinutes,
|
|
154
|
+
hostMemberId: input.hostMemberId,
|
|
155
|
+
scheduleId: input.scheduleId,
|
|
156
|
+
location: input.location,
|
|
157
|
+
publicBookingEnabled: input.publicBookingEnabled,
|
|
158
|
+
active: input.active,
|
|
159
|
+
},
|
|
160
|
+
headers: actingAs(input.asUser),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
34
163
|
/**
|
|
35
164
|
* Free times for one appointment type over one window.
|
|
36
165
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { request } from "../http.js";
|
|
1
|
+
import { GENERATION_TIMEOUT_MS, request } from "../http.js";
|
|
2
2
|
/**
|
|
3
3
|
* The header a bridge forwards the visitor's own address on.
|
|
4
4
|
*
|
|
@@ -97,6 +97,12 @@ export class VisitorResource {
|
|
|
97
97
|
path: "/visitor/chat",
|
|
98
98
|
json: input,
|
|
99
99
|
headers: visitorHeaders(options),
|
|
100
|
+
/*
|
|
101
|
+
* Matches the route's own `maxDuration`. This endpoint does not send
|
|
102
|
+
* headers until the model's first token, so the default 20s bound was
|
|
103
|
+
* aborting any turn that opened with a tool call - see `RequestSpec.timeoutMs`.
|
|
104
|
+
*/
|
|
105
|
+
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
100
106
|
raw: true,
|
|
101
107
|
signal: options.signal,
|
|
102
108
|
});
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import { actingAs } from "./acting-as.js";
|
|
2
2
|
import { request } from "../http.js";
|
|
3
|
-
/**
|
|
4
|
-
* Which workspaces the signed-in user belongs to, moving between them, who
|
|
5
|
-
* else is on the team, and inviting or granting access to your application.
|
|
6
|
-
*
|
|
7
|
-
* **Every call here needs `asUser`** - the signed-in user's opaque
|
|
8
|
-
* `sessionToken`. There is no application-wide form of any of these: they take
|
|
9
|
-
* no `userId` and no `workspaceId`, and resolve both from the token. That is
|
|
10
|
-
* what makes the whole module safe to reach from a browser through
|
|
11
|
-
* `createConnectHandler`, and it is why a partner cannot enumerate another
|
|
12
|
-
* tenant's team with their own API key.
|
|
13
|
-
*/
|
|
14
3
|
export class WorkspacesResource {
|
|
15
4
|
transport;
|
|
16
5
|
constructor(transport) {
|
|
@@ -31,6 +20,36 @@ export class WorkspacesResource {
|
|
|
31
20
|
});
|
|
32
21
|
return result.workspaces;
|
|
33
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Opens another workspace under the tenant the signed-in user already
|
|
25
|
+
* owns - a branch office, a second business unit.
|
|
26
|
+
*
|
|
27
|
+
* Every guard `CreateWorkspaceUseCase` enforces still applies: only the
|
|
28
|
+
* tenant's owner may call this, `type: "business"` still needs a non-free
|
|
29
|
+
* email domain, and the tenant's plan still decides whether it may hold
|
|
30
|
+
* another workspace at all - a `PreconditionError` surfaces as a thrown
|
|
31
|
+
* `ConnectError` with a message written to be shown ("Your current plan
|
|
32
|
+
* covers a single workspace...", "You've used all 3 of the workspaces
|
|
33
|
+
* your plans include..."). There is no `tenantId` parameter: the caller
|
|
34
|
+
* names no tenant, same as every other method on this resource names no
|
|
35
|
+
* workspace.
|
|
36
|
+
*
|
|
37
|
+
* `isActive`/`subscribed` on the result are always `false` - this does not
|
|
38
|
+
* switch the session, and the new workspace has no subscription yet. Chain
|
|
39
|
+
* `switch({workspaceId: result.workspaceId})` to move into it, but only
|
|
40
|
+
* when `result.inAudience` - a workspace outside this application's
|
|
41
|
+
* audience is refused by `switch` the same way `selectable()` in
|
|
42
|
+
* `<ConnectWorkspaceSwitcher>` already treats it.
|
|
43
|
+
*/
|
|
44
|
+
async create(input, options) {
|
|
45
|
+
const result = await request(this.transport, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
path: "/workspaces",
|
|
48
|
+
headers: actingAs(options.asUser),
|
|
49
|
+
json: input,
|
|
50
|
+
});
|
|
51
|
+
return result.workspace;
|
|
52
|
+
}
|
|
34
53
|
/**
|
|
35
54
|
* Moves this session to another workspace, returning a **new session
|
|
36
55
|
* token**. The one you passed is revoked.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AvailabilitySchedule } from "../../types.js";
|
|
2
|
+
export interface ConnectAvailabilityFormModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
5
|
+
/**
|
|
6
|
+
* `null` (or omitted) creates a new schedule. A row id from
|
|
7
|
+
* `useAvailabilitySchedules` edits that one instead - name and hours
|
|
8
|
+
* prefill from it.
|
|
9
|
+
*/
|
|
10
|
+
scheduleId?: string | null;
|
|
11
|
+
/** Called once the schedule is created or saved, before the modal closes. */
|
|
12
|
+
onSaved?: (schedule: AvailabilitySchedule) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates or edits a workspace's working hours, from inside a host
|
|
16
|
+
* application rather than a link-out to Connect.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately narrower than Connect's own Calendar settings form: one
|
|
19
|
+
* interval per day (no split shifts), no date overrides, and the timezone is
|
|
20
|
+
* set once at creation (from the browser's own) and shown but not editable
|
|
21
|
+
* here afterwards. This is a first-party setup-and-edit surface, not a
|
|
22
|
+
* second copy of the Console editor - "Manage in Connect" is still where the
|
|
23
|
+
* rest of what a schedule can do lives.
|
|
24
|
+
*
|
|
25
|
+
* **Not optimistic.** The write settles against the server before this
|
|
26
|
+
* closes, the same posture every write in this package takes - see
|
|
27
|
+
* `ConnectBookingDialog`.
|
|
28
|
+
*
|
|
29
|
+
* This outer component only decides whether the row to edit has loaded yet;
|
|
30
|
+
* the form itself is `ScheduleFields` below, mounted with
|
|
31
|
+
* `key={editing?.id}` - see that component's own doc comment for why.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ConnectAvailabilityFormModal({ isOpen, onOpenChange, scheduleId, onSaved, }: Readonly<ConnectAvailabilityFormModalProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { UserWorkspace } from "../../types.js";
|
|
2
|
+
export interface ConnectCreateWorkspaceModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
5
|
+
/** Called once the workspace is created, before the modal closes. */
|
|
6
|
+
onCreated?: (workspace: UserWorkspace) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Opens another workspace under the signed-in user's own tenant - a branch
|
|
10
|
+
* office, a second business unit - from inside a host application rather
|
|
11
|
+
* than a link-out to Connect.
|
|
12
|
+
*
|
|
13
|
+
* **Only the tenant's owner can use this successfully.** `CreateWorkspaceUseCase`
|
|
14
|
+
* refuses anyone who merely belongs to a colleague's tenant, with a message
|
|
15
|
+
* naming that - someone who wants a workspace of their own uses a personal
|
|
16
|
+
* account instead.
|
|
17
|
+
*
|
|
18
|
+
* **The tenant's plan decides how many workspaces it may hold, and this does
|
|
19
|
+
* not pre-check it.** There is no capacity read to show ahead of time - the
|
|
20
|
+
* same posture Connect's own "Create workspace" button takes - so a refusal
|
|
21
|
+
* surfaces here exactly as the server wrote it ("Your current plan covers a
|
|
22
|
+
* single workspace - subscribe to a plan that includes additional workspaces
|
|
23
|
+
* to open another.", or "You've used all 3 of the workspaces your plans
|
|
24
|
+
* include. Upgrade a plan or delete a workspace to open another.").
|
|
25
|
+
*
|
|
26
|
+
* **Does not switch the session to the new workspace.** Creating one never
|
|
27
|
+
* does, on Connect's own screen either - see `useCreateWorkspace`'s doc
|
|
28
|
+
* comment for how to chain a switch once you have the result, and guard that
|
|
29
|
+
* chain on `inAudience` the same way `<ConnectWorkspaceSwitcher>` already
|
|
30
|
+
* does for every other row.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ConnectCreateWorkspaceModal({ isOpen, onOpenChange, onCreated, }: Readonly<ConnectCreateWorkspaceModalProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ManageableEventType } from "../../types.js";
|
|
2
|
+
export interface ConnectEventTypeFormModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
5
|
+
/**
|
|
6
|
+
* `null` (or omitted) creates a new appointment type. A row id from
|
|
7
|
+
* `useManageableEventTypes` edits that one instead - name, duration,
|
|
8
|
+
* buffers, host, schedule, location and `publicBookingEnabled` all prefill
|
|
9
|
+
* from it.
|
|
10
|
+
*/
|
|
11
|
+
eventTypeId?: string | null;
|
|
12
|
+
/** Called once the appointment type is created or saved, before the modal closes. */
|
|
13
|
+
onSaved?: (eventType: ManageableEventType) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Creates or edits an appointment type, from inside a host application
|
|
17
|
+
* rather than a link-out to Connect.
|
|
18
|
+
*
|
|
19
|
+
* **Needs a schedule to exist first** - the picker reads `useAvailabilitySchedules`
|
|
20
|
+
* and renders "Set up availability first" with nothing to submit if none
|
|
21
|
+
* exists yet. Pair this with `ConnectAvailabilityFormModal` in a setup flow
|
|
22
|
+
* rather than assuming one.
|
|
23
|
+
*
|
|
24
|
+
* **That gate waits for the read, and the order matters.** Both bridge reads
|
|
25
|
+
* answer `[]` while they are in flight, so testing `schedules.length === 0`
|
|
26
|
+
* first showed "Set up availability first" to everybody who opened this -
|
|
27
|
+
* including, for a second or two, somebody pressing Edit on an appointment
|
|
28
|
+
* type that provably *has* a schedule. A load state wearing an empty state's
|
|
29
|
+
* words is worse than a spinner: it reads as a verdict, and on a slow
|
|
30
|
+
* connection it is the only thing the modal ever says. A failed read is named
|
|
31
|
+
* as a failure for the same reason - it is not the same fact as "none exist".
|
|
32
|
+
*
|
|
33
|
+
* **Host is a specific member, never a team.** There is no groups read in
|
|
34
|
+
* this bridge, so a round-robin team host stays a Console-only capability -
|
|
35
|
+
* defaults to whoever is signed in on create, with an optional picker from
|
|
36
|
+
* the workspace's own roster. **Editing a group-hosted row is refused
|
|
37
|
+
* inline** - the modal shows a notice instead of a form, because this
|
|
38
|
+
* surface cannot represent (and so cannot safely resend) a team host.
|
|
39
|
+
*
|
|
40
|
+
* This outer component only decides *which* message or form to show; the
|
|
41
|
+
* form itself is `EventTypeFields` below, mounted with `key={editing?.id}` -
|
|
42
|
+
* see that component's own doc comment for why.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ConnectEventTypeFormModal({ isOpen, onOpenChange, eventTypeId, onSaved, }: Readonly<ConnectEventTypeFormModalProps>): import("react").JSX.Element;
|
|
@@ -21,12 +21,12 @@ export interface ConnectWorkspaceSwitcherProps {
|
|
|
21
21
|
* every other host: an account with one workspace sees nothing, because a
|
|
22
22
|
* switcher offering one option teaches it nothing. Pass `true` on an
|
|
23
23
|
* application that wants a single-workspace account to be able to
|
|
24
|
-
* discover - and
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* discover - and open - a second one without leaving its own chrome. Turning
|
|
25
|
+
* this on adds a "Create workspace" row that opens
|
|
26
|
+
* `<ConnectCreateWorkspaceModal>` inline: name, individual-or-business, and
|
|
27
|
+
* the tenant's own plan decides whether another workspace is allowed at
|
|
28
|
+
* all - the same guards Connect's own "Create workspace" button runs
|
|
29
|
+
* through, surfaced as the server wrote them rather than pre-checked here.
|
|
30
30
|
*/
|
|
31
31
|
alwaysShow?: boolean;
|
|
32
32
|
}
|
|
@@ -44,9 +44,9 @@ export interface ConnectWorkspaceSwitcherProps {
|
|
|
44
44
|
* a workspace is *created*, so hiding it at one workspace would guarantee
|
|
45
45
|
* nobody ever reached two. `alwaysShow` is the escape hatch for a partner
|
|
46
46
|
* application that wants that same discoverability without becoming Connect:
|
|
47
|
-
* it keeps the dropdown open below two workspaces and adds a row that
|
|
48
|
-
*
|
|
49
|
-
* that
|
|
47
|
+
* it keeps the dropdown open below two workspaces and adds a row that opens
|
|
48
|
+
* `<ConnectCreateWorkspaceModal>` inline, auto-switching into the result when
|
|
49
|
+
* that is possible - see `handleCreated` below.
|
|
50
50
|
*
|
|
51
51
|
* **Authorization is the data, not a prop.** The list comes from the caller's
|
|
52
52
|
* own session (`GET /workspaces` takes no user id), so a workspace the user
|
|
@@ -32,6 +32,7 @@ export type { ConnectUserButtonProps, ConnectProfileModalProps } from "./compone
|
|
|
32
32
|
export { ConnectAccountSessions } from "./components/account-sessions.js";
|
|
33
33
|
export type { ConnectAccountSessionsProps } from "./components/account-sessions.js";
|
|
34
34
|
export type { ConnectUserMenuProps } from "./components/user-menu.js";
|
|
35
|
+
export { connectPortalUrl, useConnectPortalUrl } from "./portal-links.js";
|
|
35
36
|
export { AppShell } from "./components/app-shell.js";
|
|
36
37
|
export type { AppShellProps } from "./components/app-shell.js";
|
|
37
38
|
export { ConnectSidebar, ConnectSidebarHeader, ConnectSidebarBody, ConnectSidebarFooter, ConnectSidebarRow, } from "./components/sidebar.js";
|
|
@@ -101,8 +102,16 @@ export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pri
|
|
|
101
102
|
export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
102
103
|
export type { ConnectWorkspaceSwitcherProps } from "./components/workspace-switcher.js";
|
|
103
104
|
export { useConnectWorkspaces, useConnectWorkspaceMembers } from "./provider.js";
|
|
105
|
+
export { ConnectCreateWorkspaceModal } from "./components/create-workspace-form.js";
|
|
106
|
+
export type { ConnectCreateWorkspaceModalProps } from "./components/create-workspace-form.js";
|
|
107
|
+
export { useCreateWorkspace } from "./use-workspaces.js";
|
|
108
|
+
export type { WorkspaceActionsState } from "./use-workspaces.js";
|
|
104
109
|
export { ConnectBookingCalendar } from "./components/booking-calendar.js";
|
|
105
110
|
export type { ConnectBookingCalendarProps } from "./components/booking-calendar.js";
|
|
106
111
|
export { ConnectBookingDialog, ConnectBookingDetail } from "./components/booking-dialogs.js";
|
|
107
|
-
export {
|
|
112
|
+
export { ConnectAvailabilityFormModal } from "./components/availability-form.js";
|
|
113
|
+
export type { ConnectAvailabilityFormModalProps } from "./components/availability-form.js";
|
|
114
|
+
export { ConnectEventTypeFormModal } from "./components/event-type-form.js";
|
|
115
|
+
export type { ConnectEventTypeFormModalProps } from "./components/event-type-form.js";
|
|
116
|
+
export { useBookableEventTypes, useAvailabilitySchedules, useCreateAvailabilitySchedule, useUpdateAvailabilitySchedule, useManageableEventTypes, useCreateEventType, useUpdateEventType, useAvailableSlots, useBookings, useBookingActions, type SlotQuery, type BookingsQuery, } from "./use-scheduling.js";
|
|
108
117
|
export { layoutBookings, dayColumns, zonedInstant, type GridWindow, type PositionedBooking, } from "./components/booking-grid.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any Connect portal page, with the context it needs to offer a way back
|
|
3
|
+
* here - `connectPortalUrl(appUrl, clientId, "/profile")`,
|
|
4
|
+
* `connectPortalUrl(appUrl, clientId, "/calendar/settings")`, and so on.
|
|
5
|
+
*
|
|
6
|
+
* Connect answers any of these paths for a signed-in user on the bare URL -
|
|
7
|
+
* these two params only add the "Back to <your app>" banner
|
|
8
|
+
* (`ApplicationReturnBar` on Connect's side). It validates `return_to`
|
|
9
|
+
* against this `client_id`'s registered redirect origins and silently drops
|
|
10
|
+
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
11
|
+
* pairing degrades to what linking there did before rather than erroring.
|
|
12
|
+
*
|
|
13
|
+
* `window.location.href` rather than a configured home URL: the point is to
|
|
14
|
+
* come back to the page they left, and only the browser knows which one that
|
|
15
|
+
* is.
|
|
16
|
+
*/
|
|
17
|
+
export declare function connectPortalUrl(appUrl: string, clientId: string, path: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* `connectPortalUrl`, with `appUrl`/`clientId` read from the provider instead
|
|
20
|
+
* of threaded through by every caller.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useConnectPortalUrl(path: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BookableEventType, Booking, BookingAttendee } from "../types.js";
|
|
1
|
+
import type { AvailabilitySchedule, BookableEventType, Booking, BookingAttendee, ManageableEventType, WeeklyHours } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* The bridge's scheduling surface, as hooks.
|
|
4
4
|
*
|
|
@@ -14,6 +14,98 @@ export declare function useBookableEventTypes(): {
|
|
|
14
14
|
isLoading: boolean;
|
|
15
15
|
error: string | null;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* The working-hours templates an appointment type can point at.
|
|
19
|
+
*
|
|
20
|
+
* Read this before offering a "set up availability" step - a workspace that
|
|
21
|
+
* already has one does not need a second "Working hours" from your UI.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useAvailabilitySchedules(): {
|
|
24
|
+
schedules: AvailabilitySchedule[];
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The richer read `ConnectEventTypeFormModal`'s edit mode needs - everything
|
|
30
|
+
* `useBookableEventTypes`'s booking catalogue deliberately withholds. See
|
|
31
|
+
* the bridge route's own doc comment for why this is a separate read rather
|
|
32
|
+
* than a wider response on that one. Includes inactive rows.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useManageableEventTypes(): {
|
|
35
|
+
eventTypes: ManageableEventType[];
|
|
36
|
+
isLoading: boolean;
|
|
37
|
+
error: string | null;
|
|
38
|
+
refetch: () => void;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Gives a workspace its first working hours.
|
|
42
|
+
*
|
|
43
|
+
* **Not optimistic** - same posture as `useBookingActions`: this is one of
|
|
44
|
+
* the two writes a host's first-run setup makes, and a modal that closes on
|
|
45
|
+
* a failed create is worse than one that stays open with the reason shown.
|
|
46
|
+
*/
|
|
47
|
+
export declare function useCreateAvailabilitySchedule(): BookingActionsState & {
|
|
48
|
+
createSchedule(input: {
|
|
49
|
+
name: string;
|
|
50
|
+
timezone: string;
|
|
51
|
+
weeklyHours: WeeklyHours;
|
|
52
|
+
}): Promise<AvailabilitySchedule | null>;
|
|
53
|
+
};
|
|
54
|
+
/** Edits an existing availability schedule. Every field optional - send only what changed. */
|
|
55
|
+
export declare function useUpdateAvailabilitySchedule(): BookingActionsState & {
|
|
56
|
+
updateSchedule(input: {
|
|
57
|
+
scheduleId: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
timezone?: string;
|
|
60
|
+
weeklyHours?: WeeklyHours;
|
|
61
|
+
}): Promise<AvailabilitySchedule | null>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Creates the first thing a workspace can be booked for.
|
|
65
|
+
*
|
|
66
|
+
* Host is a specific member, never a team - there is no groups read in this
|
|
67
|
+
* bridge, so a round-robin team host stays a Console-only capability
|
|
68
|
+
* reachable by following "Manage in Connect".
|
|
69
|
+
*/
|
|
70
|
+
export declare function useCreateEventType(): BookingActionsState & {
|
|
71
|
+
createEventType(input: {
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string | null;
|
|
74
|
+
durationMinutes: number;
|
|
75
|
+
bufferBeforeMinutes?: number;
|
|
76
|
+
bufferAfterMinutes?: number;
|
|
77
|
+
hostMemberId: string;
|
|
78
|
+
scheduleId: string;
|
|
79
|
+
location: {
|
|
80
|
+
kind: "phone" | "address" | "link";
|
|
81
|
+
value?: string | null;
|
|
82
|
+
};
|
|
83
|
+
publicBookingEnabled?: boolean;
|
|
84
|
+
}): Promise<ManageableEventType | null>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Edits an existing appointment type. Every field optional - send only what
|
|
88
|
+
* changed. `hostMemberId` omitted leaves the host untouched, including a
|
|
89
|
+
* *group* host this surface cannot represent.
|
|
90
|
+
*/
|
|
91
|
+
export declare function useUpdateEventType(): BookingActionsState & {
|
|
92
|
+
updateEventType(input: {
|
|
93
|
+
eventTypeId: string;
|
|
94
|
+
name?: string;
|
|
95
|
+
description?: string | null;
|
|
96
|
+
durationMinutes?: number;
|
|
97
|
+
bufferBeforeMinutes?: number;
|
|
98
|
+
bufferAfterMinutes?: number;
|
|
99
|
+
hostMemberId?: string;
|
|
100
|
+
scheduleId?: string;
|
|
101
|
+
location?: {
|
|
102
|
+
kind: "phone" | "address" | "link";
|
|
103
|
+
value?: string | null;
|
|
104
|
+
};
|
|
105
|
+
publicBookingEnabled?: boolean;
|
|
106
|
+
active?: boolean;
|
|
107
|
+
}): Promise<ManageableEventType | null>;
|
|
108
|
+
};
|
|
17
109
|
export interface SlotQuery {
|
|
18
110
|
eventTypeId: string;
|
|
19
111
|
from: Date;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { UserWorkspace } from "../types.js";
|
|
2
|
+
export interface WorkspaceActionsState {
|
|
3
|
+
isPending: boolean;
|
|
4
|
+
/** The last refusal, written to be shown to the person who clicked - see `CreateWorkspaceUseCase`. */
|
|
5
|
+
error: string | null;
|
|
6
|
+
clearError(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Opens another workspace under the tenant the signed-in user already owns.
|
|
10
|
+
*
|
|
11
|
+
* **Not optimistic**, same posture as every write in `use-scheduling.ts`:
|
|
12
|
+
* whether the tenant's plan still has room for another workspace is a
|
|
13
|
+
* question only the server can answer, and a modal that closes before that
|
|
14
|
+
* answer comes back is the lie a two-phase write exists to avoid.
|
|
15
|
+
*
|
|
16
|
+
* The result is a `UserWorkspace` row with `isActive`/`subscribed` both
|
|
17
|
+
* `false` - creating does not switch the session to it, and the workspace has
|
|
18
|
+
* no subscription yet. Pair with `<ConnectWorkspaceSwitcher>`'s own
|
|
19
|
+
* `mutateResource("/workspaces/switch", ...)` call to move into it, guarded
|
|
20
|
+
* on `inAudience` the same way that component's `selectable()` already is.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useCreateWorkspace(): WorkspaceActionsState & {
|
|
23
|
+
createWorkspace(input: {
|
|
24
|
+
name: string;
|
|
25
|
+
type: "individual" | "business";
|
|
26
|
+
}): Promise<UserWorkspace | null>;
|
|
27
|
+
};
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
2
|
+
/**
|
|
3
|
+
* How long a *generating* endpoint may take to send its headers.
|
|
4
|
+
*
|
|
5
|
+
* One number, named once, and it is not a guess: it is what
|
|
6
|
+
* `POST /visitor/chat` and `POST /ai/chat` declare as their `maxDuration` on
|
|
7
|
+
* the far side. A client bound shorter than the route's own ceiling cannot be
|
|
8
|
+
* right - it can only abort work Connect is still allowed to be doing - and
|
|
9
|
+
* the two numbers drifting apart is exactly how a 20s client came to be
|
|
10
|
+
* fronting a 60s route.
|
|
11
|
+
*/
|
|
12
|
+
export declare const GENERATION_TIMEOUT_MS = 60000;
|
|
2
13
|
export interface RequestSpec {
|
|
3
14
|
/**
|
|
4
15
|
* `PUT` and `DELETE` were added for the store resources. Both are idempotent
|
|
@@ -24,6 +35,30 @@ export interface RequestSpec {
|
|
|
24
35
|
* Also stops `timeoutMs` from applying to the *body*: see `request`.
|
|
25
36
|
*/
|
|
26
37
|
raw?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Override the client's `timeoutMs` for this one call.
|
|
40
|
+
*
|
|
41
|
+
* The transport-wide default bounds how long a request may take to *reach*
|
|
42
|
+
* Connect, and 20 seconds is generous for every endpoint that answers from a
|
|
43
|
+
* read. It is the wrong bound for a **generating** endpoint, because of how
|
|
44
|
+
* those are built on the far side: `POST /visitor/chat` holds its response
|
|
45
|
+
* open until the first delta arrives, deliberately, so that a refusal is
|
|
46
|
+
* still an ordinary `{error}` envelope with a status rather than an event
|
|
47
|
+
* inside a stream nobody can change the status of. So "time to headers"
|
|
48
|
+
* there is time to the model's first token - after up to four tool-calling
|
|
49
|
+
* steps - and that route declares `maxDuration = 60` precisely because it
|
|
50
|
+
* can take that long.
|
|
51
|
+
*
|
|
52
|
+
* With one 20s number for everything, the client aborted at 20s, the abort
|
|
53
|
+
* read as `network_error`, and the bridge returned **502 after exactly
|
|
54
|
+
* 20.0s** on any conversation whose first token took longer - which is every
|
|
55
|
+
* conversation that starts with a tool call. It is the only route on the
|
|
56
|
+
* platform that exceeds the default; every other one declares 15 or 20.
|
|
57
|
+
*
|
|
58
|
+
* Set it where the endpoint's own `maxDuration` is larger, and nowhere else.
|
|
59
|
+
* It does not bound the stream once it has started - see the timer below.
|
|
60
|
+
*/
|
|
61
|
+
timeoutMs?: number;
|
|
27
62
|
signal?: AbortSignal;
|
|
28
63
|
/**
|
|
29
64
|
* Force this request into the retry path even though it is a `POST`.
|