@cosmoops/connect-sdk 0.38.0 → 0.41.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 +86 -0
- package/dist/cjs/client/components/availability-form.js +52 -24
- package/dist/cjs/client/components/call-button.js +398 -0
- package/dist/cjs/client/components/create-workspace-form.js +66 -0
- package/dist/cjs/client/components/event-type-form.js +99 -25
- package/dist/cjs/client/components/workspace-switcher.js +42 -9
- package/dist/cjs/client/index.js +17 -2
- package/dist/cjs/client/portal-links.js +1 -1
- package/dist/cjs/client/use-scheduling.js +37 -0
- package/dist/cjs/client/use-workspaces.js +41 -0
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +176 -2
- package/dist/cjs/server/resources/calls.js +58 -0
- package/dist/cjs/server/resources/scheduling.js +67 -0
- package/dist/cjs/server/resources/workspaces.js +30 -11
- package/dist/esm/client/components/availability-form.js +53 -25
- package/dist/esm/client/components/call-button.js +358 -0
- package/dist/esm/client/components/create-workspace-form.js +63 -0
- package/dist/esm/client/components/event-type-form.js +100 -26
- package/dist/esm/client/components/workspace-switcher.js +42 -9
- package/dist/esm/client/index.js +8 -2
- package/dist/esm/client/portal-links.js +1 -1
- package/dist/esm/client/use-scheduling.js +34 -0
- package/dist/esm/client/use-workspaces.js +38 -0
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +176 -2
- package/dist/esm/server/resources/calls.js +58 -0
- package/dist/esm/server/resources/scheduling.js +67 -0
- package/dist/esm/server/resources/workspaces.js +30 -11
- package/dist/types/client/components/availability-form.d.ts +23 -12
- package/dist/types/client/components/call-button.d.ts +132 -0
- package/dist/types/client/components/create-workspace-form.d.ts +32 -0
- package/dist/types/client/components/event-type-form.d.ts +30 -8
- package/dist/types/client/components/workspace-switcher.d.ts +9 -9
- package/dist/types/client/index.d.ts +7 -1
- package/dist/types/client/portal-links.d.ts +1 -1
- package/dist/types/client/use-scheduling.d.ts +46 -2
- package/dist/types/client/use-workspaces.d.ts +27 -0
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/resources/calls.d.ts +32 -1
- package/dist/types/server/resources/scheduling.d.ts +66 -2
- package/dist/types/server/resources/workspaces.d.ts +28 -0
- package/dist/types/types.d.ts +143 -1
- package/package.json +4 -1
|
@@ -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.
|
|
@@ -2,21 +2,32 @@ import type { AvailabilitySchedule } from "../../types.js";
|
|
|
2
2
|
export interface ConnectAvailabilityFormModalProps {
|
|
3
3
|
isOpen: boolean;
|
|
4
4
|
onOpenChange: (isOpen: boolean) => void;
|
|
5
|
-
/**
|
|
6
|
-
|
|
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;
|
|
7
13
|
}
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
10
|
-
* rather than a link-out to Connect.
|
|
15
|
+
* Creates or edits a workspace's working hours, from inside a host
|
|
16
|
+
* application rather than a link-out to Connect.
|
|
11
17
|
*
|
|
12
18
|
* Deliberately narrower than Connect's own Calendar settings form: one
|
|
13
|
-
* interval per day (no split shifts), no date overrides, and the timezone
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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.
|
|
17
24
|
*
|
|
18
|
-
* **Not optimistic.**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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.
|
|
21
32
|
*/
|
|
22
|
-
export declare function ConnectAvailabilityFormModal({ isOpen, onOpenChange,
|
|
33
|
+
export declare function ConnectAvailabilityFormModal({ isOpen, onOpenChange, scheduleId, onSaved, }: Readonly<ConnectAvailabilityFormModalProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { CallingAvailability } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Calling from the browser, through the workspace's own number.
|
|
5
|
+
*
|
|
6
|
+
* Three pieces: `<ConnectCallProvider>` once, high in your tree;
|
|
7
|
+
* `<ConnectCallButton phone="+91...">` wherever a person can be rung; and
|
|
8
|
+
* `useConnectCall()` if you want your own button.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a provider, and why high up
|
|
11
|
+
*
|
|
12
|
+
* A call has to outlive the page it was started from - someone who rings a
|
|
13
|
+
* customer and then opens their invoices must not hang up on them by doing so.
|
|
14
|
+
* So the call lives in the provider, which also draws the in-call bar, and a
|
|
15
|
+
* button only asks it to start one. Mount it inside `<ConnectProvider>`,
|
|
16
|
+
* beside your app shell rather than inside a page.
|
|
17
|
+
*
|
|
18
|
+
* ## What happens when someone presses Call
|
|
19
|
+
*
|
|
20
|
+
* 1. The browser signs in to the phone network (`POST /calls/browser-token`
|
|
21
|
+
* on your bridge), once per session - Plivo's SDK keeps its registration.
|
|
22
|
+
* Plivo's SDK is loaded at this moment, not before, so a page that never
|
|
23
|
+
* places a call never downloads it.
|
|
24
|
+
* 2. Connect approves the call (`POST /calls/start`): the workspace must hold
|
|
25
|
+
* a number and the member must have "Make calls". What comes back lets this
|
|
26
|
+
* browser place exactly this call and no other.
|
|
27
|
+
* 3. The browser places it. Connect dials the number from the workspace's own
|
|
28
|
+
* number; nothing is recorded.
|
|
29
|
+
*
|
|
30
|
+
* ## Logging a call
|
|
31
|
+
*
|
|
32
|
+
* `onEnded` hands you the call id - deliberately not an outcome. A browser is
|
|
33
|
+
* not a witness your call log should trust, and the carrier's report reaches
|
|
34
|
+
* Connect a moment after the line drops anyway. Read the outcome on your
|
|
35
|
+
* **server** with `connect.calls.get(callId)` (`calls:read`): `status`,
|
|
36
|
+
* `talkSeconds`, `unansweredReason` and `placedByUserId` are what to log.
|
|
37
|
+
*/
|
|
38
|
+
type CallPhase = "connecting" | "ringing" | "in-call" | "ended";
|
|
39
|
+
interface ActiveCall {
|
|
40
|
+
phase: CallPhase;
|
|
41
|
+
label: string;
|
|
42
|
+
number: string;
|
|
43
|
+
callId: string | null;
|
|
44
|
+
connectedAt: number | null;
|
|
45
|
+
endedAfterSeconds: number | null;
|
|
46
|
+
muted: boolean;
|
|
47
|
+
notice: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** The number to ring, and how to name the person on the in-call bar. */
|
|
50
|
+
export interface ConnectCallTarget {
|
|
51
|
+
/** E.164, e.g. `+919876543210`. */
|
|
52
|
+
phone: string;
|
|
53
|
+
/** Shown on the in-call bar. Defaults to the number. */
|
|
54
|
+
name?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Called once the call is over, with Connect's id for it - or `null` when it
|
|
57
|
+
* never got as far as being approved. Read the outcome on your server with
|
|
58
|
+
* `connect.calls.get(callId)`; see the module comment for why not here.
|
|
59
|
+
*/
|
|
60
|
+
onEnded?: (result: {
|
|
61
|
+
callId: string | null;
|
|
62
|
+
}) => void;
|
|
63
|
+
}
|
|
64
|
+
export interface ConnectCallContextValue {
|
|
65
|
+
/** A call is in progress - every Call button disables on it. */
|
|
66
|
+
isBusy: boolean;
|
|
67
|
+
startCall(target: ConnectCallTarget): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Whether this member can call and, if not, why - `null` while it loads.
|
|
70
|
+
* Read once by the provider and shared, so every button and status panel
|
|
71
|
+
* agrees, and refreshed after each call so the minutes shown keep up.
|
|
72
|
+
*/
|
|
73
|
+
availability: CallingAvailability | null;
|
|
74
|
+
/** Re-read `availability` - after a top-up in another tab, say. */
|
|
75
|
+
refreshAvailability(): void;
|
|
76
|
+
}
|
|
77
|
+
/** Start a call yourself. Must be inside `<ConnectCallProvider>`. */
|
|
78
|
+
export declare function useConnectCall(): ConnectCallContextValue;
|
|
79
|
+
export interface ConnectCallProviderProps {
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
/**
|
|
82
|
+
* Called for every call that ends, whichever button started it - the place
|
|
83
|
+
* to wire "log this call" once for the whole app. A button's own `onEnded`
|
|
84
|
+
* runs as well.
|
|
85
|
+
*/
|
|
86
|
+
onCallEnded?: (result: {
|
|
87
|
+
callId: string | null;
|
|
88
|
+
phone: string;
|
|
89
|
+
}) => void;
|
|
90
|
+
/** Called when a call could not be placed, with the reason to show. Defaults to the in-call bar saying so. */
|
|
91
|
+
onError?: (message: string) => void;
|
|
92
|
+
/** Extra classes for the in-call bar's outer element - merged with `cn`. */
|
|
93
|
+
className?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Holds the call in progress and draws the in-call bar. See the module comment.
|
|
97
|
+
*/
|
|
98
|
+
export declare function ConnectCallProvider({ children, onCallEnded, onError, className, }: ConnectCallProviderProps): import("react").JSX.Element;
|
|
99
|
+
export interface ConnectCallButtonProps extends ConnectCallTarget {
|
|
100
|
+
/** `compact` is an icon-only button for a row beside a number. */
|
|
101
|
+
variant?: "default" | "compact";
|
|
102
|
+
/** Button label. Defaults to "Call". */
|
|
103
|
+
children?: ReactNode;
|
|
104
|
+
isDisabled?: boolean;
|
|
105
|
+
/** Merged with `cn`, so an override replaces the default it conflicts with. */
|
|
106
|
+
className?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Rings `phone` from the workspace's number. Needs `<ConnectCallProvider>`
|
|
110
|
+
* above it, and a member with "Make calls" - render it only for them, since a
|
|
111
|
+
* button the server refuses for everyone else helps nobody.
|
|
112
|
+
*/
|
|
113
|
+
export declare function ConnectCallButton({ phone, name, onEnded, variant, children, isDisabled, className, }: ConnectCallButtonProps): import("react").JSX.Element | null;
|
|
114
|
+
export interface ConnectCallingStatusProps {
|
|
115
|
+
/** Merged with `cn`. */
|
|
116
|
+
className?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The workspace's calling at a glance - its business number, the minutes
|
|
120
|
+
* left, and the next step - for a settings page. Reads the same availability
|
|
121
|
+
* the Call buttons do, so the two never disagree. Buying minutes and adding a
|
|
122
|
+
* number happen in Connect, and come back here when done.
|
|
123
|
+
*/
|
|
124
|
+
export declare function ConnectCallingStatus({ className }: ConnectCallingStatusProps): import("react").JSX.Element;
|
|
125
|
+
/** The floating in-call card. Exported for Storybook only - the provider draws it. */
|
|
126
|
+
export declare function ConnectActiveCallBar({ call, onHangUp, onToggleMute, className, }: {
|
|
127
|
+
call: ActiveCall;
|
|
128
|
+
onHangUp: () => void;
|
|
129
|
+
onToggleMute: () => void;
|
|
130
|
+
className?: string;
|
|
131
|
+
}): import("react").JSX.Element;
|
|
132
|
+
export type { ActiveCall as ConnectActiveCall };
|
|
@@ -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;
|
|
@@ -1,22 +1,44 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ManageableEventType } from "../../types.js";
|
|
2
2
|
export interface ConnectEventTypeFormModalProps {
|
|
3
3
|
isOpen: boolean;
|
|
4
4
|
onOpenChange: (isOpen: boolean) => void;
|
|
5
|
-
/**
|
|
6
|
-
|
|
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;
|
|
7
14
|
}
|
|
8
15
|
/**
|
|
9
|
-
* Creates
|
|
10
|
-
*
|
|
16
|
+
* Creates or edits an appointment type, from inside a host application
|
|
17
|
+
* rather than a link-out to Connect.
|
|
11
18
|
*
|
|
12
19
|
* **Needs a schedule to exist first** - the picker reads `useAvailabilitySchedules`
|
|
13
20
|
* and renders "Set up availability first" with nothing to submit if none
|
|
14
21
|
* exists yet. Pair this with `ConnectAvailabilityFormModal` in a setup flow
|
|
15
22
|
* rather than assuming one.
|
|
16
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
|
+
*
|
|
17
33
|
* **Host is a specific member, never a team.** There is no groups read in
|
|
18
34
|
* this bridge, so a round-robin team host stays a Console-only capability -
|
|
19
|
-
* defaults to whoever is signed in, with an optional picker from
|
|
20
|
-
* workspace's own roster.
|
|
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.
|
|
21
43
|
*/
|
|
22
|
-
export declare function ConnectEventTypeFormModal({ isOpen, onOpenChange,
|
|
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
|
|
@@ -102,6 +102,12 @@ export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pri
|
|
|
102
102
|
export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
103
103
|
export type { ConnectWorkspaceSwitcherProps } from "./components/workspace-switcher.js";
|
|
104
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";
|
|
109
|
+
export { ConnectCallProvider, ConnectCallButton, ConnectCallingStatus, useConnectCall, } from "./components/call-button.js";
|
|
110
|
+
export type { ConnectCallProviderProps, ConnectCallButtonProps, ConnectCallTarget, ConnectCallContextValue, ConnectCallingStatusProps, } from "./components/call-button.js";
|
|
105
111
|
export { ConnectBookingCalendar } from "./components/booking-calendar.js";
|
|
106
112
|
export type { ConnectBookingCalendarProps } from "./components/booking-calendar.js";
|
|
107
113
|
export { ConnectBookingDialog, ConnectBookingDetail } from "./components/booking-dialogs.js";
|
|
@@ -109,5 +115,5 @@ export { ConnectAvailabilityFormModal } from "./components/availability-form.js"
|
|
|
109
115
|
export type { ConnectAvailabilityFormModalProps } from "./components/availability-form.js";
|
|
110
116
|
export { ConnectEventTypeFormModal } from "./components/event-type-form.js";
|
|
111
117
|
export type { ConnectEventTypeFormModalProps } from "./components/event-type-form.js";
|
|
112
|
-
export { useBookableEventTypes, useAvailabilitySchedules, useCreateAvailabilitySchedule, useCreateEventType, useAvailableSlots, useBookings, useBookingActions, type SlotQuery, type BookingsQuery, } from "./use-scheduling.js";
|
|
118
|
+
export { useBookableEventTypes, useAvailabilitySchedules, useCreateAvailabilitySchedule, useUpdateAvailabilitySchedule, useManageableEventTypes, useCreateEventType, useUpdateEventType, useAvailableSlots, useBookings, useBookingActions, type SlotQuery, type BookingsQuery, } from "./use-scheduling.js";
|
|
113
119
|
export { layoutBookings, dayColumns, zonedInstant, type GridWindow, type PositionedBooking, } from "./components/booking-grid.js";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `connectPortalUrl(appUrl, clientId, "/calendar/settings")`, and so on.
|
|
5
5
|
*
|
|
6
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
|
|
7
|
+
* these two params only add the `"Back to <your app>"` banner
|
|
8
8
|
* (`ApplicationReturnBar` on Connect's side). It validates `return_to`
|
|
9
9
|
* against this `client_id`'s registered redirect origins and silently drops
|
|
10
10
|
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AvailabilitySchedule, BookableEventType, Booking, BookingAttendee, WeeklyHours } 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
|
*
|
|
@@ -25,6 +25,18 @@ export declare function useAvailabilitySchedules(): {
|
|
|
25
25
|
isLoading: boolean;
|
|
26
26
|
error: string | null;
|
|
27
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
|
+
};
|
|
28
40
|
/**
|
|
29
41
|
* Gives a workspace its first working hours.
|
|
30
42
|
*
|
|
@@ -39,6 +51,15 @@ export declare function useCreateAvailabilitySchedule(): BookingActionsState & {
|
|
|
39
51
|
weeklyHours: WeeklyHours;
|
|
40
52
|
}): Promise<AvailabilitySchedule | null>;
|
|
41
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
|
+
};
|
|
42
63
|
/**
|
|
43
64
|
* Creates the first thing a workspace can be booked for.
|
|
44
65
|
*
|
|
@@ -60,7 +81,30 @@ export declare function useCreateEventType(): BookingActionsState & {
|
|
|
60
81
|
value?: string | null;
|
|
61
82
|
};
|
|
62
83
|
publicBookingEnabled?: boolean;
|
|
63
|
-
}): Promise<
|
|
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>;
|
|
64
108
|
};
|
|
65
109
|
export interface SlotQuery {
|
|
66
110
|
eventTypeId: string;
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { CallDetail, CallsPage } from "../../types.js";
|
|
2
|
+
import type { BrowserCallingToken, CallingAvailability, CallDetail, CallsPage, StartedBrowserCall } from "../../types.js";
|
|
3
3
|
export interface ListCallsInput {
|
|
4
4
|
/** Narrow to one channel. A channel belonging to another application simply matches nothing. */
|
|
5
5
|
channelId?: string;
|
|
@@ -42,4 +42,35 @@ export declare class CallsResource {
|
|
|
42
42
|
* exist *or* belongs to another application.
|
|
43
43
|
*/
|
|
44
44
|
get(callId: string): Promise<CallDetail>;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the member can call, and if not why - what `<ConnectCallButton>`
|
|
47
|
+
* renders from. Throws `ConnectError` 403 for a member without "Make
|
|
48
|
+
* calls"; the bridge turns that into `status: "no_permission"`.
|
|
49
|
+
*/
|
|
50
|
+
getCallingAvailability(input: {
|
|
51
|
+
asUser: string;
|
|
52
|
+
}): Promise<CallingAvailability>;
|
|
53
|
+
/** A carrier token the member's browser signs in to the phone network with. */
|
|
54
|
+
getBrowserCallingToken(input: {
|
|
55
|
+
asUser: string;
|
|
56
|
+
}): Promise<BrowserCallingToken>;
|
|
57
|
+
/**
|
|
58
|
+
* Approves one call from the member to `phone` (E.164) and records it.
|
|
59
|
+
*
|
|
60
|
+
* Throws `ConnectError` with status `412` when the workspace has no active
|
|
61
|
+
* number, is not allowed to place calls, or the member is already on one -
|
|
62
|
+
* the message is written to be shown to them.
|
|
63
|
+
*/
|
|
64
|
+
startBrowserCall(input: {
|
|
65
|
+
phone: string;
|
|
66
|
+
asUser: string;
|
|
67
|
+
}): Promise<StartedBrowserCall>;
|
|
68
|
+
/**
|
|
69
|
+
* Closes a call the browser never managed to place - recorded as
|
|
70
|
+
* `unanswered`/`cancelled`. A no-op once the carrier has it.
|
|
71
|
+
*/
|
|
72
|
+
cancelBrowserCall(input: {
|
|
73
|
+
callId: string;
|
|
74
|
+
asUser: string;
|
|
75
|
+
}): Promise<void>;
|
|
45
76
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { AvailabilitySchedule, BookableEventType, Booking, RescheduledBooking, BookingAttendee, BookingSlots, BookingStatus, ListBookingsPage, WeeklyHours } from "../../types.js";
|
|
2
|
+
import type { AvailabilitySchedule, BookableEventType, Booking, ManageableEventType, RescheduledBooking, BookingAttendee, BookingSlots, BookingStatus, ListBookingsPage, WeeklyHours } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Present on every method here.
|
|
5
5
|
*
|
|
@@ -98,6 +98,42 @@ export interface CreateEventTypeInput extends ActingAsOption {
|
|
|
98
98
|
/** Off by default - most appointment types are booked by your integration or a member, not the public. */
|
|
99
99
|
publicBookingEnabled?: boolean;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* What `updateSchedule` takes. Every field optional - send only what changed.
|
|
103
|
+
* `ownerMemberId` and date overrides are not settable from this surface at
|
|
104
|
+
* all, so an edit can never touch them.
|
|
105
|
+
*/
|
|
106
|
+
export interface UpdateScheduleInput extends ActingAsOption {
|
|
107
|
+
workspaceId: string;
|
|
108
|
+
scheduleId: string;
|
|
109
|
+
name?: string;
|
|
110
|
+
timezone?: string;
|
|
111
|
+
weeklyHours?: WeeklyHours;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What `updateEventType` takes. Every field optional - send only what
|
|
115
|
+
* changed. `hostMemberId` omitted leaves the host untouched, including a
|
|
116
|
+
* *group* host this surface cannot represent; sending one always rewrites to
|
|
117
|
+
* a member host, by design.
|
|
118
|
+
*/
|
|
119
|
+
export interface UpdateEventTypeInput extends ActingAsOption {
|
|
120
|
+
workspaceId: string;
|
|
121
|
+
eventTypeId: string;
|
|
122
|
+
name?: string;
|
|
123
|
+
description?: string | null;
|
|
124
|
+
durationMinutes?: number;
|
|
125
|
+
bufferBeforeMinutes?: number;
|
|
126
|
+
bufferAfterMinutes?: number;
|
|
127
|
+
hostMemberId?: string;
|
|
128
|
+
scheduleId?: string;
|
|
129
|
+
location?: {
|
|
130
|
+
kind: "phone" | "address" | "link";
|
|
131
|
+
value?: string | null;
|
|
132
|
+
};
|
|
133
|
+
publicBookingEnabled?: boolean;
|
|
134
|
+
/** Take an appointment type out of (or back into) service. */
|
|
135
|
+
active?: boolean;
|
|
136
|
+
}
|
|
101
137
|
export interface CreateBookingInput extends ActingAsOption {
|
|
102
138
|
workspaceId: string;
|
|
103
139
|
eventTypeId: string;
|
|
@@ -152,8 +188,36 @@ export declare class SchedulingResource {
|
|
|
152
188
|
* Needs a schedule - create or pick one with `listSchedules`/`createSchedule`
|
|
153
189
|
* first. The slug for a future public booking link is derived from `name`
|
|
154
190
|
* server-side; you cannot choose one.
|
|
191
|
+
*
|
|
192
|
+
* Answers the richer *manageable* shape, not `BookableEventType` - you just
|
|
193
|
+
* created this row and are about to manage it, not book against it.
|
|
194
|
+
*/
|
|
195
|
+
createEventType(input: CreateEventTypeInput): Promise<ManageableEventType>;
|
|
196
|
+
/**
|
|
197
|
+
* The richer read `createEventType`/`updateEventType`'s edit mode needs -
|
|
198
|
+
* everything `listEventTypes`'s booking catalogue deliberately withholds,
|
|
199
|
+
* because this is the opposite audience: a member managing their own
|
|
200
|
+
* calendar, not a widget rendering someone else's. Includes inactive rows,
|
|
201
|
+
* since an edit screen is exactly where one gets reactivated.
|
|
202
|
+
*/
|
|
203
|
+
listManageableEventTypes(workspaceId: string, options?: ActingAsOption): Promise<ManageableEventType[]>;
|
|
204
|
+
/**
|
|
205
|
+
* Edits an existing availability schedule.
|
|
206
|
+
*
|
|
207
|
+
* Narrower than what the workspace's own Calendar settings can do, the
|
|
208
|
+
* same way `createSchedule` is: `ownerMemberId` and date overrides stay
|
|
209
|
+
* untouched, since this surface never sets either.
|
|
210
|
+
*/
|
|
211
|
+
updateSchedule(input: UpdateScheduleInput): Promise<AvailabilitySchedule>;
|
|
212
|
+
/**
|
|
213
|
+
* Edits an existing appointment type.
|
|
214
|
+
*
|
|
215
|
+
* `hostMemberId` omitted leaves the host untouched, including a *group*
|
|
216
|
+
* host this surface cannot represent - so an edit that says nothing about
|
|
217
|
+
* the host can never accidentally overwrite a team assignment made in
|
|
218
|
+
* Connect.
|
|
155
219
|
*/
|
|
156
|
-
|
|
220
|
+
updateEventType(input: UpdateEventTypeInput): Promise<ManageableEventType>;
|
|
157
221
|
/**
|
|
158
222
|
* Free times for one appointment type over one window.
|
|
159
223
|
*
|