@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.
- package/CHANGELOG.md +55 -0
- package/dist/cjs/client/components/access-gate.js +1 -1
- package/dist/cjs/client/components/account-sessions.js +1 -1
- package/dist/cjs/client/components/ai-format.js +3 -3
- package/dist/cjs/client/components/ai-widgets.js +5 -5
- package/dist/cjs/client/components/apps-grid.js +2 -2
- package/dist/cjs/client/components/ask-ai-history.js +2 -2
- package/dist/cjs/client/components/ask-ai.js +1 -1
- package/dist/cjs/client/components/availability-form.js +1 -1
- package/dist/cjs/client/components/booking-calendar.js +4 -4
- package/dist/cjs/client/components/booking-dialogs.js +2 -2
- package/dist/cjs/client/components/call-button.js +6 -6
- package/dist/cjs/client/components/create-workspace-form.js +1 -1
- package/dist/cjs/client/components/event-type-form.js +1 -1
- package/dist/cjs/client/components/filter-tabs.js +1 -1
- package/dist/cjs/client/components/grievance-notice.js +1 -1
- package/dist/cjs/client/components/integrations-modal.js +2 -2
- package/dist/cjs/client/components/plans.js +5 -5
- package/dist/cjs/client/components/readiness.js +1 -1
- package/dist/cjs/client/components/subscribe-dialog.js +1 -1
- package/dist/cjs/client/components/subscription.js +5 -5
- package/dist/cjs/client/components/usage-meter.js +2 -2
- package/dist/cjs/client/components/user-menu.js +1 -1
- package/dist/cjs/client/components/workspace-switcher.js +1 -1
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/resources/drive.js +11 -0
- package/dist/cjs/ui/page-shell.js +64 -2
- package/dist/esm/client/components/access-gate.js +1 -1
- package/dist/esm/client/components/account-sessions.js +1 -1
- package/dist/esm/client/components/ai-format.js +3 -3
- package/dist/esm/client/components/ai-widgets.js +5 -5
- package/dist/esm/client/components/apps-grid.js +2 -2
- package/dist/esm/client/components/ask-ai-history.js +2 -2
- package/dist/esm/client/components/ask-ai.js +1 -1
- package/dist/esm/client/components/availability-form.js +1 -1
- package/dist/esm/client/components/booking-calendar.js +4 -4
- package/dist/esm/client/components/booking-dialogs.js +2 -2
- package/dist/esm/client/components/call-button.js +6 -6
- package/dist/esm/client/components/create-workspace-form.js +1 -1
- package/dist/esm/client/components/event-type-form.js +1 -1
- package/dist/esm/client/components/filter-tabs.js +1 -1
- package/dist/esm/client/components/grievance-notice.js +1 -1
- package/dist/esm/client/components/integrations-modal.js +2 -2
- package/dist/esm/client/components/plans.js +5 -5
- package/dist/esm/client/components/readiness.js +1 -1
- package/dist/esm/client/components/subscribe-dialog.js +1 -1
- package/dist/esm/client/components/subscription.js +5 -5
- package/dist/esm/client/components/usage-meter.js +2 -2
- package/dist/esm/client/components/user-menu.js +1 -1
- package/dist/esm/client/components/workspace-switcher.js +1 -1
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/resources/drive.js +11 -0
- package/dist/esm/ui/page-shell.js +31 -2
- package/dist/theme.css +78 -2
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/resources/drive.d.ts +8 -1
- package/dist/types/types.d.ts +9 -0
- package/dist/types/ui/page-shell.d.ts +20 -2
- 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-
|
|
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-
|
|
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: "
|
|
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, {
|
|
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, {
|
|
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: "
|
|
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, {
|
|
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, {
|
|
92
|
+
return _jsx(Chip, { color: "danger", variant: "soft", children: "Needs attention" });
|
|
93
93
|
default:
|
|
94
|
-
return _jsx(Chip, {
|
|
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: "
|
|
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) {
|
|
@@ -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
|
-
|
|
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
|
-
*
|
|
101
|
-
* A
|
|
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
|
*
|
|
@@ -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
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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>):
|
|
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
|
-
}>) =>
|
|
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.
|
|
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",
|