@cosmoops/connect-sdk 0.45.0 → 0.46.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 (59) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/cjs/client/components/access-gate.js +1 -1
  3. package/dist/cjs/client/components/account-sessions.js +1 -1
  4. package/dist/cjs/client/components/ai-format.js +3 -3
  5. package/dist/cjs/client/components/ai-widgets.js +5 -5
  6. package/dist/cjs/client/components/apps-grid.js +2 -2
  7. package/dist/cjs/client/components/ask-ai-history.js +2 -2
  8. package/dist/cjs/client/components/ask-ai.js +1 -1
  9. package/dist/cjs/client/components/availability-form.js +1 -1
  10. package/dist/cjs/client/components/booking-calendar.js +4 -4
  11. package/dist/cjs/client/components/booking-dialogs.js +2 -2
  12. package/dist/cjs/client/components/call-button.js +6 -6
  13. package/dist/cjs/client/components/create-workspace-form.js +1 -1
  14. package/dist/cjs/client/components/event-type-form.js +1 -1
  15. package/dist/cjs/client/components/filter-tabs.js +1 -1
  16. package/dist/cjs/client/components/grievance-notice.js +1 -1
  17. package/dist/cjs/client/components/integrations-modal.js +2 -2
  18. package/dist/cjs/client/components/plans.js +5 -5
  19. package/dist/cjs/client/components/readiness.js +1 -1
  20. package/dist/cjs/client/components/subscribe-dialog.js +1 -1
  21. package/dist/cjs/client/components/subscription.js +5 -5
  22. package/dist/cjs/client/components/usage-meter.js +2 -2
  23. package/dist/cjs/client/components/user-menu.js +1 -1
  24. package/dist/cjs/client/components/workspace-switcher.js +1 -1
  25. package/dist/cjs/server/client.js +1 -1
  26. package/dist/cjs/server/resources/drive.js +11 -0
  27. package/dist/cjs/ui/page-shell.js +64 -2
  28. package/dist/esm/client/components/access-gate.js +1 -1
  29. package/dist/esm/client/components/account-sessions.js +1 -1
  30. package/dist/esm/client/components/ai-format.js +3 -3
  31. package/dist/esm/client/components/ai-widgets.js +5 -5
  32. package/dist/esm/client/components/apps-grid.js +2 -2
  33. package/dist/esm/client/components/ask-ai-history.js +2 -2
  34. package/dist/esm/client/components/ask-ai.js +1 -1
  35. package/dist/esm/client/components/availability-form.js +1 -1
  36. package/dist/esm/client/components/booking-calendar.js +4 -4
  37. package/dist/esm/client/components/booking-dialogs.js +2 -2
  38. package/dist/esm/client/components/call-button.js +6 -6
  39. package/dist/esm/client/components/create-workspace-form.js +1 -1
  40. package/dist/esm/client/components/event-type-form.js +1 -1
  41. package/dist/esm/client/components/filter-tabs.js +1 -1
  42. package/dist/esm/client/components/grievance-notice.js +1 -1
  43. package/dist/esm/client/components/integrations-modal.js +2 -2
  44. package/dist/esm/client/components/plans.js +5 -5
  45. package/dist/esm/client/components/readiness.js +1 -1
  46. package/dist/esm/client/components/subscribe-dialog.js +1 -1
  47. package/dist/esm/client/components/subscription.js +5 -5
  48. package/dist/esm/client/components/usage-meter.js +2 -2
  49. package/dist/esm/client/components/user-menu.js +1 -1
  50. package/dist/esm/client/components/workspace-switcher.js +1 -1
  51. package/dist/esm/server/client.js +1 -1
  52. package/dist/esm/server/resources/drive.js +11 -0
  53. package/dist/esm/ui/page-shell.js +31 -2
  54. package/dist/theme.css +78 -2
  55. package/dist/types/server/client.d.ts +1 -1
  56. package/dist/types/server/resources/drive.d.ts +8 -1
  57. package/dist/types/types.d.ts +9 -0
  58. package/dist/types/ui/page-shell.d.ts +20 -2
  59. package/package.json +1 -1
@@ -149,5 +149,5 @@ function EventTypeFields({ editing, schedules, members, onCancel, onSaved, }) {
149
149
  if (saved)
150
150
  onSaved(saved);
151
151
  };
152
- return (_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs(TextField, { isRequired: true, variant: "secondary", children: [_jsx(Label, { children: "Name" }), _jsx(Input, { placeholder: "Consultation", value: name, onChange: (e) => setName(e.currentTarget.value), disabled: isPending, autoFocus: true })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Description" }), _jsx(TextArea, { placeholder: "What this appointment is for", value: description, onChange: (e) => setDescription(e.currentTarget.value), disabled: isPending, rows: 2 })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(TextField, { isRequired: true, variant: "secondary", children: [_jsx(Label, { children: "Length (min)" }), _jsx(Input, { type: "number", value: String(durationMinutes), onChange: (e) => setDurationMinutes(Number(e.currentTarget.value)), disabled: isPending })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Gap before" }), _jsx(Input, { type: "number", value: String(bufferBefore), onChange: (e) => setBufferBefore(Number(e.currentTarget.value)), disabled: isPending })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Gap after" }), _jsx(Input, { type: "number", value: String(bufferAfter), onChange: (e) => setBufferAfter(Number(e.currentTarget.value)), disabled: isPending })] })] }), schedules.length > 1 && (_jsxs(Select, { variant: "secondary", value: effectiveScheduleId, onChange: (value) => setScheduleId(String(value)), isDisabled: isPending, children: [_jsx(Label, { children: "Availability" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: schedules.map((schedule) => (_jsxs(ListBox.Item, { id: schedule.id, textValue: schedule.name, children: [schedule.name, _jsx(ListBox.ItemIndicator, {})] }, schedule.id))) }) })] })), members.length > 1 && (_jsxs(Select, { variant: "secondary", value: effectiveHostMemberId, onChange: (value) => setHostMemberId(String(value)), isDisabled: isPending, children: [_jsx(Label, { children: "Who hosts it" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: members.map((member) => (_jsxs(ListBox.Item, { id: member.memberId, textValue: member.displayName ?? member.email, children: [member.displayName ?? member.email, _jsx(ListBox.ItemIndicator, {})] }, member.memberId))) }) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs(Select, { variant: "secondary", value: locationKind, onChange: (value) => setLocationKind(value), isDisabled: isPending, children: [_jsx(Label, { children: "Where" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: LOCATION_KINDS.map((kind) => (_jsxs(ListBox.Item, { id: kind, textValue: LOCATION_LABELS[kind], children: [LOCATION_LABELS[kind], _jsx(ListBox.ItemIndicator, {})] }, kind))) }) })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Details" }), _jsx(TextArea, { placeholder: locationKind === "address" ? "Where to come" : "Left blank is fine", value: locationValue, onChange: (e) => setLocationValue(e.currentTarget.value), disabled: isPending, rows: 2 })] })] }), error && _jsx("p", { className: "text-sm text-danger", children: error }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onPress: onCancel, isDisabled: isPending, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", isDisabled: !isFormValid, children: isPending ? "Saving..." : isEdit ? "Save changes" : "Create" })] })] }));
152
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs(TextField, { isRequired: true, variant: "secondary", children: [_jsx(Label, { children: "Name" }), _jsx(Input, { placeholder: "Consultation", value: name, onChange: (e) => setName(e.currentTarget.value), disabled: isPending, autoFocus: true })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Description" }), _jsx(TextArea, { placeholder: "What this appointment is for", value: description, onChange: (e) => setDescription(e.currentTarget.value), disabled: isPending, rows: 2 })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(TextField, { isRequired: true, variant: "secondary", children: [_jsx(Label, { children: "Length (min)" }), _jsx(Input, { type: "number", value: String(durationMinutes), onChange: (e) => setDurationMinutes(Number(e.currentTarget.value)), disabled: isPending })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Gap before" }), _jsx(Input, { type: "number", value: String(bufferBefore), onChange: (e) => setBufferBefore(Number(e.currentTarget.value)), disabled: isPending })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Gap after" }), _jsx(Input, { type: "number", value: String(bufferAfter), onChange: (e) => setBufferAfter(Number(e.currentTarget.value)), disabled: isPending })] })] }), schedules.length > 1 && (_jsxs(Select, { variant: "secondary", value: effectiveScheduleId, onChange: (value) => setScheduleId(String(value)), isDisabled: isPending, children: [_jsx(Label, { children: "Availability" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: schedules.map((schedule) => (_jsxs(ListBox.Item, { id: schedule.id, textValue: schedule.name, children: [schedule.name, _jsx(ListBox.ItemIndicator, {})] }, schedule.id))) }) })] })), members.length > 1 && (_jsxs(Select, { variant: "secondary", value: effectiveHostMemberId, onChange: (value) => setHostMemberId(String(value)), isDisabled: isPending, children: [_jsx(Label, { children: "Who hosts it" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: members.map((member) => (_jsxs(ListBox.Item, { id: member.memberId, textValue: member.displayName ?? member.email, children: [member.displayName ?? member.email, _jsx(ListBox.ItemIndicator, {})] }, member.memberId))) }) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs(Select, { variant: "secondary", value: locationKind, onChange: (value) => setLocationKind(value), isDisabled: isPending, children: [_jsx(Label, { children: "Where" }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: LOCATION_KINDS.map((kind) => (_jsxs(ListBox.Item, { id: kind, textValue: LOCATION_LABELS[kind], children: [LOCATION_LABELS[kind], _jsx(ListBox.ItemIndicator, {})] }, kind))) }) })] }), _jsxs(TextField, { variant: "secondary", children: [_jsx(Label, { children: "Details" }), _jsx(TextArea, { placeholder: locationKind === "address" ? "Where to come" : "Left blank is fine", value: locationValue, onChange: (e) => setLocationValue(e.currentTarget.value), disabled: isPending, rows: 2 })] })] }), error && _jsx("p", { className: "text-sm text-danger-soft-foreground", children: error }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onPress: onCancel, isDisabled: isPending, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", isDisabled: !isFormValid, children: isPending ? "Saving..." : isEdit ? "Save changes" : "Create" })] })] }));
153
153
  }
@@ -34,6 +34,6 @@ export function ConnectFilterTabs({ options, value, onChange, label, className,
34
34
  */
35
35
  _jsx("div", { role: "radiogroup", "aria-label": label, className: cn("-mx-1 flex flex-wrap gap-2 px-1", className), children: options.map((option) => {
36
36
  const isSelected = option.value === value;
37
- return (_jsx("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: _jsxs(Chip, { variant: isSelected ? "primary" : "soft", color: isSelected ? "accent" : "default", className: "min-h-8 px-3.5 py-1.5 text-sm", children: [option.label, option.count !== undefined && _jsx("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
37
+ return (_jsx("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", children: _jsxs(Chip, { variant: isSelected ? "primary" : "soft", color: isSelected ? "accent" : "default", className: "min-h-8 px-3.5 py-1.5 text-sm", children: [option.label, option.count !== undefined && _jsx("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
38
38
  }) }));
39
39
  }
@@ -24,5 +24,5 @@ export function ConnectGrievanceNotice({ title = "Grievance redressal", applicat
24
24
  if (!profile)
25
25
  return null;
26
26
  const { grievance } = profile;
27
- return (_jsxs("section", { className: cn("space-y-3", className), children: [title !== null && _jsx("h3", { className: "text-base font-semibold", children: title }), _jsxs("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", _jsx("strong", { className: "font-medium text-foreground", children: profile.legalName }), " ", "is the Data Fiduciary for personal data processed here, under the Digital Personal Data Protection Act, 2023."] }), _jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [_jsx("dt", { className: "text-muted", children: "Grievance Officer" }), _jsx("dd", { className: "text-foreground", children: grievance.officerName }), _jsx("dt", { className: "text-muted", children: "Email" }), _jsx("dd", { children: _jsx("a", { className: "text-accent underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), _jsx("dt", { className: "text-muted", children: "Contactable" }), _jsx("dd", { className: "text-foreground", children: grievance.hours }), _jsx("dt", { className: "text-muted", children: "Registered address" }), _jsx("dd", { className: "text-foreground", children: profile.registeredAddress })] }), _jsxs("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
27
+ return (_jsxs("section", { className: cn("space-y-3", className), children: [title !== null && _jsx("h3", { className: "text-base font-semibold", children: title }), _jsxs("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", _jsx("strong", { className: "font-medium text-foreground", children: profile.legalName }), " ", "is the Data Fiduciary for personal data processed here, under the Digital Personal Data Protection Act, 2023."] }), _jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [_jsx("dt", { className: "text-muted", children: "Grievance Officer" }), _jsx("dd", { className: "text-foreground", children: grievance.officerName }), _jsx("dt", { className: "text-muted", children: "Email" }), _jsx("dd", { children: _jsx("a", { className: "text-link underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), _jsx("dt", { className: "text-muted", children: "Contactable" }), _jsx("dd", { className: "text-foreground", children: grievance.hours }), _jsx("dt", { className: "text-muted", children: "Registered address" }), _jsx("dd", { className: "text-foreground", children: profile.registeredAddress })] }), _jsxs("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
28
28
  }
@@ -39,7 +39,7 @@ function ToolkitTile({ toolkit, isPending, onConnect, onDisconnect }) {
39
39
  // Deliberately a plain `<img>`: this package ships into any React app
40
40
  // and cannot assume `next/image` or any other framework loader.
41
41
  // eslint-disable-next-line @next/next/no-img-element
42
- _jsx("img", { src: toolkit.logoUrl, alt: "", className: "size-full object-cover" })) : (_jsx(Puzzle, { className: "size-6 text-muted", "aria-hidden": "true" })), toolkit.status === "failed" && (_jsx("span", { className: "absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-danger text-danger-foreground", children: _jsx(AlertCircle, { className: "size-3", "aria-hidden": "true" }) }))] }), _jsx("span", { className: "line-clamp-2 text-center text-xs font-medium text-foreground", children: toolkit.name }), _jsx(Chip, { size: "sm", variant: "soft", color: STATUS_COLOR[toolkit.status], children: STATUS_LABEL[toolkit.status] }), _jsxs("div", { className: "flex w-full flex-col gap-1.5 pt-1", children: [CAN_CONNECT.has(toolkit.status) && (_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Connect" })), toolkit.status === "active" && (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Reconnect" }), _jsxs(AlertDialog, { isOpen: confirmingDisconnect, onOpenChange: setConfirmingDisconnect, children: [_jsx(Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, children: "Disconnect" }), _jsx(AlertDialog.Backdrop, { children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { className: "sm:max-w-100", children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger" }), _jsxs(AlertDialog.Heading, { children: ["Disconnect ", toolkit.name, "?"] })] }), _jsx(AlertDialog.Body, { children: _jsx("p", { className: "text-sm text-muted", children: "Every member loses this connection immediately - it can be reconnected later." }) }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), _jsx(Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onDisconnect, children: "Disconnect" })] })] }) }) })] })] })), toolkit.status === "pending" && (_jsx(Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, onPress: onDisconnect, children: "Cancel" }))] })] }));
42
+ _jsx("img", { src: toolkit.logoUrl, alt: "", className: "size-full object-cover" })) : (_jsx(Puzzle, { className: "size-6 text-muted", "aria-hidden": "true" })), toolkit.status === "failed" && (_jsx("span", { className: "absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-danger text-danger-foreground", children: _jsx(AlertCircle, { className: "size-3", "aria-hidden": "true" }) }))] }), _jsx("span", { className: "line-clamp-2 text-center text-xs font-medium text-foreground", children: toolkit.name }), _jsx(Chip, { size: "sm", variant: "soft", color: STATUS_COLOR[toolkit.status], children: STATUS_LABEL[toolkit.status] }), _jsxs("div", { className: "flex w-full flex-col gap-1.5 pt-1", children: [CAN_CONNECT.has(toolkit.status) && (_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Connect" })), toolkit.status === "active" && (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Reconnect" }), _jsxs(AlertDialog, { isOpen: confirmingDisconnect, onOpenChange: setConfirmingDisconnect, children: [_jsx(Button, { size: "sm", variant: "danger-soft", className: "w-full text-xs", isDisabled: isPending, children: "Disconnect" }), _jsx(AlertDialog.Backdrop, { children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { className: "sm:max-w-100", children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger" }), _jsxs(AlertDialog.Heading, { children: ["Disconnect ", toolkit.name, "?"] })] }), _jsx(AlertDialog.Body, { children: _jsx("p", { className: "text-sm text-muted", children: "Every member loses this connection immediately - it can be reconnected later." }) }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), _jsx(Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onDisconnect, children: "Disconnect" })] })] }) }) })] })] })), toolkit.status === "pending" && (_jsx(Button, { size: "sm", variant: "danger-soft", className: "w-full text-xs", isDisabled: isPending, onPress: onDisconnect, children: "Cancel" }))] })] }));
43
43
  }
44
44
  /**
45
45
  * A grid of every Composio-hosted toolkit this workspace can connect -
@@ -88,5 +88,5 @@ export function ConnectIntegrationsModal({ className, label = "Integrations" })
88
88
  })
89
89
  .finally(() => setPendingSlug(null));
90
90
  };
91
- return (_jsx(MotionProvider, { children: _jsxs(Modal, { children: [_jsxs(Button, { variant: "secondary", className: className, children: [_jsx(Puzzle, { className: "size-4" }), label] }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { children: _jsxs(Modal.Dialog, { className: "sm:max-w-lg", children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Integrations" }) }), _jsxs(Modal.Body, { children: [isLoading && (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: Array.from({ length: 6 }, (_, index) => (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-xl border border-separator p-3", children: [_jsx(Skeleton, { className: "size-12 rounded-xl" }), _jsx(Skeleton, { className: "h-3 w-16" }), _jsx(Skeleton, { className: "h-7 w-full rounded-lg" })] }, index))) })), !isLoading && error && (_jsx("p", { className: "py-6 text-center text-sm text-danger", children: "Could not load integrations." })), !isLoading && !error && toolkits.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted", children: "No integrations are enabled on this platform yet." })), !isLoading && !error && toolkits.length > 0 && (_jsx(m.div, { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: toolkits.map((toolkit) => (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsx(ToolkitTile, { toolkit: toolkit, isPending: pendingSlug === toolkit.slug, onConnect: () => connect(toolkit.slug), onDisconnect: () => disconnect(toolkit.slug) }) }, toolkit.slug))) }))] })] }) }) })] }) }));
91
+ return (_jsx(MotionProvider, { children: _jsxs(Modal, { children: [_jsxs(Button, { variant: "secondary", className: className, children: [_jsx(Puzzle, { className: "size-4" }), label] }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { children: _jsxs(Modal.Dialog, { className: "sm:max-w-lg", children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Integrations" }) }), _jsxs(Modal.Body, { children: [isLoading && (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: Array.from({ length: 6 }, (_, index) => (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-xl border border-separator p-3", children: [_jsx(Skeleton, { className: "size-12 rounded-xl" }), _jsx(Skeleton, { className: "h-3 w-16" }), _jsx(Skeleton, { className: "h-7 w-full rounded-lg" })] }, index))) })), !isLoading && error && (_jsx("p", { className: "py-6 text-center text-sm text-danger-soft-foreground", children: "Could not load integrations." })), !isLoading && !error && toolkits.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted", children: "No integrations are enabled on this platform yet." })), !isLoading && !error && toolkits.length > 0 && (_jsx(m.div, { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: toolkits.map((toolkit) => (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsx(ToolkitTile, { toolkit: toolkit, isPending: pendingSlug === toolkit.slug, onConnect: () => connect(toolkit.slug), onDisconnect: () => disconnect(toolkit.slug) }) }, toolkit.slug))) }))] })] }) }) })] }) }));
92
92
  }
@@ -109,9 +109,9 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
109
109
  return _jsx("p", { className: "text-sm text-muted", children: "Sign in to see your plan options." });
110
110
  }
111
111
  if (error) {
112
- return _jsx("p", { className: "text-sm text-danger", children: "Could not load plans." });
112
+ return _jsx("p", { className: "text-sm text-danger-soft-foreground", children: "Could not load plans." });
113
113
  }
114
- return (_jsx(MotionProvider, { children: _jsxs("div", { className: cn("flex flex-col gap-6", className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), _jsx(Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: _jsx(Switch.Content, { children: _jsx(Switch.Control, { children: _jsx(Switch.Thumb, {}) }) }) }), _jsx("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && (_jsxs(Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), _jsx(Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [_jsx(Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), _jsxs("span", { children: [discountLabel(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
114
+ return (_jsx(MotionProvider, { children: _jsxs("div", { className: cn("flex flex-col gap-6", className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), _jsx(Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: _jsx(Switch.Content, { children: _jsx(Switch.Control, { children: _jsx(Switch.Thumb, {}) }) }) }), _jsx("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && (_jsxs(Chip, { color: "accent", variant: "soft", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), _jsx(Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent-soft-foreground", children: [_jsx(Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), _jsxs("span", { children: [discountLabel(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
115
115
  ? ` - code ${subscription.subscription.appliedDiscount.code}`
116
116
  : ""] })] })), _jsx(ConnectCheckoutStatusRow, { checkout: checkout }), _jsx(m.div, { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", variants: { show: { transition: { staggerChildren: 0.06 } } }, initial: "hidden", animate: "show", children: tiers.map((tier) => {
117
117
  // The price point this card is showing. A tier missing the toggled
@@ -131,7 +131,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
131
131
  tier.oneTime?.id === subscription.plan.id);
132
132
  const isCurrent = action === "current";
133
133
  const cadenceUnavailable = showAnnual ? tier.annual === null : tier.monthly === null;
134
- return (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 12 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsxs(Card, { className: isCurrentTier ? "h-full border-accent" : "h-full", children: [_jsxs(Card.Header, { children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx(Card.Title, { children: tier.name }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [tier.annualSavingPercent !== null && showAnnual && (_jsxs(Chip, { className: "bg-accent-soft text-accent", children: ["Save ", tier.annualSavingPercent, "%"] })), isCurrentTier && _jsx(Chip, { className: "bg-accent-soft text-accent", children: "Current" })] })] }), tier.description && _jsx(Card.Description, { children: tier.description })] }), _jsxs(Card.Content, { className: "flex flex-col gap-4", children: [_jsxs("div", { children: [_jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(m.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.15 }, children: isFreePlan(plan.price) ? (_jsx("span", { className: "text-2xl font-semibold text-foreground", children: "Free" })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-2xl font-semibold text-foreground", children: formatPrice(plan.price) }), _jsx("span", { className: "text-sm text-muted", children: pricePerLabel(plan.billingInterval) })] })) }, plan.billingInterval) }), !isFreePlan(plan.price) && (_jsxs("p", { className: "mt-1 text-xs text-muted", children: ["+", plan.gstRate, "% GST"] })), cadenceUnavailable && (_jsxs("p", { className: "mt-1 text-xs text-muted", children: ["Only sold ", intervalLabel(plan.billingInterval).adverb, "."] }))] }), plan.trialDays && !isFreePlan(plan.price) ? (_jsxs("p", { className: "text-xs font-medium text-accent", children: [plan.trialDays, "-day free trial"] })) : null, plan.features.length > 0 && (_jsx("ul", { className: "flex flex-col gap-1.5", children: plan.features.map((feature) => (
134
+ return (_jsx(m.div, { variants: { hidden: { opacity: 0, y: 12 }, show: { opacity: 1, y: 0, transition: ENTER } }, children: _jsxs(Card, { className: isCurrentTier ? "h-full border-accent" : "h-full", children: [_jsxs(Card.Header, { children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx(Card.Title, { children: tier.name }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [tier.annualSavingPercent !== null && showAnnual && (_jsxs(Chip, { color: "accent", variant: "soft", children: ["Save ", tier.annualSavingPercent, "%"] })), isCurrentTier && _jsx(Chip, { color: "accent", variant: "soft", children: "Current" })] })] }), tier.description && _jsx(Card.Description, { children: tier.description })] }), _jsxs(Card.Content, { className: "flex flex-col gap-4", children: [_jsxs("div", { children: [_jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(m.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.15 }, children: isFreePlan(plan.price) ? (_jsx("span", { className: "text-2xl font-semibold text-foreground", children: "Free" })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-2xl font-semibold text-foreground", children: formatPrice(plan.price) }), _jsx("span", { className: "text-sm text-muted", children: pricePerLabel(plan.billingInterval) })] })) }, plan.billingInterval) }), !isFreePlan(plan.price) && (_jsxs("p", { className: "mt-1 text-xs text-muted", children: ["+", plan.gstRate, "% GST"] })), cadenceUnavailable && (_jsxs("p", { className: "mt-1 text-xs text-muted", children: ["Only sold ", intervalLabel(plan.billingInterval).adverb, "."] }))] }), plan.trialDays && !isFreePlan(plan.price) ? (_jsxs("p", { className: "text-xs font-medium text-accent-soft-foreground", children: [plan.trialDays, "-day free trial"] })) : null, plan.features.length > 0 && (_jsx("ul", { className: "flex flex-col gap-1.5", children: plan.features.map((feature) => (
135
135
  // `name`, not `key` - the key is an identifier
136
136
  // ("campaign-module"), and printing it here is what this
137
137
  // card used to do. The description rides along as a
@@ -167,7 +167,7 @@ function CheckoutStatusRowContent({ checkout, }) {
167
167
  return (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-separator px-3 py-2 text-sm text-muted", children: [_jsx(Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": "true" }), _jsx("span", { children: "Finish up in the Connect window. This page updates on its own." })] }));
168
168
  }
169
169
  if (checkout.status === "completed") {
170
- return (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "size-4 shrink-0", "aria-hidden": "true" }), "Your plan is updated."] }), _jsx(Button, { variant: "tertiary", onPress: checkout.reset, children: "Dismiss" })] }));
170
+ return (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent-soft-foreground", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "size-4 shrink-0", "aria-hidden": "true" }), "Your plan is updated."] }), _jsx(Button, { variant: "tertiary", onPress: checkout.reset, children: "Dismiss" })] }));
171
171
  }
172
172
  // `blocked` and `abandoned` share a row: both mean nothing changed, and both
173
173
  // are fixed by going to the same URL - so offer it rather than explaining.
@@ -202,7 +202,7 @@ function ConnectUnsubscribeRow({ subscription, appUrl, checkout, }) {
202
202
  if (subscription.cancelAtPeriodEnd) {
203
203
  return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-separator pt-4", children: [_jsxs("p", { className: "text-sm text-muted", children: ["Cancels", endsOn ? ` on ${new Date(endsOn).toLocaleDateString()}` : " at the end of this cycle", ". You keep access until then."] }), _jsxs(Button, { variant: "tertiary", onPress: () => openConnect(`/billing/subscriptions/${encodeURIComponent(subscription.id)}`), children: ["View on Connect", _jsx(ExternalLink, { className: "size-3.5" })] })] }));
204
204
  }
205
- return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-separator pt-4", children: [_jsx("p", { className: "text-sm text-muted", children: "Cancel any time. Access continues to the end of the paid cycle." }), _jsxs(Button, { variant: "tertiary", className: "text-danger", isDisabled: checkout.status === "waiting", onPress: () => checkout.start({
205
+ return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-separator pt-4", children: [_jsx("p", { className: "text-sm text-muted", children: "Cancel any time. Access continues to the end of the paid cycle." }), _jsxs(Button, { variant: "danger-soft", isDisabled: checkout.status === "waiting", onPress: () => checkout.start({
206
206
  url: buildCancelUrl({ appUrl, subscriptionId: subscription.id }),
207
207
  // A paused subscription cancels outright (`status` goes to
208
208
  // `"cancelled"`); an active one is deferred to the cycle end
@@ -71,7 +71,7 @@ function BlockerRow({ blocker }) {
71
71
  const { openConnect } = useConnect();
72
72
  const inProgress = IN_PROGRESS_CODES.has(blocker.code);
73
73
  const isBlocking = blocker.severity === "blocking";
74
- return (_jsxs("div", { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm ${inProgress || !isBlocking ? "bg-surface text-muted" : "bg-danger-soft text-danger"}`, children: [_jsxs("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? (_jsx(Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : (_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && (_jsxs(Button, { variant: "secondary", onPress: () => openConnect(blocker.resolveUrl), children: [actionLabel(blocker), _jsx(ExternalLink, { className: "size-3.5" })] }))] }));
74
+ return (_jsxs("div", { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm ${inProgress || !isBlocking ? "bg-surface text-muted" : "bg-danger-soft text-danger-soft-foreground"}`, children: [_jsxs("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? (_jsx(Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : (_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && (_jsxs(Button, { variant: "secondary", onPress: () => openConnect(blocker.resolveUrl), children: [actionLabel(blocker), _jsx(ExternalLink, { className: "size-3.5" })] }))] }));
75
75
  }
76
76
  function ReadyRow({ className }) {
77
77
  return (_jsx("p", { className: cn("text-sm text-muted", className), children: "This workspace is cleared to use every part of the application." }));
@@ -47,7 +47,7 @@ export function ConnectSubscribeDialog({ plan, action, current, coupon, onClose,
47
47
  // member and the one most in need of it: the two prices differ by an order
48
48
  // of magnitude, and showing only the new one reads as a price rise.
49
49
  const isChange = action === "upgrade" || action === "downgrade" || isIntervalSwitch;
50
- return (_jsx(Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: _jsx(Modal.Container, { size: "sm", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, { onPress: onClose }), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: HEADING[action] }) }), plan && (_jsxs(_Fragment, { children: [_jsxs(Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? (_jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-muted", children: currentPlan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(currentPlan.price), "/", intervalLabel(currentPlan.billingInterval).short] })] }), _jsx(ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium text-foreground", children: plan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(plan.price), "/", intervalLabel(plan.billingInterval).short] })] })] })) : (_jsxs("div", { children: [_jsx("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && _jsx("p", { className: "text-xs text-muted", children: plan.description })] })), isFreePlan(plan.price) ? (_jsxs("div", { className: "flex justify-between border-t border-separator pt-3 text-sm font-semibold text-foreground", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: "Free" })] })) : (_jsxs("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [_jsxs("div", { className: "flex justify-between text-muted", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: formatPrice(plan.price) })] }), _jsxs("div", { className: "flex justify-between text-muted", children: [_jsxs("span", { children: ["GST (", plan.gstRate, "%)"] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate) - plan.price) })] }), _jsxs("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [_jsxs("span", { children: ["Per ", cadenceOf(plan)] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && (_jsxs("p", { className: "text-xs text-muted", children: ["Works out to ", formatPrice(monthlyOf(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] })), plan.trialDays && !isFreePlan(plan.price) ? (_jsxs("p", { className: "text-xs text-accent", children: [plan.trialDays, "-day free trial, if this workspace has not subscribed to this application before."] })) : null, coupon?.trim() ? (_jsxs("p", { className: "text-xs text-muted", children: ["Coupon ", _jsx("span", { className: "font-mono text-foreground", children: coupon.trim() }), " is carried over and validated on Connect."] })) : null, _jsxs("div", { className: "flex gap-2 rounded-lg bg-surface p-3 text-xs text-muted", children: [_jsx(Info, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), _jsxs("div", { className: "flex flex-col gap-1", children: [action === "subscribe" &&
50
+ return (_jsx(Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: _jsx(Modal.Container, { size: "sm", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, { onPress: onClose }), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: HEADING[action] }) }), plan && (_jsxs(_Fragment, { children: [_jsxs(Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? (_jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-muted", children: currentPlan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(currentPlan.price), "/", intervalLabel(currentPlan.billingInterval).short] })] }), _jsx(ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium text-foreground", children: plan.name }), _jsxs("p", { className: "text-xs text-muted", children: [formatPrice(plan.price), "/", intervalLabel(plan.billingInterval).short] })] })] })) : (_jsxs("div", { children: [_jsx("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && _jsx("p", { className: "text-xs text-muted", children: plan.description })] })), isFreePlan(plan.price) ? (_jsxs("div", { className: "flex justify-between border-t border-separator pt-3 text-sm font-semibold text-foreground", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: "Free" })] })) : (_jsxs("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [_jsxs("div", { className: "flex justify-between text-muted", children: [_jsx("span", { children: plan.name }), _jsx("span", { children: formatPrice(plan.price) })] }), _jsxs("div", { className: "flex justify-between text-muted", children: [_jsxs("span", { children: ["GST (", plan.gstRate, "%)"] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate) - plan.price) })] }), _jsxs("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [_jsxs("span", { children: ["Per ", cadenceOf(plan)] }), _jsx("span", { children: formatPrice(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && (_jsxs("p", { className: "text-xs text-muted", children: ["Works out to ", formatPrice(monthlyOf(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] })), plan.trialDays && !isFreePlan(plan.price) ? (_jsxs("p", { className: "text-xs text-accent-soft-foreground", children: [plan.trialDays, "-day free trial, if this workspace has not subscribed to this application before."] })) : null, coupon?.trim() ? (_jsxs("p", { className: "text-xs text-muted", children: ["Coupon ", _jsx("span", { className: "font-mono text-foreground", children: coupon.trim() }), " is carried over and validated on Connect."] })) : null, _jsxs("div", { className: "flex gap-2 rounded-lg bg-surface p-3 text-xs text-muted", children: [_jsx(Info, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), _jsxs("div", { className: "flex flex-col gap-1", children: [action === "subscribe" &&
51
51
  (isFreePlan(plan.price) ? (_jsx("p", { children: "This plan is free. Connect activates it straight away - there is no payment to make, no card or UPI mandate to set up, and no invoice to expect. This page updates by itself once it is done." })) : (_jsx("p", { children: "Payment is completed on Connect, where your card or UPI mandate is set up and the tax invoice is issued. This page updates by itself once it is done." }))), action === "upgrade" && (_jsxs("p", { children: ["Connect prices the difference for the days left in your current cycle and collects just that amount now. From the next cycle you are billed the ", plan.name, " rate."] })), action === "downgrade" && (_jsx("p", { children: "You keep what you have paid for. Connect works out the unused part of this cycle and applies it as credit or a refund, depending on how this application is configured." })), isIntervalSwitch && (
52
52
  // Not a proration. The cycle you are on ends, so this is
53
53
  // the one action where the amount collected can exceed a
@@ -83,15 +83,15 @@ export function ConnectSubscriptionCard({ plansHref, className }) {
83
83
  function SubscriptionChip({ state }) {
84
84
  switch (state) {
85
85
  case "trialing":
86
- return _jsx(Chip, { className: "bg-accent-soft text-accent", children: "Free trial" });
86
+ return _jsx(Chip, { color: "accent", variant: "soft", children: "Free trial" });
87
87
  case "cancelling":
88
88
  return _jsx(Chip, { className: "bg-surface text-muted", children: "Ending soon" });
89
89
  case "pending":
90
90
  return _jsx(Chip, { className: "bg-surface text-muted", children: "Confirming payment" });
91
91
  case "problem":
92
- return _jsx(Chip, { className: "bg-danger-soft text-danger", children: "Needs attention" });
92
+ return _jsx(Chip, { color: "danger", variant: "soft", children: "Needs attention" });
93
93
  default:
94
- return _jsx(Chip, { className: "bg-accent-soft text-accent", children: "Active" });
94
+ return _jsx(Chip, { color: "accent", variant: "soft", children: "Active" });
95
95
  }
96
96
  }
97
97
  /** The one sentence that matters most: what happens next, and when. */
@@ -104,7 +104,7 @@ function NextEvent({ detail, state, }) {
104
104
  return (_jsxs("span", { className: "flex items-center gap-2 text-muted", children: [_jsx(CreditCard, { className: "size-4 shrink-0", "aria-hidden": "true" }), "Waiting for your first payment to confirm."] }));
105
105
  }
106
106
  if (state === "problem") {
107
- return (_jsxs("span", { className: "flex items-center gap-2 text-danger", children: [_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), "This subscription isn't active. Settle it on Connect to restore access."] }));
107
+ return (_jsxs("span", { className: "flex items-center gap-2 text-danger-soft-foreground", children: [_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), "This subscription isn't active. Settle it on Connect to restore access."] }));
108
108
  }
109
109
  if (!detail.nextBillingDate)
110
110
  return null;
@@ -153,7 +153,7 @@ export function ConnectTrialBanner({ plansHref, className }) {
153
153
  const { detail, state } = useSubscriptionState();
154
154
  if (state !== "trialing" || !detail)
155
155
  return null;
156
- return (_jsxs("div", { className: cn("flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", className), children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "size-4 shrink-0", "aria-hidden": "true" }), "You're on a free trial", detail.nextBillingDate ? `. Your first charge is on ${formatDate(detail.nextBillingDate)}.` : "."] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Compare plans" }), _jsxs(Button, { variant: "secondary", onPress: () => openConnect(`/billing/subscriptions/${encodeURIComponent(detail.id)}`), children: ["Manage", _jsx(ExternalLink, { className: "size-3.5" })] })] })] }));
156
+ return (_jsxs("div", { className: cn("flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent-soft-foreground", className), children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "size-4 shrink-0", "aria-hidden": "true" }), "You're on a free trial", detail.nextBillingDate ? `. Your first charge is on ${formatDate(detail.nextBillingDate)}.` : "."] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Compare plans" }), _jsxs(Button, { variant: "secondary", onPress: () => openConnect(`/billing/subscriptions/${encodeURIComponent(detail.id)}`), children: ["Manage", _jsx(ExternalLink, { className: "size-3.5" })] })] })] }));
157
157
  }
158
158
  /**
159
159
  * Renders `children` for a subscribed workspace, and the "pick a plan" card for
@@ -61,7 +61,7 @@ function AddonRow({ addon }) {
61
61
  // unclamped width renders as a bar overflowing its own track.
62
62
  const fraction = addon.includedPerCycle > 0 ? Math.min(1, Math.max(0, addon.balance / addon.includedPerCycle)) : 0;
63
63
  const carried = addon.balance > addon.includedPerCycle ? addon.balance - addon.includedPerCycle : 0;
64
- return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2 text-sm", children: [_jsx("span", { className: "font-medium text-foreground", children: addon.name }), _jsxs("span", { className: overdrawn ? "text-danger" : "text-muted", children: [overdrawn
64
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2 text-sm", children: [_jsx("span", { className: "font-medium text-foreground", children: addon.name }), _jsxs("span", { className: overdrawn ? "text-danger-soft-foreground" : "text-muted", children: [overdrawn
65
65
  ? `${formatUnits(Math.abs(addon.balance), addon.unit)} over`
66
- : `${formatUnits(addon.balance, addon.unit)} left`, _jsxs("span", { className: "text-muted", children: [" of ", formatUnits(addon.includedPerCycle, addon.unit)] })] })] }), _jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-surface", role: "meter", "aria-label": `${addon.name} remaining`, "aria-valuenow": addon.balance, "aria-valuemin": 0, "aria-valuemax": addon.includedPerCycle, children: _jsx(m.div, { className: `h-full rounded-full ${overdrawn ? "bg-danger" : "bg-accent"}`, initial: { width: 0 }, animate: { width: `${overdrawn ? 100 : fraction * 100}%` }, transition: METER_SPRING }) }), carried > 0 && (_jsxs("p", { className: "text-xs text-muted", children: ["Includes ", formatUnits(carried, addon.unit), " carried over from last cycle."] })), overdrawn && addon.overagePricePerUnit !== null && (_jsxs("p", { className: "flex items-center gap-1.5 text-xs text-danger", children: [_jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Billed at ", formatPrice(addon.overagePricePerUnit), " per ", addon.unit, " beyond your allowance."] })), !overdrawn && addon.overagePolicy === "reject" && addon.balance === 0 && (_jsxs("p", { className: "flex items-center gap-1.5 text-xs text-danger", children: [_jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Nothing left this cycle - further use is blocked until it resets."] }))] }));
66
+ : `${formatUnits(addon.balance, addon.unit)} left`, _jsxs("span", { className: "text-muted", children: [" of ", formatUnits(addon.includedPerCycle, addon.unit)] })] })] }), _jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-surface", role: "meter", "aria-label": `${addon.name} remaining`, "aria-valuenow": addon.balance, "aria-valuemin": 0, "aria-valuemax": addon.includedPerCycle, children: _jsx(m.div, { className: `h-full rounded-full ${overdrawn ? "bg-danger" : "bg-accent"}`, initial: { width: 0 }, animate: { width: `${overdrawn ? 100 : fraction * 100}%` }, transition: METER_SPRING }) }), carried > 0 && (_jsxs("p", { className: "text-xs text-muted", children: ["Includes ", formatUnits(carried, addon.unit), " carried over from last cycle."] })), overdrawn && addon.overagePricePerUnit !== null && (_jsxs("p", { className: "flex items-center gap-1.5 text-xs text-danger-soft-foreground", children: [_jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Billed at ", formatPrice(addon.overagePricePerUnit), " per ", addon.unit, " beyond your allowance."] })), !overdrawn && addon.overagePolicy === "reject" && addon.balance === 0 && (_jsxs("p", { className: "flex items-center gap-1.5 text-xs text-danger-soft-foreground", children: [_jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Nothing left this cycle - further use is blocked until it resets."] }))] }));
67
67
  }
@@ -128,7 +128,7 @@ function ProfileSurface({ onSignOut, theme, onThemeChange, }) {
128
128
  const [next] = [...keys];
129
129
  if (typeof next === "string")
130
130
  onThemeChange(next);
131
- }, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => (_jsxs(ToggleButton, { id: value, children: [index > 0 && _jsx(ToggleButtonGroup.Separator, {}), _jsx(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), _jsx(ConnectAccountSessions, {})] }), _jsx(Modal.Footer, { children: _jsxs(Button, { slot: "close", fullWidth: true, variant: "tertiary", className: "text-danger", onPress: () => {
131
+ }, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => (_jsxs(ToggleButton, { id: value, children: [index > 0 && _jsx(ToggleButtonGroup.Separator, {}), _jsx(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), _jsx(ConnectAccountSessions, {})] }), _jsx(Modal.Footer, { children: _jsxs(Button, { slot: "close", fullWidth: true, variant: "danger-soft", onPress: () => {
132
132
  void signOut().then(onSignOut);
133
133
  }, children: [_jsx(LogOut, { className: "size-4 shrink-0" }), "Sign out"] }) })] }) }) }));
134
134
  }
@@ -127,7 +127,7 @@ export function ConnectWorkspaceSwitcher({ onSwitched, row = true, className, al
127
127
  // A 32px tile rather than a 20px glyph, so the initials land on the
128
128
  // same optical centre the avatar below them uses - see
129
129
  // `SIDEBAR_ROW_TILE`. Collapsed, this tile *is* the switcher.
130
- _jsxs(Button, { variant: "tertiary", "aria-label": `Workspace: ${label}. Switch workspace`, className: sidebarRowClass({ tile: true, className }), children: [_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) }), _jsx(SidebarLabel, { className: "text-sm font-medium text-foreground", children: label }), _jsx(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: _jsx(ChevronsUpDown, { className: "size-4" }) })] })) : (_jsxs(Button, { variant: "secondary", size: "sm", className: "w-full justify-between", children: [_jsx("span", { className: "truncate", children: label }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" })] })), _jsx(Dropdown.Popover, { children: _jsxs(ListBox, { children: [workspaces.map((workspace) => (_jsxs(ListBox.Item, { id: workspace.workspaceId, textValue: workspace.name, isDisabled: !selectable(workspace) || switching !== null, onAction: () => void handleSwitch(workspace), children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm", children: workspace.name }), selectable(workspace) ? null : (_jsx("p", { className: "truncate text-xs text-muted", children: reasonUnavailable(workspace) }))] }), _jsx(ListBox.ItemIndicator, {})] }, workspace.workspaceId))), alwaysShow ? (_jsxs(_Fragment, { children: [workspaces.length > 0 && _jsx(Separator, {}), _jsxs(ListBox.Item, { id: "__create_workspace", textValue: "Create workspace", onAction: () => setIsCreateOpen(true), children: [_jsx(Plus, { className: "size-4 shrink-0 text-muted", "aria-hidden": true }), _jsx("p", { className: "truncate text-sm", children: "Create workspace" })] })] })) : null] }) })] }), error && !collapsed ? _jsx("p", { className: "mt-1 px-2 text-xs text-danger", children: error }) : null, alwaysShow ? (_jsx(ConnectCreateWorkspaceModal, { isOpen: isCreateOpen, onOpenChange: setIsCreateOpen, onCreated: (workspace) => void handleCreated(workspace) })) : null] }));
130
+ _jsxs(Button, { variant: "tertiary", "aria-label": `Workspace: ${label}. Switch workspace`, className: sidebarRowClass({ tile: true, className }), children: [_jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-xs font-semibold text-accent-soft-foreground", children: workspaceInitials(label) }), _jsx(SidebarLabel, { className: "text-sm font-medium text-foreground", children: label }), _jsx(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: _jsx(ChevronsUpDown, { className: "size-4" }) })] })) : (_jsxs(Button, { variant: "secondary", size: "sm", className: "w-full justify-between", children: [_jsx("span", { className: "truncate", children: label }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" })] })), _jsx(Dropdown.Popover, { children: _jsxs(ListBox, { children: [workspaces.map((workspace) => (_jsxs(ListBox.Item, { id: workspace.workspaceId, textValue: workspace.name, isDisabled: !selectable(workspace) || switching !== null, onAction: () => void handleSwitch(workspace), children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm", children: workspace.name }), selectable(workspace) ? null : (_jsx("p", { className: "truncate text-xs text-muted", children: reasonUnavailable(workspace) }))] }), _jsx(ListBox.ItemIndicator, {})] }, workspace.workspaceId))), alwaysShow ? (_jsxs(_Fragment, { children: [workspaces.length > 0 && _jsx(Separator, {}), _jsxs(ListBox.Item, { id: "__create_workspace", textValue: "Create workspace", onAction: () => setIsCreateOpen(true), children: [_jsx(Plus, { className: "size-4 shrink-0 text-muted", "aria-hidden": true }), _jsx("p", { className: "truncate text-sm", children: "Create workspace" })] })] })) : null] }) })] }), error && !collapsed ? _jsx("p", { className: "mt-1 px-2 text-xs text-danger-soft-foreground", children: error }) : null, alwaysShow ? (_jsx(ConnectCreateWorkspaceModal, { isOpen: isCreateOpen, onOpenChange: setIsCreateOpen, onCreated: (workspace) => void handleCreated(workspace) })) : null] }));
131
131
  }
132
132
  /** Belongs to the user, and usable here. The same two facts `switch` enforces. */
133
133
  function selectable(workspace) {
@@ -179,4 +179,4 @@ export class ConnectClient {
179
179
  }
180
180
  }
181
181
  /** Kept in step with `package.json` by `sdk.test.ts`. */
182
- export const SDK_VERSION = "0.45.0";
182
+ export const SDK_VERSION = "0.46.0";
@@ -32,4 +32,15 @@ export class DriveResource {
32
32
  });
33
33
  return result.url;
34
34
  }
35
+ /**
36
+ * How much of the workspace's Drive is used, against its limit. Needs
37
+ * `drive:read` and an active subscription to your application.
38
+ */
39
+ getUsage(input) {
40
+ return request(this.transport, {
41
+ method: "GET",
42
+ path: "/drive/usage",
43
+ query: { workspaceId: input.workspaceId },
44
+ });
45
+ }
35
46
  }
@@ -1,7 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
2
3
  import { Suspense } from "react";
3
4
  import { ConnectPageHeader } from "./page-header.js";
4
5
  import { cn } from "../cn.js";
6
+ /**
7
+ * React's `<ViewTransition>`, when the host's React has one.
8
+ *
9
+ * It ships in the React canary that Next's App Router bundles and in no stable
10
+ * React yet - 19.2 exports nothing by that name. So it is looked up rather
11
+ * than imported: a static `import { ViewTransition }` resolves to `undefined`
12
+ * on a Vite or Storybook host and renders as an invalid element type, which
13
+ * would take down every page drawn in this shell to animate it. Absent, the
14
+ * page renders exactly as it did before, with no motion.
15
+ *
16
+ * Read by a computed key so a bundler doing strict export checks does not
17
+ * warn that `react` has no such export - on those hosts it genuinely has not.
18
+ */
19
+ const RouteTransition = React["ViewTransition"];
5
20
  /**
6
21
  * The frame a page opens with: its vertical rhythm, its header, and the
7
22
  * suspended regions beneath.
@@ -20,9 +35,23 @@ import { cn } from "../cn.js";
20
35
  * never be made to wait, not that every title can be static.
21
36
  *
22
37
  * `space-y-5` is the page gap, owned here so it has exactly one definition.
38
+ *
39
+ * **It is also where the route transition lives**, because it is the one
40
+ * thing every page opens with. A transition has to wrap the *page*: a layout
41
+ * persists across navigation, so its enter and exit never fire, and a
42
+ * `template.tsx` remounts its subtree on every navigation, discarding the
43
+ * form drafts and scroll positions Next's `<Activity>` keeps. Where a layout
44
+ * draws the shell instead - a record with tabs - entering and leaving the
45
+ * record animates and switching tabs does not, which is right: a tab is the
46
+ * same place. `default="none"` keeps it still during anything else - a
47
+ * filter written to the URL, a `router.refresh()` after a save. The
48
+ * animations are `connect-page-enter`/`connect-page-exit` in `theme.css`.
23
49
  */
24
- export function ConnectPageShell({ title, description, action, clamp, above, children, className, }) {
25
- return (_jsxs("div", { className: cn("space-y-5", className), children: [above, title !== undefined && (_jsx(ConnectPageHeader, { title: title, description: description, action: action, clamp: clamp })), children] }));
50
+ export function ConnectPageShell({ title, description, action, clamp, above, children, className, transition = true, }) {
51
+ const page = (_jsxs("div", { className: cn("space-y-5", className), children: [above, title !== undefined && (_jsx(ConnectPageHeader, { title: title, description: description, action: action, clamp: clamp })), children] }));
52
+ if (!transition || !RouteTransition)
53
+ return page;
54
+ return (_jsx(RouteTransition, { enter: "connect-page-enter", exit: "connect-page-exit", default: "none", children: page }));
26
55
  }
27
56
  /**
28
57
  * The page header, for a detail route that can only name itself once its data
package/dist/theme.css CHANGED
@@ -97,8 +97,13 @@
97
97
  * around a green primary button, and measured 3.33:1 against the page;
98
98
  * indigo is a different hue from the thing it outlines and clears 5:1.
99
99
  *
100
- * Green acts, indigo navigates - which is why indigo is never a fill here.
101
- * A second filled hue beside the green competes for the main action.
100
+ * - `secondary` Buttons, as a *soft* fill - see `.button--secondary`
101
+ * below. A view's main action is the solid green; the actions beside it
102
+ * are an indigo tint, so they read as branded without competing.
103
+ *
104
+ * Green acts, indigo navigates and supports - which is why indigo is never
105
+ * a *solid* fill. A second solid hue beside the green competes for the
106
+ * main action.
102
107
  *
103
108
  * The hue is chosen by what is already taken: danger sits at ~13 and
104
109
  * warning at ~60-76, so green's textbook complement (magenta) would read as
@@ -110,6 +115,11 @@
110
115
  --secondary: oklch(50% 0.16 268);
111
116
  --link: var(--secondary);
112
117
  --focus: var(--secondary);
118
+ /* The secondary button's tint. Its text is a step darker than
119
+ `--secondary` so it clears 4.5:1 on the tint, not just on the page. */
120
+ --secondary-soft: oklch(95% 0.03 268);
121
+ --secondary-soft-hover: oklch(91.5% 0.05 268);
122
+ --secondary-soft-foreground: oklch(44% 0.16 268);
113
123
  }
114
124
 
115
125
  .dark,
@@ -137,6 +147,27 @@
137
147
  --secondary: oklch(68% 0.13 268);
138
148
  --link: var(--secondary);
139
149
  --focus: var(--secondary);
150
+ --secondary-soft: oklch(29% 0.06 268);
151
+ --secondary-soft-hover: oklch(34% 0.08 268);
152
+ --secondary-soft-foreground: oklch(84% 0.08 268);
153
+ }
154
+
155
+ /*
156
+ * The secondary button, in the secondary colour.
157
+ *
158
+ * HeroUI paints `variant="secondary"` with the neutral `--default`, which left
159
+ * the indigo on links and focus rings only - a page's actions were one green
160
+ * button and a row of grey ones, and the brand's second colour appeared on no
161
+ * control at all. This re-points the same four variables HeroUI's own rule
162
+ * sets, so hover, press, disabled and the focus ring all behave exactly as
163
+ * before; only the paint changes. `tertiary` stays neutral, which is what keeps
164
+ * an in-row control quieter than a page action.
165
+ */
166
+ .button--secondary {
167
+ --button-bg: var(--secondary-soft);
168
+ --button-bg-hover: var(--secondary-soft-hover);
169
+ --button-bg-pressed: var(--secondary-soft-hover);
170
+ --button-fg: var(--secondary-soft-foreground);
140
171
  }
141
172
 
142
173
  /*
@@ -274,6 +305,51 @@ body {
274
305
  }
275
306
  }
276
307
 
308
+ /* ---------------------------------------------------------------------------
309
+ * The route transition `ConnectPageShell` plays.
310
+ *
311
+ * The old page leaves fast and accelerating, so it never competes with the
312
+ * arrival; the new one fades in once it has gone and settles as it rises. An
313
+ * exit and an entrance on the same curve is the mistake these two avoid.
314
+ * Inert on any host whose React has no `<ViewTransition>`, which is every
315
+ * stable React today - see `page-shell.tsx`.
316
+ * ------------------------------------------------------------------------- */
317
+
318
+ /* Clicks during a transition reach the live page rather than the overlay. */
319
+ ::view-transition {
320
+ pointer-events: none;
321
+ }
322
+
323
+ @keyframes connect-vt-fade-in {
324
+ from { opacity: 0; }
325
+ }
326
+
327
+ @keyframes connect-vt-fade-out {
328
+ to { opacity: 0; }
329
+ }
330
+
331
+ @keyframes connect-vt-rise {
332
+ from { transform: translateY(8px); }
333
+ }
334
+
335
+ ::view-transition-old(.connect-page-exit) {
336
+ animation: 140ms cubic-bezier(0.64, 0, 0.78, 0) both connect-vt-fade-out;
337
+ }
338
+
339
+ ::view-transition-new(.connect-page-enter) {
340
+ animation:
341
+ 200ms cubic-bezier(0.22, 1, 0.36, 1) 60ms both connect-vt-fade-in,
342
+ 260ms cubic-bezier(0.22, 1, 0.36, 1) both connect-vt-rise;
343
+ }
344
+
345
+ @media (prefers-reduced-motion: reduce) {
346
+ ::view-transition-old(.connect-page-exit),
347
+ ::view-transition-new(.connect-page-enter) {
348
+ animation-duration: 0s;
349
+ animation-delay: 0s;
350
+ }
351
+ }
352
+
277
353
  /* ---------------------------------------------------------------------------
278
354
  * A hand-rolled button still looks pressable.
279
355
  *
@@ -141,4 +141,4 @@ export declare class ConnectClient {
141
141
  constructor(options: ConnectClientOptions);
142
142
  }
143
143
  /** Kept in step with `package.json` by `sdk.test.ts`. */
144
- export declare const SDK_VERSION = "0.45.0";
144
+ export declare const SDK_VERSION = "0.46.0";
@@ -1,5 +1,5 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { ThumbnailSize, UploadedFile } from "../../types.js";
2
+ import type { DriveUsage, ThumbnailSize, UploadedFile } from "../../types.js";
3
3
  export interface UploadInput {
4
4
  workspaceId: string;
5
5
  /** Any `Blob`/`File`. In Node, `new Blob([buffer])` or `fs.openAsBlob(path)`. */
@@ -23,4 +23,11 @@ export declare class DriveResource {
23
23
  workspaceId: string;
24
24
  size?: ThumbnailSize;
25
25
  }): Promise<string>;
26
+ /**
27
+ * How much of the workspace's Drive is used, against its limit. Needs
28
+ * `drive:read` and an active subscription to your application.
29
+ */
30
+ getUsage(input: {
31
+ workspaceId: string;
32
+ }): Promise<DriveUsage>;
26
33
  }
@@ -588,6 +588,15 @@ export interface UploadedFile {
588
588
  size: number;
589
589
  url: string;
590
590
  }
591
+ /**
592
+ * How full a workspace's Drive is. The limit is the one an upload is refused
593
+ * against, so a meter built on this agrees with `quota_exceeded`.
594
+ */
595
+ export interface DriveUsage {
596
+ usedBytes: number;
597
+ /** `null` = unlimited. */
598
+ limitBytes: number | null;
599
+ }
591
600
  export type ThumbnailSize = "small" | "medium" | "large";
592
601
  export type LogLevel = "debug" | "info" | "warn" | "error";
593
602
  export interface WriteLogInput {
@@ -1,3 +1,4 @@
1
+ import * as React from "react";
1
2
  import { type ReactNode } from "react";
2
3
  import { ConnectPageHeader } from "./page-header.js";
3
4
  export interface ConnectPageShellProps {
@@ -13,6 +14,12 @@ export interface ConnectPageShellProps {
13
14
  * See `cn`.
14
15
  */
15
16
  className?: string;
17
+ /**
18
+ * The route transition - the outgoing page fades out, the arriving one fades
19
+ * in and rises. On by default; pass `false` where a host already animates
20
+ * its routes, or the two play over each other. See {@link RouteTransition}.
21
+ */
22
+ transition?: boolean;
16
23
  children?: ReactNode;
17
24
  }
18
25
  /**
@@ -33,13 +40,24 @@ export interface ConnectPageShellProps {
33
40
  * never be made to wait, not that every title can be static.
34
41
  *
35
42
  * `space-y-5` is the page gap, owned here so it has exactly one definition.
43
+ *
44
+ * **It is also where the route transition lives**, because it is the one
45
+ * thing every page opens with. A transition has to wrap the *page*: a layout
46
+ * persists across navigation, so its enter and exit never fire, and a
47
+ * `template.tsx` remounts its subtree on every navigation, discarding the
48
+ * form drafts and scroll positions Next's `<Activity>` keeps. Where a layout
49
+ * draws the shell instead - a record with tabs - entering and leaving the
50
+ * record animates and switching tabs does not, which is right: a tab is the
51
+ * same place. `default="none"` keeps it still during anything else - a
52
+ * filter written to the URL, a `router.refresh()` after a save. The
53
+ * animations are `connect-page-enter`/`connect-page-exit` in `theme.css`.
36
54
  */
37
- export declare function ConnectPageShell({ title, description, action, clamp, above, children, className, }: Readonly<ConnectPageShellProps>): import("react").JSX.Element;
55
+ export declare function ConnectPageShell({ title, description, action, clamp, above, children, className, transition, }: Readonly<ConnectPageShellProps>): React.JSX.Element;
38
56
  export declare namespace ConnectPageShell {
39
57
  var Header: typeof ConnectPageHeader;
40
58
  var Async: ({ fallback, children, }: Readonly<{
41
59
  /** Omit where the region has nothing to draw while it waits. */
42
60
  fallback?: ReactNode;
43
61
  children: ReactNode;
44
- }>) => import("react").JSX.Element;
62
+ }>) => React.JSX.Element;
45
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",