@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/dist/cjs/client/components/availability-form.js +52 -24
  3. package/dist/cjs/client/components/call-button.js +398 -0
  4. package/dist/cjs/client/components/create-workspace-form.js +66 -0
  5. package/dist/cjs/client/components/event-type-form.js +99 -25
  6. package/dist/cjs/client/components/workspace-switcher.js +42 -9
  7. package/dist/cjs/client/index.js +17 -2
  8. package/dist/cjs/client/portal-links.js +1 -1
  9. package/dist/cjs/client/use-scheduling.js +37 -0
  10. package/dist/cjs/client/use-workspaces.js +41 -0
  11. package/dist/cjs/server/client.js +1 -1
  12. package/dist/cjs/server/handler.js +176 -2
  13. package/dist/cjs/server/resources/calls.js +58 -0
  14. package/dist/cjs/server/resources/scheduling.js +67 -0
  15. package/dist/cjs/server/resources/workspaces.js +30 -11
  16. package/dist/esm/client/components/availability-form.js +53 -25
  17. package/dist/esm/client/components/call-button.js +358 -0
  18. package/dist/esm/client/components/create-workspace-form.js +63 -0
  19. package/dist/esm/client/components/event-type-form.js +100 -26
  20. package/dist/esm/client/components/workspace-switcher.js +42 -9
  21. package/dist/esm/client/index.js +8 -2
  22. package/dist/esm/client/portal-links.js +1 -1
  23. package/dist/esm/client/use-scheduling.js +34 -0
  24. package/dist/esm/client/use-workspaces.js +38 -0
  25. package/dist/esm/server/client.js +1 -1
  26. package/dist/esm/server/handler.js +176 -2
  27. package/dist/esm/server/resources/calls.js +58 -0
  28. package/dist/esm/server/resources/scheduling.js +67 -0
  29. package/dist/esm/server/resources/workspaces.js +30 -11
  30. package/dist/types/client/components/availability-form.d.ts +23 -12
  31. package/dist/types/client/components/call-button.d.ts +132 -0
  32. package/dist/types/client/components/create-workspace-form.d.ts +32 -0
  33. package/dist/types/client/components/event-type-form.d.ts +30 -8
  34. package/dist/types/client/components/workspace-switcher.d.ts +9 -9
  35. package/dist/types/client/index.d.ts +7 -1
  36. package/dist/types/client/portal-links.d.ts +1 -1
  37. package/dist/types/client/use-scheduling.d.ts +46 -2
  38. package/dist/types/client/use-workspaces.d.ts +27 -0
  39. package/dist/types/server/client.d.ts +1 -1
  40. package/dist/types/server/resources/calls.d.ts +32 -1
  41. package/dist/types/server/resources/scheduling.d.ts +66 -2
  42. package/dist/types/server/resources/workspaces.d.ts +28 -0
  43. package/dist/types/types.d.ts +143 -1
  44. 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 the first thing a workspace can be booked for, from inside a host
18
- * application rather than a link-out to Connect.
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 the
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, onCreated, }) {
31
- const { session } = (0, provider_js_1.useConnect)();
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 members = (0, react_1.useMemo)(() => membersData?.members ?? [], [membersData]);
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 created = await createEventType({
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
- if (created) {
77
- onCreated?.(created);
78
- onOpenChange(false);
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.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: "New appointment type" }) }), (0, jsx_runtime_1.jsx)(react_2.Modal.Body, { children: schedules.length === 0 ? ((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." })) : ((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: () => onOpenChange(false), isDisabled: isPending, children: "Cancel" }), (0, jsx_runtime_1.jsx)(react_2.Button, { type: "submit", variant: "primary", isDisabled: !isFormValid, children: isPending ? "Saving..." : "Create" })] })] })) })] }) }) }) }));
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 sends
35
- * the member to Connect to actually create one, since there is nothing here
36
- * that can.
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, appUrl } = (0, provider_js_1.useConnect)();
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: () => openExternal(appUrl), 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] }));
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) {
@@ -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
+ }
@@ -179,4 +179,4 @@ class ConnectClient {
179
179
  }
180
180
  exports.ConnectClient = ConnectClient;
181
181
  /** Kept in step with `package.json` by `sdk.test.ts`. */
182
- exports.SDK_VERSION = "0.38.0";
182
+ exports.SDK_VERSION = "0.41.0";