@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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ConnectCreateWorkspaceModal = ConnectCreateWorkspaceModal;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const react_2 = require("@heroui/react");
|
|
8
|
+
const use_workspaces_js_1 = require("../use-workspaces.js");
|
|
9
|
+
const WORKSPACE_TYPES = [
|
|
10
|
+
{
|
|
11
|
+
id: "business",
|
|
12
|
+
label: "Business",
|
|
13
|
+
hint: "A company. Verified with company KYB, and can subscribe to anything sold to businesses.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "individual",
|
|
17
|
+
label: "Individual",
|
|
18
|
+
hint: "A person. Verified against your own identity rather than a company.",
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Opens another workspace under the signed-in user's own tenant - a branch
|
|
23
|
+
* office, a second business unit - from inside a host application rather
|
|
24
|
+
* than a link-out to Connect.
|
|
25
|
+
*
|
|
26
|
+
* **Only the tenant's owner can use this successfully.** `CreateWorkspaceUseCase`
|
|
27
|
+
* refuses anyone who merely belongs to a colleague's tenant, with a message
|
|
28
|
+
* naming that - someone who wants a workspace of their own uses a personal
|
|
29
|
+
* account instead.
|
|
30
|
+
*
|
|
31
|
+
* **The tenant's plan decides how many workspaces it may hold, and this does
|
|
32
|
+
* not pre-check it.** There is no capacity read to show ahead of time - the
|
|
33
|
+
* same posture Connect's own "Create workspace" button takes - so a refusal
|
|
34
|
+
* surfaces here exactly as the server wrote it ("Your current plan covers a
|
|
35
|
+
* single workspace - subscribe to a plan that includes additional workspaces
|
|
36
|
+
* to open another.", or "You've used all 3 of the workspaces your plans
|
|
37
|
+
* include. Upgrade a plan or delete a workspace to open another.").
|
|
38
|
+
*
|
|
39
|
+
* **Does not switch the session to the new workspace.** Creating one never
|
|
40
|
+
* does, on Connect's own screen either - see `useCreateWorkspace`'s doc
|
|
41
|
+
* comment for how to chain a switch once you have the result, and guard that
|
|
42
|
+
* chain on `inAudience` the same way `<ConnectWorkspaceSwitcher>` already
|
|
43
|
+
* does for every other row.
|
|
44
|
+
*/
|
|
45
|
+
function ConnectCreateWorkspaceModal({ isOpen, onOpenChange, onCreated, }) {
|
|
46
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Modal, { isOpen: isOpen, onOpenChange: onOpenChange, children: (0, jsx_runtime_1.jsx)(react_2.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_2.Modal.Container, { children: (0, jsx_runtime_1.jsxs)(react_2.Modal.Dialog, { className: "sm:max-w-110", children: [(0, jsx_runtime_1.jsx)(react_2.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_2.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_2.Modal.Heading, { children: "Create workspace" }) }), (0, jsx_runtime_1.jsx)(react_2.Modal.Body, { children: (0, jsx_runtime_1.jsx)(CreateWorkspaceFields, { onCancel: () => onOpenChange(false), onCreated: (workspace) => {
|
|
47
|
+
onCreated?.(workspace);
|
|
48
|
+
onOpenChange(false);
|
|
49
|
+
} }) })] }) }) }) }));
|
|
50
|
+
}
|
|
51
|
+
function CreateWorkspaceFields({ onCancel, onCreated, }) {
|
|
52
|
+
const [name, setName] = (0, react_1.useState)("");
|
|
53
|
+
const [type, setType] = (0, react_1.useState)("business");
|
|
54
|
+
const { createWorkspace, isPending, error, clearError } = (0, use_workspaces_js_1.useCreateWorkspace)();
|
|
55
|
+
const isFormValid = name.trim().length > 0 && !isPending;
|
|
56
|
+
const handleSubmit = async (e) => {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
if (!isFormValid)
|
|
59
|
+
return;
|
|
60
|
+
clearError();
|
|
61
|
+
const created = await createWorkspace({ name: name.trim(), type });
|
|
62
|
+
if (created)
|
|
63
|
+
onCreated(created);
|
|
64
|
+
};
|
|
65
|
+
return ((0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)(react_2.TextField, { isRequired: true, variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Name" }), (0, jsx_runtime_1.jsx)(react_2.Input, { placeholder: "Acme Logistics", value: name, onChange: (e) => setName(e.currentTarget.value), disabled: isPending, autoFocus: true })] }), (0, jsx_runtime_1.jsxs)(react_2.RadioGroup, { variant: "secondary", value: type, onChange: (value) => setType(value), isDisabled: isPending, children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Type" }), (0, jsx_runtime_1.jsx)("div", { className: "mt-1.5 flex flex-col gap-2", children: WORKSPACE_TYPES.map((option) => ((0, jsx_runtime_1.jsx)(react_2.Radio, { value: option.id, children: (0, jsx_runtime_1.jsxs)(react_2.Radio.Content, { children: [(0, jsx_runtime_1.jsx)(react_2.Radio.Control, { children: (0, jsx_runtime_1.jsx)(react_2.Radio.Indicator, {}) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium", children: option.label }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: option.hint })] })] }) }, option.id))) })] }), error && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: error }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-end gap-2", children: [(0, jsx_runtime_1.jsx)(react_2.Button, { type: "button", variant: "secondary", onPress: onCancel, isDisabled: isPending, children: "Cancel" }), (0, jsx_runtime_1.jsx)(react_2.Button, { type: "submit", variant: "primary", isDisabled: !isFormValid, children: isPending ? "Creating…" : "Create" })] })] }));
|
|
66
|
+
}
|
|
@@ -14,40 +14,113 @@ const LOCATION_LABELS = {
|
|
|
14
14
|
};
|
|
15
15
|
const LOCATION_KINDS = ["phone", "address", "link"];
|
|
16
16
|
/**
|
|
17
|
-
* Creates
|
|
18
|
-
*
|
|
17
|
+
* Creates or edits an appointment type, from inside a host application
|
|
18
|
+
* rather than a link-out to Connect.
|
|
19
19
|
*
|
|
20
20
|
* **Needs a schedule to exist first** - the picker reads `useAvailabilitySchedules`
|
|
21
21
|
* and renders "Set up availability first" with nothing to submit if none
|
|
22
22
|
* exists yet. Pair this with `ConnectAvailabilityFormModal` in a setup flow
|
|
23
23
|
* rather than assuming one.
|
|
24
24
|
*
|
|
25
|
+
* **That gate waits for the read, and the order matters.** Both bridge reads
|
|
26
|
+
* answer `[]` while they are in flight, so testing `schedules.length === 0`
|
|
27
|
+
* first showed "Set up availability first" to everybody who opened this -
|
|
28
|
+
* including, for a second or two, somebody pressing Edit on an appointment
|
|
29
|
+
* type that provably *has* a schedule. A load state wearing an empty state's
|
|
30
|
+
* words is worse than a spinner: it reads as a verdict, and on a slow
|
|
31
|
+
* connection it is the only thing the modal ever says. A failed read is named
|
|
32
|
+
* as a failure for the same reason - it is not the same fact as "none exist".
|
|
33
|
+
*
|
|
25
34
|
* **Host is a specific member, never a team.** There is no groups read in
|
|
26
35
|
* this bridge, so a round-robin team host stays a Console-only capability -
|
|
27
|
-
* defaults to whoever is signed in, with an optional picker from
|
|
28
|
-
* workspace's own roster.
|
|
36
|
+
* defaults to whoever is signed in on create, with an optional picker from
|
|
37
|
+
* the workspace's own roster. **Editing a group-hosted row is refused
|
|
38
|
+
* inline** - the modal shows a notice instead of a form, because this
|
|
39
|
+
* surface cannot represent (and so cannot safely resend) a team host.
|
|
40
|
+
*
|
|
41
|
+
* This outer component only decides *which* message or form to show; the
|
|
42
|
+
* form itself is `EventTypeFields` below, mounted with `key={editing?.id}` -
|
|
43
|
+
* see that component's own doc comment for why.
|
|
29
44
|
*/
|
|
30
|
-
function ConnectEventTypeFormModal({ isOpen, onOpenChange,
|
|
31
|
-
const
|
|
45
|
+
function ConnectEventTypeFormModal({ isOpen, onOpenChange, eventTypeId = null, onSaved, }) {
|
|
46
|
+
const isEdit = eventTypeId !== null;
|
|
32
47
|
const { data: membersData } = (0, provider_js_1.useConnectWorkspaceMembers)();
|
|
33
|
-
const { schedules } = (0, use_scheduling_js_1.useAvailabilitySchedules)();
|
|
34
|
-
const
|
|
48
|
+
const { schedules, isLoading: isLoadingSchedules, error: schedulesError, } = (0, use_scheduling_js_1.useAvailabilitySchedules)();
|
|
49
|
+
const { eventTypes: manageableEventTypes, isLoading: isLoadingEventTypes, error: eventTypesError, } = (0, use_scheduling_js_1.useManageableEventTypes)();
|
|
50
|
+
const members = membersData?.members ?? [];
|
|
51
|
+
const editing = isEdit ? manageableEventTypes.find((row) => row.id === eventTypeId) ?? null : null;
|
|
52
|
+
/** A group host cannot be represented (or safely resent) by this form. */
|
|
53
|
+
const isUnrepresentableHost = isEdit && editing?.host.kind === "group";
|
|
54
|
+
/*
|
|
55
|
+
* Everything this modal decides from depends on a read, so the two
|
|
56
|
+
* not-yet-answered states come first - see this component's doc comment.
|
|
57
|
+
* An edit also waits for the row itself: `editing` is `null` both while the
|
|
58
|
+
* list is loading and when it has arrived without this id, and only the
|
|
59
|
+
* second of those is a real answer.
|
|
60
|
+
*/
|
|
61
|
+
const isLoading = isLoadingSchedules || (isEdit && (isLoadingEventTypes || (!editing && !eventTypesError)));
|
|
62
|
+
const readError = schedulesError ?? (isEdit ? eventTypesError : null);
|
|
63
|
+
let body;
|
|
64
|
+
if (isLoading) {
|
|
65
|
+
body = (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "Loading\u2026" });
|
|
66
|
+
}
|
|
67
|
+
else if (readError) {
|
|
68
|
+
body = ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "That could not be loaded just now. Close this and try again." }));
|
|
69
|
+
}
|
|
70
|
+
else if (isUnrepresentableHost) {
|
|
71
|
+
body = ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "This appointment type is hosted by a team, which this screen cannot show or change. Edit it in Connect." }));
|
|
72
|
+
}
|
|
73
|
+
else if (schedules.length === 0) {
|
|
74
|
+
body = ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "Set up availability first - an appointment type needs working hours to book against." }));
|
|
75
|
+
}
|
|
76
|
+
else if (isEdit && !editing) {
|
|
77
|
+
/*
|
|
78
|
+
* The list arrived and does not hold this id - deleted, or never visible
|
|
79
|
+
* to this caller. Distinct from the load above, which is why that one
|
|
80
|
+
* cannot be allowed to borrow these words either.
|
|
81
|
+
*/
|
|
82
|
+
body = (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "That appointment type no longer exists." });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
body = ((0, jsx_runtime_1.jsx)(EventTypeFields, { editing: editing, schedules: schedules, members: members, onCancel: () => onOpenChange(false), onSaved: (saved) => {
|
|
86
|
+
onSaved?.(saved);
|
|
87
|
+
onOpenChange(false);
|
|
88
|
+
} }, editing?.id ?? "create"));
|
|
89
|
+
}
|
|
90
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Modal, { isOpen: isOpen, onOpenChange: onOpenChange, children: (0, jsx_runtime_1.jsx)(react_2.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_2.Modal.Container, { children: (0, jsx_runtime_1.jsxs)(react_2.Modal.Dialog, { className: "sm:max-w-140", children: [(0, jsx_runtime_1.jsx)(react_2.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_2.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_2.Modal.Heading, { children: isEdit ? "Edit appointment type" : "New appointment type" }) }), (0, jsx_runtime_1.jsx)(react_2.Modal.Body, { children: body })] }) }) }) }));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The form itself, mounted fresh (via the parent's `key`) for each distinct
|
|
94
|
+
* record it edits - or once for create. That remount is what initializes
|
|
95
|
+
* every field straight from `editing` with a plain `useState(initial)`
|
|
96
|
+
* rather than a `useEffect` copying it in after the fact: `editing` arrives
|
|
97
|
+
* asynchronously (`useManageableEventTypes` is a bridge read), and the
|
|
98
|
+
* parent already withholds this component until it has landed, so there is
|
|
99
|
+
* no later moment for a field to overwrite a half-typed edit.
|
|
100
|
+
*/
|
|
101
|
+
function EventTypeFields({ editing, schedules, members, onCancel, onSaved, }) {
|
|
102
|
+
const isEdit = editing !== null;
|
|
103
|
+
const { session } = (0, provider_js_1.useConnect)();
|
|
35
104
|
const defaultHostId = (0, react_1.useMemo)(() => {
|
|
36
105
|
const self = members.find((member) => member.userId === session?.user?.userId);
|
|
37
106
|
return self?.memberId ?? members[0]?.memberId ?? "";
|
|
38
107
|
}, [members, session?.user?.userId]);
|
|
39
|
-
const [name, setName] = (0, react_1.useState)("");
|
|
40
|
-
const [description, setDescription] = (0, react_1.useState)("");
|
|
41
|
-
const [durationMinutes, setDurationMinutes] = (0, react_1.useState)(30);
|
|
42
|
-
const [bufferBefore, setBufferBefore] = (0, react_1.useState)(5);
|
|
43
|
-
const [bufferAfter, setBufferAfter] = (0, react_1.useState)(5);
|
|
44
|
-
const [scheduleId, setScheduleId] = (0, react_1.useState)("");
|
|
45
|
-
const [hostMemberId, setHostMemberId] = (0, react_1.useState)("");
|
|
46
|
-
const [locationKind, setLocationKind] = (0, react_1.useState)("phone");
|
|
47
|
-
const [locationValue, setLocationValue] = (0, react_1.useState)("");
|
|
108
|
+
const [name, setName] = (0, react_1.useState)(editing?.name ?? "");
|
|
109
|
+
const [description, setDescription] = (0, react_1.useState)(editing?.description ?? "");
|
|
110
|
+
const [durationMinutes, setDurationMinutes] = (0, react_1.useState)(editing?.durationMinutes ?? 30);
|
|
111
|
+
const [bufferBefore, setBufferBefore] = (0, react_1.useState)(editing?.bufferBeforeMinutes ?? 5);
|
|
112
|
+
const [bufferAfter, setBufferAfter] = (0, react_1.useState)(editing?.bufferAfterMinutes ?? 5);
|
|
113
|
+
const [scheduleId, setScheduleId] = (0, react_1.useState)(editing?.scheduleId ?? "");
|
|
114
|
+
const [hostMemberId, setHostMemberId] = (0, react_1.useState)(editing?.host.kind === "member" ? editing.host.memberId : "");
|
|
115
|
+
const [locationKind, setLocationKind] = (0, react_1.useState)(editing?.location.kind ?? "phone");
|
|
116
|
+
const [locationValue, setLocationValue] = (0, react_1.useState)(editing?.location.value ?? "");
|
|
48
117
|
const effectiveScheduleId = scheduleId || schedules[0]?.id || "";
|
|
49
118
|
const effectiveHostMemberId = hostMemberId || defaultHostId;
|
|
50
|
-
const { createEventType, isPending, error, clearError } = (0, use_scheduling_js_1.useCreateEventType)();
|
|
119
|
+
const { createEventType, isPending: isCreating, error: createError, clearError: clearCreateError } = (0, use_scheduling_js_1.useCreateEventType)();
|
|
120
|
+
const { updateEventType, isPending: isUpdating, error: updateError, clearError: clearUpdateError } = (0, use_scheduling_js_1.useUpdateEventType)();
|
|
121
|
+
const isPending = isCreating || isUpdating;
|
|
122
|
+
const error = isEdit ? updateError : createError;
|
|
123
|
+
const clearError = isEdit ? clearUpdateError : clearCreateError;
|
|
51
124
|
const isFormValid = name.trim().length > 0 &&
|
|
52
125
|
durationMinutes >= 5 &&
|
|
53
126
|
durationMinutes <= 480 &&
|
|
@@ -60,7 +133,7 @@ function ConnectEventTypeFormModal({ isOpen, onOpenChange, onCreated, }) {
|
|
|
60
133
|
return;
|
|
61
134
|
clearError();
|
|
62
135
|
const trimmedDescription = description.trim();
|
|
63
|
-
const
|
|
136
|
+
const shared = {
|
|
64
137
|
name: name.trim(),
|
|
65
138
|
description: trimmedDescription.length > 0 ? trimmedDescription : null,
|
|
66
139
|
durationMinutes,
|
|
@@ -72,11 +145,12 @@ function ConnectEventTypeFormModal({ isOpen, onOpenChange, onCreated, }) {
|
|
|
72
145
|
kind: locationKind,
|
|
73
146
|
value: locationValue.trim().length > 0 ? locationValue.trim() : null,
|
|
74
147
|
},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
148
|
+
};
|
|
149
|
+
const saved = isEdit
|
|
150
|
+
? await updateEventType({ eventTypeId: editing.id, ...shared })
|
|
151
|
+
: await createEventType(shared);
|
|
152
|
+
if (saved)
|
|
153
|
+
onSaved(saved);
|
|
80
154
|
};
|
|
81
|
-
return ((0, jsx_runtime_1.
|
|
155
|
+
return ((0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)(react_2.TextField, { isRequired: true, variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Name" }), (0, jsx_runtime_1.jsx)(react_2.Input, { placeholder: "Consultation", value: name, onChange: (e) => setName(e.currentTarget.value), disabled: isPending, autoFocus: true })] }), (0, jsx_runtime_1.jsxs)(react_2.TextField, { variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Description" }), (0, jsx_runtime_1.jsx)(react_2.TextArea, { placeholder: "What this appointment is for", value: description, onChange: (e) => setDescription(e.currentTarget.value), disabled: isPending, rows: 2 })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid gap-4 sm:grid-cols-3", children: [(0, jsx_runtime_1.jsxs)(react_2.TextField, { isRequired: true, variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Length (min)" }), (0, jsx_runtime_1.jsx)(react_2.Input, { type: "number", value: String(durationMinutes), onChange: (e) => setDurationMinutes(Number(e.currentTarget.value)), disabled: isPending })] }), (0, jsx_runtime_1.jsxs)(react_2.TextField, { variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Gap before" }), (0, jsx_runtime_1.jsx)(react_2.Input, { type: "number", value: String(bufferBefore), onChange: (e) => setBufferBefore(Number(e.currentTarget.value)), disabled: isPending })] }), (0, jsx_runtime_1.jsxs)(react_2.TextField, { variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Gap after" }), (0, jsx_runtime_1.jsx)(react_2.Input, { type: "number", value: String(bufferAfter), onChange: (e) => setBufferAfter(Number(e.currentTarget.value)), disabled: isPending })] })] }), schedules.length > 1 && ((0, jsx_runtime_1.jsxs)(react_2.Select, { variant: "secondary", value: effectiveScheduleId, onChange: (value) => setScheduleId(String(value)), isDisabled: isPending, children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Availability" }), (0, jsx_runtime_1.jsxs)(react_2.Select.Trigger, { children: [(0, jsx_runtime_1.jsx)(react_2.Select.Value, {}), (0, jsx_runtime_1.jsx)(react_2.Select.Indicator, {})] }), (0, jsx_runtime_1.jsx)(react_2.Select.Popover, { children: (0, jsx_runtime_1.jsx)(react_2.ListBox, { children: schedules.map((schedule) => ((0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: schedule.id, textValue: schedule.name, children: [schedule.name, (0, jsx_runtime_1.jsx)(react_2.ListBox.ItemIndicator, {})] }, schedule.id))) }) })] })), members.length > 1 && ((0, jsx_runtime_1.jsxs)(react_2.Select, { variant: "secondary", value: effectiveHostMemberId, onChange: (value) => setHostMemberId(String(value)), isDisabled: isPending, children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Who hosts it" }), (0, jsx_runtime_1.jsxs)(react_2.Select.Trigger, { children: [(0, jsx_runtime_1.jsx)(react_2.Select.Value, {}), (0, jsx_runtime_1.jsx)(react_2.Select.Indicator, {})] }), (0, jsx_runtime_1.jsx)(react_2.Select.Popover, { children: (0, jsx_runtime_1.jsx)(react_2.ListBox, { children: members.map((member) => ((0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: member.memberId, textValue: member.displayName ?? member.email, children: [member.displayName ?? member.email, (0, jsx_runtime_1.jsx)(react_2.ListBox.ItemIndicator, {})] }, member.memberId))) }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "grid gap-4 sm:grid-cols-2", children: [(0, jsx_runtime_1.jsxs)(react_2.Select, { variant: "secondary", value: locationKind, onChange: (value) => setLocationKind(value), isDisabled: isPending, children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Where" }), (0, jsx_runtime_1.jsxs)(react_2.Select.Trigger, { children: [(0, jsx_runtime_1.jsx)(react_2.Select.Value, {}), (0, jsx_runtime_1.jsx)(react_2.Select.Indicator, {})] }), (0, jsx_runtime_1.jsx)(react_2.Select.Popover, { children: (0, jsx_runtime_1.jsx)(react_2.ListBox, { children: LOCATION_KINDS.map((kind) => ((0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: kind, textValue: LOCATION_LABELS[kind], children: [LOCATION_LABELS[kind], (0, jsx_runtime_1.jsx)(react_2.ListBox.ItemIndicator, {})] }, kind))) }) })] }), (0, jsx_runtime_1.jsxs)(react_2.TextField, { variant: "secondary", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { children: "Details" }), (0, jsx_runtime_1.jsx)(react_2.TextArea, { placeholder: locationKind === "address" ? "Where to come" : "Left blank is fine", value: locationValue, onChange: (e) => setLocationValue(e.currentTarget.value), disabled: isPending, rows: 2 })] })] }), error && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: error }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-end gap-2", children: [(0, jsx_runtime_1.jsx)(react_2.Button, { type: "button", variant: "secondary", onPress: onCancel, isDisabled: isPending, children: "Cancel" }), (0, jsx_runtime_1.jsx)(react_2.Button, { type: "submit", variant: "primary", isDisabled: !isFormValid, children: isPending ? "Saving..." : isEdit ? "Save changes" : "Create" })] })] }));
|
|
82
156
|
}
|
|
@@ -7,6 +7,7 @@ const react_1 = require("react");
|
|
|
7
7
|
const react_2 = require("@heroui/react");
|
|
8
8
|
const lucide_react_1 = require("lucide-react");
|
|
9
9
|
const provider_js_1 = require("../provider.js");
|
|
10
|
+
const create_workspace_form_js_1 = require("./create-workspace-form.js");
|
|
10
11
|
const motion_js_1 = require("./motion.js");
|
|
11
12
|
const shell_context_js_1 = require("./shell-context.js");
|
|
12
13
|
/** `Acme Logistics` -> `AL`. The collapsed rail's whole label for a workspace. */
|
|
@@ -14,9 +15,6 @@ function workspaceInitials(name) {
|
|
|
14
15
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
15
16
|
return (parts.slice(0, 2).map((part) => part[0]).join("") || "?").toUpperCase();
|
|
16
17
|
}
|
|
17
|
-
function openExternal(url) {
|
|
18
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
19
|
-
}
|
|
20
18
|
/**
|
|
21
19
|
* Switches which workspace this application is looking at.
|
|
22
20
|
*
|
|
@@ -31,9 +29,9 @@ function openExternal(url) {
|
|
|
31
29
|
* a workspace is *created*, so hiding it at one workspace would guarantee
|
|
32
30
|
* nobody ever reached two. `alwaysShow` is the escape hatch for a partner
|
|
33
31
|
* application that wants that same discoverability without becoming Connect:
|
|
34
|
-
* it keeps the dropdown open below two workspaces and adds a row that
|
|
35
|
-
*
|
|
36
|
-
* that
|
|
32
|
+
* it keeps the dropdown open below two workspaces and adds a row that opens
|
|
33
|
+
* `<ConnectCreateWorkspaceModal>` inline, auto-switching into the result when
|
|
34
|
+
* that is possible - see `handleCreated` below.
|
|
37
35
|
*
|
|
38
36
|
* **Authorization is the data, not a prop.** The list comes from the caller's
|
|
39
37
|
* own session (`GET /workspaces` takes no user id), so a workspace the user
|
|
@@ -53,12 +51,13 @@ function openExternal(url) {
|
|
|
53
51
|
* `onSwitched` defaults to.
|
|
54
52
|
*/
|
|
55
53
|
function ConnectWorkspaceSwitcher({ onSwitched, row = true, className, alwaysShow = false, }) {
|
|
56
|
-
const { mutateResource
|
|
54
|
+
const { mutateResource } = (0, provider_js_1.useConnect)();
|
|
57
55
|
const { collapsed } = (0, shell_context_js_1.useConnectShell)();
|
|
58
56
|
const user = (0, provider_js_1.useConnectUser)();
|
|
59
|
-
const { data, isLoading } = (0, provider_js_1.useConnectWorkspaces)();
|
|
57
|
+
const { data, isLoading, refetch } = (0, provider_js_1.useConnectWorkspaces)();
|
|
60
58
|
const [switching, setSwitching] = (0, react_1.useState)(null);
|
|
61
59
|
const [error, setError] = (0, react_1.useState)(null);
|
|
60
|
+
const [isCreateOpen, setIsCreateOpen] = (0, react_1.useState)(false);
|
|
62
61
|
const workspaces = data?.workspaces ?? [];
|
|
63
62
|
// Nothing loaded yet. Deliberately renders `null` rather than a skeleton:
|
|
64
63
|
// this sits in persistent chrome, and a placeholder that resolves to
|
|
@@ -93,11 +92,45 @@ function ConnectWorkspaceSwitcher({ onSwitched, row = true, className, alwaysSho
|
|
|
93
92
|
setSwitching(null);
|
|
94
93
|
}
|
|
95
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* After a successful create: bring the new row into this list (it is
|
|
97
|
+
* created, not switched to - see `ConnectCreateWorkspaceModal`'s doc
|
|
98
|
+
* comment), then move into it when that is actually possible.
|
|
99
|
+
*
|
|
100
|
+
* Deliberately **not** `handleSwitch` - that function's `selectable()`
|
|
101
|
+
* guard requires `subscribed`, which a workspace this young never is, so
|
|
102
|
+
* calling it here would silently do nothing. `SwitchWorkspaceUseCase`
|
|
103
|
+
* itself has no subscription gate, only membership/MFA/audience, so this
|
|
104
|
+
* calls the bridge directly and only withholds on `inAudience` - a
|
|
105
|
+
* workspace of a type this application is not sold to, which `switch`
|
|
106
|
+
* really does refuse.
|
|
107
|
+
*/
|
|
108
|
+
const handleCreated = async (workspace) => {
|
|
109
|
+
setIsCreateOpen(false);
|
|
110
|
+
refetch();
|
|
111
|
+
if (!workspace.inAudience)
|
|
112
|
+
return;
|
|
113
|
+
setSwitching(workspace.workspaceId);
|
|
114
|
+
setError(null);
|
|
115
|
+
try {
|
|
116
|
+
await mutateResource("/workspaces/switch", { workspaceId: workspace.workspaceId });
|
|
117
|
+
if (onSwitched) {
|
|
118
|
+
onSwitched(workspace);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
window.location.reload();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
setError(cause instanceof Error ? cause.message : "Workspace created, but could not switch to it");
|
|
126
|
+
setSwitching(null);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
96
129
|
return ((0, jsx_runtime_1.jsxs)("div", { className: row ? "" : className, children: [(0, jsx_runtime_1.jsxs)(react_2.Dropdown, { children: [row ? (
|
|
97
130
|
// A 32px tile rather than a 20px glyph, so the initials land on the
|
|
98
131
|
// same optical centre the avatar below them uses - see
|
|
99
132
|
// `SIDEBAR_ROW_TILE`. Collapsed, this tile *is* the switcher.
|
|
100
|
-
(0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "tertiary", "aria-label": `Workspace: ${label}. Switch workspace`, className: (0, shell_context_js_1.sidebarRowClass)({ tile: true, className }), children: [(0, jsx_runtime_1.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-xs font-semibold text-accent", children: workspaceInitials(label) }), (0, jsx_runtime_1.jsx)(shell_context_js_1.SidebarLabel, { className: "text-sm font-medium text-foreground", children: label }), (0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className: "shrink-0 text-muted", animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "size-4" }) })] })) : ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "secondary", size: "sm", className: "w-full justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "truncate", children: label }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" })] })), (0, jsx_runtime_1.jsx)(react_2.Dropdown.Popover, { children: (0, jsx_runtime_1.jsxs)(react_2.ListBox, { children: [workspaces.map((workspace) => ((0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: workspace.workspaceId, textValue: workspace.name, isDisabled: !selectable(workspace) || switching !== null, onAction: () => void handleSwitch(workspace), children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm", children: workspace.name }), selectable(workspace) ? null : ((0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: reasonUnavailable(workspace) }))] }), (0, jsx_runtime_1.jsx)(react_2.ListBox.ItemIndicator, {})] }, workspace.workspaceId))), alwaysShow ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [workspaces.length > 0 && (0, jsx_runtime_1.jsx)(react_2.Separator, {}), (0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: "__create_workspace", textValue: "Create workspace", onAction: () =>
|
|
133
|
+
(0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "tertiary", "aria-label": `Workspace: ${label}. Switch workspace`, className: (0, shell_context_js_1.sidebarRowClass)({ tile: true, className }), children: [(0, jsx_runtime_1.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-xs font-semibold text-accent", children: workspaceInitials(label) }), (0, jsx_runtime_1.jsx)(shell_context_js_1.SidebarLabel, { className: "text-sm font-medium text-foreground", children: label }), (0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className: "shrink-0 text-muted", animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "size-4" }) })] })) : ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "secondary", size: "sm", className: "w-full justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "truncate", children: label }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" })] })), (0, jsx_runtime_1.jsx)(react_2.Dropdown.Popover, { children: (0, jsx_runtime_1.jsxs)(react_2.ListBox, { children: [workspaces.map((workspace) => ((0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: workspace.workspaceId, textValue: workspace.name, isDisabled: !selectable(workspace) || switching !== null, onAction: () => void handleSwitch(workspace), children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm", children: workspace.name }), selectable(workspace) ? null : ((0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: reasonUnavailable(workspace) }))] }), (0, jsx_runtime_1.jsx)(react_2.ListBox.ItemIndicator, {})] }, workspace.workspaceId))), alwaysShow ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [workspaces.length > 0 && (0, jsx_runtime_1.jsx)(react_2.Separator, {}), (0, jsx_runtime_1.jsxs)(react_2.ListBox.Item, { id: "__create_workspace", textValue: "Create workspace", onAction: () => setIsCreateOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { className: "size-4 shrink-0 text-muted", "aria-hidden": true }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm", children: "Create workspace" })] })] })) : null] }) })] }), error && !collapsed ? (0, jsx_runtime_1.jsx)("p", { className: "mt-1 px-2 text-xs text-danger", children: error }) : null, alwaysShow ? ((0, jsx_runtime_1.jsx)(create_workspace_form_js_1.ConnectCreateWorkspaceModal, { isOpen: isCreateOpen, onOpenChange: setIsCreateOpen, onCreated: (workspace) => void handleCreated(workspace) })) : null] }));
|
|
101
134
|
}
|
|
102
135
|
/** Belongs to the user, and usable here. The same two facts `switch` enforces. */
|
|
103
136
|
function selectable(workspace) {
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
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
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.useCreateEventType = exports.useCreateAvailabilitySchedule = exports.useAvailabilitySchedules = exports.useBookableEventTypes = exports.ConnectEventTypeFormModal = exports.ConnectAvailabilityFormModal = 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 = exports.ConnectFilterTabs = exports.ConnectCursorPagination = 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.useConnectCall = exports.ConnectCallingStatus = exports.ConnectCallButton = exports.ConnectCallProvider = 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,7 +69,7 @@ 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
|
|
72
|
+
// A link to *any* Connect portal page that offers a `"Back to <your app>"` way
|
|
73
73
|
// home - what `ConnectProfileButton` builds for `/profile` internally, made
|
|
74
74
|
// reusable for a host linking out to a different Connect page (e.g.
|
|
75
75
|
// `/calendar/settings`) on its own.
|
|
@@ -264,6 +264,18 @@ Object.defineProperty(exports, "ConnectWorkspaceSwitcher", { enumerable: true, g
|
|
|
264
264
|
var provider_js_2 = require("./provider.js");
|
|
265
265
|
Object.defineProperty(exports, "useConnectWorkspaces", { enumerable: true, get: function () { return provider_js_2.useConnectWorkspaces; } });
|
|
266
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; } });
|
|
274
|
+
var call_button_js_1 = require("./components/call-button.js");
|
|
275
|
+
Object.defineProperty(exports, "ConnectCallProvider", { enumerable: true, get: function () { return call_button_js_1.ConnectCallProvider; } });
|
|
276
|
+
Object.defineProperty(exports, "ConnectCallButton", { enumerable: true, get: function () { return call_button_js_1.ConnectCallButton; } });
|
|
277
|
+
Object.defineProperty(exports, "ConnectCallingStatus", { enumerable: true, get: function () { return call_button_js_1.ConnectCallingStatus; } });
|
|
278
|
+
Object.defineProperty(exports, "useConnectCall", { enumerable: true, get: function () { return call_button_js_1.useConnectCall; } });
|
|
267
279
|
var booking_calendar_js_1 = require("./components/booking-calendar.js");
|
|
268
280
|
Object.defineProperty(exports, "ConnectBookingCalendar", { enumerable: true, get: function () { return booking_calendar_js_1.ConnectBookingCalendar; } });
|
|
269
281
|
var booking_dialogs_js_1 = require("./components/booking-dialogs.js");
|
|
@@ -281,7 +293,10 @@ var use_scheduling_js_1 = require("./use-scheduling.js");
|
|
|
281
293
|
Object.defineProperty(exports, "useBookableEventTypes", { enumerable: true, get: function () { return use_scheduling_js_1.useBookableEventTypes; } });
|
|
282
294
|
Object.defineProperty(exports, "useAvailabilitySchedules", { enumerable: true, get: function () { return use_scheduling_js_1.useAvailabilitySchedules; } });
|
|
283
295
|
Object.defineProperty(exports, "useCreateAvailabilitySchedule", { enumerable: true, get: function () { return use_scheduling_js_1.useCreateAvailabilitySchedule; } });
|
|
296
|
+
Object.defineProperty(exports, "useUpdateAvailabilitySchedule", { enumerable: true, get: function () { return use_scheduling_js_1.useUpdateAvailabilitySchedule; } });
|
|
297
|
+
Object.defineProperty(exports, "useManageableEventTypes", { enumerable: true, get: function () { return use_scheduling_js_1.useManageableEventTypes; } });
|
|
284
298
|
Object.defineProperty(exports, "useCreateEventType", { enumerable: true, get: function () { return use_scheduling_js_1.useCreateEventType; } });
|
|
299
|
+
Object.defineProperty(exports, "useUpdateEventType", { enumerable: true, get: function () { return use_scheduling_js_1.useUpdateEventType; } });
|
|
285
300
|
Object.defineProperty(exports, "useAvailableSlots", { enumerable: true, get: function () { return use_scheduling_js_1.useAvailableSlots; } });
|
|
286
301
|
Object.defineProperty(exports, "useBookings", { enumerable: true, get: function () { return use_scheduling_js_1.useBookings; } });
|
|
287
302
|
Object.defineProperty(exports, "useBookingActions", { enumerable: true, get: function () { return use_scheduling_js_1.useBookingActions; } });
|
|
@@ -10,7 +10,7 @@ const provider_js_1 = require("./provider.js");
|
|
|
10
10
|
* `connectPortalUrl(appUrl, clientId, "/calendar/settings")`, and so on.
|
|
11
11
|
*
|
|
12
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
|
|
13
|
+
* these two params only add the `"Back to <your app>"` banner
|
|
14
14
|
* (`ApplicationReturnBar` on Connect's side). It validates `return_to`
|
|
15
15
|
* against this `client_id`'s registered redirect origins and silently drops
|
|
16
16
|
* the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.useBookableEventTypes = useBookableEventTypes;
|
|
5
5
|
exports.useAvailabilitySchedules = useAvailabilitySchedules;
|
|
6
|
+
exports.useManageableEventTypes = useManageableEventTypes;
|
|
6
7
|
exports.useCreateAvailabilitySchedule = useCreateAvailabilitySchedule;
|
|
8
|
+
exports.useUpdateAvailabilitySchedule = useUpdateAvailabilitySchedule;
|
|
7
9
|
exports.useCreateEventType = useCreateEventType;
|
|
10
|
+
exports.useUpdateEventType = useUpdateEventType;
|
|
8
11
|
exports.useAvailableSlots = useAvailableSlots;
|
|
9
12
|
exports.useBookings = useBookings;
|
|
10
13
|
exports.useBookingActions = useBookingActions;
|
|
@@ -34,6 +37,16 @@ function useAvailabilitySchedules() {
|
|
|
34
37
|
const { data, isLoading, error } = (0, provider_js_1.useConnectResource)("/scheduling/schedules");
|
|
35
38
|
return { schedules: data?.schedules ?? [], isLoading, error };
|
|
36
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
|
+
}
|
|
37
50
|
/**
|
|
38
51
|
* Gives a workspace its first working hours.
|
|
39
52
|
*
|
|
@@ -50,6 +63,16 @@ function useCreateAvailabilitySchedule() {
|
|
|
50
63
|
createSchedule: (input) => run("/scheduling/schedules", input),
|
|
51
64
|
};
|
|
52
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
|
+
}
|
|
53
76
|
/**
|
|
54
77
|
* Creates the first thing a workspace can be booked for.
|
|
55
78
|
*
|
|
@@ -66,6 +89,20 @@ function useCreateEventType() {
|
|
|
66
89
|
createEventType: (input) => run("/scheduling/event-types", input),
|
|
67
90
|
};
|
|
68
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
|
+
}
|
|
69
106
|
/**
|
|
70
107
|
* Free times for one appointment type.
|
|
71
108
|
*
|
|
@@ -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
|
+
}
|