@cosmoops/connect-sdk 0.6.0 → 0.7.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/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +3 -3
- package/dist/cjs/client/components/account-sessions.js +3 -3
- package/dist/cjs/client/components/app-shell.js +2 -2
- package/dist/cjs/client/components/apps-grid.js +3 -3
- package/dist/cjs/client/components/feature-gate.js +2 -2
- package/dist/cjs/client/components/integrations-modal.js +1 -1
- package/dist/cjs/client/components/logo.js +3 -3
- package/dist/cjs/client/components/plan-pricing.js +1 -1
- package/dist/cjs/client/components/plans.js +6 -6
- package/dist/cjs/client/components/readiness.js +3 -3
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/usage-meter.js +1 -1
- package/dist/cjs/client/components/user-menu.js +4 -4
- package/dist/cjs/client/index.js +1 -1
- package/dist/cjs/client/mark.js +1 -1
- package/dist/cjs/client/provider.js +7 -7
- package/dist/cjs/client/use-checkout.js +3 -3
- package/dist/cjs/errors.js +4 -4
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +15 -15
- package/dist/cjs/server/handler.js +13 -13
- package/dist/cjs/server/http.js +1 -1
- package/dist/cjs/server/resources/access.js +3 -3
- package/dist/cjs/server/resources/ai.js +1 -1
- package/dist/cjs/server/resources/analytics.js +2 -2
- package/dist/cjs/server/resources/apps.js +4 -4
- package/dist/cjs/server/resources/billing.js +5 -5
- package/dist/cjs/server/resources/drive.js +4 -4
- package/dist/cjs/server/resources/entitlements.js +7 -7
- package/dist/cjs/server/resources/identity.js +4 -4
- package/dist/cjs/server/resources/integrations.js +13 -13
- package/dist/cjs/server/resources/logs.js +3 -3
- package/dist/cjs/server/resources/sessions.js +6 -6
- package/dist/cjs/server/webhooks.js +4 -4
- package/dist/cjs/types.js +1 -1
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +3 -3
- package/dist/esm/client/components/account-sessions.js +3 -3
- package/dist/esm/client/components/app-shell.js +2 -2
- package/dist/esm/client/components/apps-grid.js +3 -3
- package/dist/esm/client/components/feature-gate.js +2 -2
- package/dist/esm/client/components/integrations-modal.js +1 -1
- package/dist/esm/client/components/logo.js +3 -3
- package/dist/esm/client/components/plan-pricing.js +1 -1
- package/dist/esm/client/components/plans.js +6 -6
- package/dist/esm/client/components/readiness.js +3 -3
- package/dist/esm/client/components/subscribe-dialog.js +2 -2
- package/dist/esm/client/components/subscription.js +1 -1
- package/dist/esm/client/components/usage-meter.js +1 -1
- package/dist/esm/client/components/user-menu.js +4 -4
- package/dist/esm/client/index.js +1 -1
- package/dist/esm/client/mark.js +1 -1
- package/dist/esm/client/provider.js +7 -7
- package/dist/esm/client/use-checkout.js +3 -3
- package/dist/esm/errors.js +4 -4
- package/dist/esm/helpers.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +15 -15
- package/dist/esm/server/handler.js +13 -13
- package/dist/esm/server/http.js +1 -1
- package/dist/esm/server/resources/access.js +3 -3
- package/dist/esm/server/resources/ai.js +1 -1
- package/dist/esm/server/resources/analytics.js +2 -2
- package/dist/esm/server/resources/apps.js +4 -4
- package/dist/esm/server/resources/billing.js +5 -5
- package/dist/esm/server/resources/drive.js +4 -4
- package/dist/esm/server/resources/entitlements.js +7 -7
- package/dist/esm/server/resources/identity.js +4 -4
- package/dist/esm/server/resources/integrations.js +13 -13
- package/dist/esm/server/resources/logs.js +3 -3
- package/dist/esm/server/resources/sessions.js +6 -6
- package/dist/esm/server/webhooks.js +4 -4
- package/dist/esm/types.js +1 -1
- package/dist/types/client/components/access-gate.d.ts +1 -1
- package/dist/types/client/components/account-sessions.d.ts +1 -1
- package/dist/types/client/components/app-shell.d.ts +4 -4
- package/dist/types/client/components/apps-grid.d.ts +1 -1
- package/dist/types/client/components/feature-gate.d.ts +1 -1
- package/dist/types/client/components/integrations-modal.d.ts +1 -1
- package/dist/types/client/components/logo.d.ts +5 -5
- package/dist/types/client/components/plan-pricing.d.ts +2 -2
- package/dist/types/client/components/readiness.d.ts +2 -2
- package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
- package/dist/types/client/components/usage-meter.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +2 -2
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/mark.d.ts +1 -1
- package/dist/types/client/provider.d.ts +9 -9
- package/dist/types/client/use-checkout.d.ts +3 -3
- package/dist/types/errors.d.ts +4 -4
- package/dist/types/helpers.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +13 -13
- package/dist/types/server/handler.d.ts +2 -2
- package/dist/types/server/resources/access.d.ts +3 -3
- package/dist/types/server/resources/ai.d.ts +2 -2
- package/dist/types/server/resources/analytics.d.ts +2 -2
- package/dist/types/server/resources/apps.d.ts +4 -4
- package/dist/types/server/resources/billing.d.ts +4 -4
- package/dist/types/server/resources/drive.d.ts +4 -4
- package/dist/types/server/resources/entitlements.d.ts +4 -4
- package/dist/types/server/resources/identity.d.ts +2 -2
- package/dist/types/server/resources/integrations.d.ts +9 -9
- package/dist/types/server/resources/logs.d.ts +3 -3
- package/dist/types/server/resources/sessions.d.ts +5 -5
- package/dist/types/server/webhooks.d.ts +4 -4
- package/dist/types/types.d.ts +61 -61
- package/package.json +1 -1
package/dist/cjs/cli/doctor.js
CHANGED
|
@@ -162,8 +162,8 @@ function checkEnv(root) {
|
|
|
162
162
|
status: "fail",
|
|
163
163
|
title: `${name} set`,
|
|
164
164
|
detail: declaredEmpty
|
|
165
|
-
? `Declared but empty in .env.local. The value is in your
|
|
166
|
-
: `Not found in .env.local or the environment. Both values are in your
|
|
165
|
+
? `Declared but empty in .env.local. The value is in your application's Integration tab in Connect.`
|
|
166
|
+
: `Not found in .env.local or the environment. Both values are in your application's Integration tab in Connect.`,
|
|
167
167
|
};
|
|
168
168
|
});
|
|
169
169
|
}
|
|
@@ -176,7 +176,7 @@ function checkRedirectUri() {
|
|
|
176
176
|
return {
|
|
177
177
|
status: "warn",
|
|
178
178
|
title: "Redirect URI registered in Connect",
|
|
179
|
-
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your
|
|
179
|
+
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's Integration tab - including the deployed origin, not just http://localhost:3000.",
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
const MARKER = { pass: " ok ", warn: " note ", fail: " FAIL " };
|
package/dist/cjs/cli/init.js
CHANGED
|
@@ -44,7 +44,7 @@ const PLANS_PAGE = `"use client";
|
|
|
44
44
|
// with the other exports) whenever you want your own copy or layout.
|
|
45
45
|
export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
|
|
46
46
|
`;
|
|
47
|
-
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your
|
|
47
|
+
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's Integration tab.
|
|
48
48
|
CONNECT_API_KEY=
|
|
49
49
|
CONNECT_CLIENT_ID=
|
|
50
50
|
`;
|
|
@@ -111,7 +111,7 @@ function runInit(argv = []) {
|
|
|
111
111
|
console.log(` ${marker} ${(0, node_path_1.relative)(root, target)} - ${scaffold.description}`);
|
|
112
112
|
}
|
|
113
113
|
const env = ensureEnv(root);
|
|
114
|
-
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your
|
|
114
|
+
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's Integration tab`);
|
|
115
115
|
// The two steps a scaffolder must not do for you: editing an existing layout
|
|
116
116
|
// means parsing and rewriting JSX someone else wrote, and the redirect URI
|
|
117
117
|
// lives in Connect, not in this repo.
|
|
@@ -137,7 +137,7 @@ Three things left, all one-time:
|
|
|
137
137
|
Tailwind roots its content scan inside node_modules and skips your app.
|
|
138
138
|
|
|
139
139
|
3. Register {your origin}/api/connect/callback as a redirect URI in your
|
|
140
|
-
|
|
140
|
+
application's Integration tab in Connect.
|
|
141
141
|
|
|
142
142
|
No next.config.ts changes needed - not even transpilePackages: dist/ is
|
|
143
143
|
already ES2022 with "use client" hoisted, so Turbopack and webpack consume it
|
|
@@ -47,7 +47,7 @@ function useConnectAccessGate() {
|
|
|
47
47
|
}
|
|
48
48
|
const readiness = data.readiness;
|
|
49
49
|
const blocking = (0, helpers_js_1.blockingBlockers)(readiness);
|
|
50
|
-
const suspended = blocking.find((b) => b.code === "
|
|
50
|
+
const suspended = blocking.find((b) => b.code === "workspace_suspended");
|
|
51
51
|
if (suspended)
|
|
52
52
|
return { state: "suspended", blocker: suspended, paymentRetry: null };
|
|
53
53
|
const noSubscription = blocking.find((b) => b.code === "subscription_missing");
|
|
@@ -110,13 +110,13 @@ function ConnectAccessGate({ children, fallback, loading, className }) {
|
|
|
110
110
|
}
|
|
111
111
|
function AccessGateFallback({ state, blocker, className, }) {
|
|
112
112
|
if (state === "no_subscription") {
|
|
113
|
-
return ((0, jsx_runtime_1.jsx)(GateCard, { icon: lucide_react_1.CreditCard, iconClassName: "text-accent", title: "Choose a plan to continue", message: blocker?.message ?? "This workspace isn't subscribed to this
|
|
113
|
+
return ((0, jsx_runtime_1.jsx)(GateCard, { icon: lucide_react_1.CreditCard, iconClassName: "text-accent", title: "Choose a plan to continue", message: blocker?.message ?? "This workspace isn't subscribed to this application yet.", actionLabel: "See plans", resolveUrl: blocker?.resolveUrl ?? null, className: className }));
|
|
114
114
|
}
|
|
115
115
|
if (state === "suspended") {
|
|
116
116
|
return ((0, jsx_runtime_1.jsx)(GateCard, { icon: lucide_react_1.Ban, iconClassName: "text-danger", title: "Workspace suspended", message: blocker?.message ?? "This workspace has been suspended.", resolveUrl: null, className: className }));
|
|
117
117
|
}
|
|
118
118
|
if (state === "onboarding_pending") {
|
|
119
|
-
return ((0, jsx_runtime_1.jsx)(GateCard, { icon: lucide_react_1.Clock, iconClassName: "text-muted", title: "Waiting on approval", message: blocker?.message ?? "This workspace is waiting on approval before it can use this
|
|
119
|
+
return ((0, jsx_runtime_1.jsx)(GateCard, { icon: lucide_react_1.Clock, iconClassName: "text-muted", title: "Waiting on approval", message: blocker?.message ?? "This workspace is waiting on approval before it can use this application.", resolveUrl: null, className: className }));
|
|
120
120
|
}
|
|
121
121
|
// "blocked" - any other blocking code (verification, audience, seats, ...).
|
|
122
122
|
// Full fidelity for a code this component doesn't special-case: the same
|
|
@@ -44,7 +44,7 @@ function ordered(sessions) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* One row's revoke action, confirmed before it fires - revoking a session
|
|
47
|
-
* ends every partner-
|
|
47
|
+
* ends every partner-application sign-in from that same browser, not just this
|
|
48
48
|
* one. Closes on confirm, not on response - the same convention every
|
|
49
49
|
* destructive confirmation in this codebase follows - so the dialog itself
|
|
50
50
|
* needs no async/pending state of its own; the row simply disappears once
|
|
@@ -58,11 +58,11 @@ function ordered(sessions) {
|
|
|
58
58
|
*/
|
|
59
59
|
function RevokeRow({ session, onRevoke }) {
|
|
60
60
|
const label = deviceLabel(session);
|
|
61
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.AlertDialog, { children: [(0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, size: "sm", variant: "tertiary", className: "shrink-0 text-muted hover:text-danger", "aria-label": `Sign out ${label}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { className: "size-4" }) }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Container, { children: (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Dialog, { className: "sm:max-w-100", children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.CloseTrigger, {}), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Header, { children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.Icon, { status: "danger" }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Heading, { children: "Sign out this device?" })] }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Body, { children: (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: ["This ends every sign-in from ", label, ", on every
|
|
61
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.AlertDialog, { children: [(0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, size: "sm", variant: "tertiary", className: "shrink-0 text-muted hover:text-danger", "aria-label": `Sign out ${label}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { className: "size-4" }) }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Container, { children: (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Dialog, { className: "sm:max-w-100", children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.CloseTrigger, {}), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Header, { children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.Icon, { status: "danger" }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Heading, { children: "Sign out this device?" })] }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Body, { children: (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: ["This ends every sign-in from ", label, ", on every application, not just this one."] }) }), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Footer, { children: [(0, jsx_runtime_1.jsx)(react_2.Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), (0, jsx_runtime_1.jsx)(react_2.Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onRevoke, children: "Sign out" })] })] }) }) })] }));
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* Every device signed into this Connect account - not scoped to this
|
|
65
|
-
*
|
|
65
|
+
* application, see `ConnectUserSession`'s doc comment. Owns the one
|
|
66
66
|
* `useConnectSessions()` call for this modal; every revoke action here
|
|
67
67
|
* mutates through it and re-reads the list itself, since nothing else
|
|
68
68
|
* invalidates it automatically - see `mutateResource`'s doc comment.
|
|
@@ -67,12 +67,12 @@ function BrandLink({ name, icon, homeHref, collapsed, renderLink, }) {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* The application frame every CosmoOps
|
|
70
|
+
* The application frame every CosmoOps application platform runs inside: a
|
|
71
71
|
* collapsible sidebar, a top bar carrying the app launcher and the account
|
|
72
72
|
* menu, and your pages in the middle.
|
|
73
73
|
*
|
|
74
74
|
* This is the piece each partner was otherwise writing by hand, and writing
|
|
75
|
-
* slightly differently - which is how a family of
|
|
75
|
+
* slightly differently - which is how a family of applications stops looking like
|
|
76
76
|
* one product. Everything that should be identical across platforms (the
|
|
77
77
|
* lockup, the rail geometry, the active-route treatment, the launcher, the
|
|
78
78
|
* account menu, the access gate) lives here; everything that genuinely
|
|
@@ -22,7 +22,7 @@ function AppTile({ app }) {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* The Google-style app launcher: a grid icon in your top bar that opens a
|
|
25
|
-
* modal of every Connect
|
|
25
|
+
* modal of every Connect application this workspace can actually open.
|
|
26
26
|
*
|
|
27
27
|
* Only launchable apps appear, so no tile is a dead end - a paused
|
|
28
28
|
* subscription still shows, badged, because a dunning cycle should not make a
|
|
@@ -39,9 +39,9 @@ function ConnectAppsGrid({ className, label = "Switch app" }) {
|
|
|
39
39
|
if (sessionLoading || !session?.user)
|
|
40
40
|
return null;
|
|
41
41
|
const apps = data?.apps ?? [];
|
|
42
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(react_1.Modal, { children: [(0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, variant: "primary", "aria-label": label, className: className, children: (0, jsx_runtime_1.jsx)(lucide_react_1.LayoutGrid, { className: "size-5" }) }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { className: "sm:max-w-md", children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Your apps" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { children: [isLoading && ((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 6 }, (_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center gap-2 p-3", children: [(0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "size-12 rounded-xl" }), (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-3 w-14" })] }, index))) })), !isLoading && error && ((0, jsx_runtime_1.jsx)("p", { className: "py-6 text-center text-sm text-danger", children: "Could not load your apps." })), !isLoading && !error && apps.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: "py-6 text-center text-sm text-muted", children: "No other apps yet. Anything this workspace subscribes to shows up here." })), !isLoading && !error && apps.length > 0 && ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: "grid grid-cols-3 gap-2", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: apps.map((app) => ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: motion_js_1.ENTER } }, children: (0, jsx_runtime_1.jsx)(AppTile, { app: app }) }, app.
|
|
42
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(react_1.Modal, { children: [(0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, variant: "primary", "aria-label": label, className: className, children: (0, jsx_runtime_1.jsx)(lucide_react_1.LayoutGrid, { className: "size-5" }) }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { className: "sm:max-w-md", children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Your apps" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { children: [isLoading && ((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 6 }, (_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center gap-2 p-3", children: [(0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "size-12 rounded-xl" }), (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-3 w-14" })] }, index))) })), !isLoading && error && ((0, jsx_runtime_1.jsx)("p", { className: "py-6 text-center text-sm text-danger", children: "Could not load your apps." })), !isLoading && !error && apps.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: "py-6 text-center text-sm text-muted", children: "No other apps yet. Anything this workspace subscribes to shows up here." })), !isLoading && !error && apps.length > 0 && ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: "grid grid-cols-3 gap-2", variants: { show: { transition: { staggerChildren: 0.04 } } }, initial: "hidden", animate: "show", children: apps.map((app) => ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { variants: { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: motion_js_1.ENTER } }, children: (0, jsx_runtime_1.jsx)(AppTile, { app: app }) }, app.applicationId))) }))] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Footer, { children: (0, jsx_runtime_1.jsx)(ConnectBrowseAllLink, {}) })] }) }) })] }) }));
|
|
43
43
|
}
|
|
44
44
|
function ConnectBrowseAllLink() {
|
|
45
45
|
const { appUrl } = (0, provider_js_1.useConnect)();
|
|
46
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", className: "w-full", onPress: () => window.open(`${appUrl}/
|
|
46
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", className: "w-full", onPress: () => window.open(`${appUrl}/applications`, "_blank", "noopener,noreferrer"), children: ["Browse all applications", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5 text-muted" })] }));
|
|
47
47
|
}
|
|
@@ -42,7 +42,7 @@ function useConnectFeature(feature) {
|
|
|
42
42
|
*
|
|
43
43
|
* Like every gate in this package, it is a **UX gate, not a security
|
|
44
44
|
* boundary**: the entitlement is read through your own origin and the browser
|
|
45
|
-
* can ignore it. Check `client.entitlements.hasFeature(
|
|
45
|
+
* can ignore it. Check `client.entitlements.hasFeature(workspaceId, feature)` on the
|
|
46
46
|
* server before doing the thing the feature unlocks.
|
|
47
47
|
*
|
|
48
48
|
* Renders `loading` - not `fallback` - while the answer is unknown, so a paying
|
|
@@ -57,5 +57,5 @@ function ConnectFeatureGate({ feature, children, fallback, loading = null, plans
|
|
|
57
57
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
58
58
|
if (fallback !== undefined)
|
|
59
59
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback });
|
|
60
|
-
return ((0, jsx_runtime_1.jsx)(subscription_js_1.ConnectNoSubscriptionCard, { plansHref: plansHref, title: title ?? "Not included in your plan", description: description ?? "This part of the
|
|
60
|
+
return ((0, jsx_runtime_1.jsx)(subscription_js_1.ConnectNoSubscriptionCard, { plansHref: plansHref, title: title ?? "Not included in your plan", description: description ?? "This part of the application is on a higher plan. Compare plans to see what unlocks it.", className: className }));
|
|
61
61
|
}
|
|
@@ -45,7 +45,7 @@ function ToolkitTile({ toolkit, isPending, onConnect, onDisconnect }) {
|
|
|
45
45
|
(0, jsx_runtime_1.jsx)("img", { src: toolkit.logoUrl, alt: "", className: "size-full object-cover" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Puzzle, { className: "size-6 text-muted", "aria-hidden": "true" })), toolkit.status === "failed" && ((0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { className: "size-3", "aria-hidden": "true" }) }))] }), (0, jsx_runtime_1.jsx)("span", { className: "line-clamp-2 text-center text-xs font-medium text-foreground", children: toolkit.name }), (0, jsx_runtime_1.jsx)(react_2.Chip, { size: "sm", variant: "soft", color: STATUS_COLOR[toolkit.status], children: STATUS_LABEL[toolkit.status] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex w-full flex-col gap-1.5 pt-1", children: [CAN_CONNECT.has(toolkit.status) && ((0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Connect" })), toolkit.status === "active" && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "secondary", className: "w-full text-xs", isPending: isPending, isDisabled: isPending, onPress: onConnect, children: "Reconnect" }), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog, { isOpen: confirmingDisconnect, onOpenChange: setConfirmingDisconnect, children: [(0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, children: "Disconnect" }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Container, { children: (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Dialog, { className: "sm:max-w-100", children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.CloseTrigger, {}), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Header, { children: [(0, jsx_runtime_1.jsx)(react_2.AlertDialog.Icon, { status: "danger" }), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Heading, { children: ["Disconnect ", toolkit.name, "?"] })] }), (0, jsx_runtime_1.jsx)(react_2.AlertDialog.Body, { children: (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "Every member loses this connection immediately - it can be reconnected later." }) }), (0, jsx_runtime_1.jsxs)(react_2.AlertDialog.Footer, { children: [(0, jsx_runtime_1.jsx)(react_2.Button, { slot: "close", variant: "secondary", className: "text-xs", children: "Cancel" }), (0, jsx_runtime_1.jsx)(react_2.Button, { slot: "close", variant: "danger", className: "text-xs", onPress: onDisconnect, children: "Disconnect" })] })] }) }) })] })] })), toolkit.status === "pending" && ((0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "tertiary", className: "w-full text-xs text-danger", isDisabled: isPending, onPress: onDisconnect, children: "Cancel" }))] })] }));
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* A grid of every Composio-hosted toolkit this
|
|
48
|
+
* A grid of every Composio-hosted toolkit this workspace can connect -
|
|
49
49
|
* Google Calendar, Slack, Twitter/X, whatever a platform admin enabled in
|
|
50
50
|
* Connect's own Console. Each tile carries its own connect/reconnect/
|
|
51
51
|
* disconnect action and its own pending state, independent of every other
|
|
@@ -5,10 +5,10 @@ exports.ConnectLogo = ConnectLogo;
|
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const mark_js_1 = require("../mark.js");
|
|
7
7
|
/**
|
|
8
|
-
* The shared platform lockup:
|
|
9
|
-
* `cosmo·ops` beside it, and the
|
|
8
|
+
* The shared platform lockup: an application's own glyph on the CosmoOps badge,
|
|
9
|
+
* `cosmo·ops` beside it, and the application name underneath.
|
|
10
10
|
*
|
|
11
|
-
* This is what makes a family of
|
|
11
|
+
* This is what makes a family of application platforms look like one product. Each
|
|
12
12
|
* platform passes only what differs - its `name` and its `icon` - and inherits
|
|
13
13
|
* the mark geometry, the wordmark treatment, the separator, and the accent from
|
|
14
14
|
* one place. Change the lockup here and every platform follows.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* an upgrade while the card behind it said downgrade would be worse than
|
|
6
6
|
* either label being wrong on its own.
|
|
7
7
|
*
|
|
8
|
-
* Mirrors `packages/data/src/
|
|
8
|
+
* Mirrors `packages/data/src/applications/catalog.ts` and
|
|
9
9
|
* `packages/data/src/payments/plan-change.ts`, which are the copies Connect's
|
|
10
10
|
* own UI uses and which decide which use-case actually runs. The duplication
|
|
11
11
|
* is deliberate and load-bearing: this package carries no dependency on
|
|
@@ -47,7 +47,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
47
47
|
/** The plan under review in the confirmation dialog, or `null` when it is closed. */
|
|
48
48
|
const [pending, setPending] = (0, react_2.useState)(null);
|
|
49
49
|
const subscription = entitlements?.subscription ?? null;
|
|
50
|
-
const
|
|
50
|
+
const applicationId = subscription?.applicationId ?? null;
|
|
51
51
|
const allPlans = (0, react_2.useMemo)(() => {
|
|
52
52
|
const plans = catalog?.plans ?? [];
|
|
53
53
|
if (!planIds)
|
|
@@ -85,12 +85,12 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
85
85
|
* then would misreport a completed checkout as an abandoned one.
|
|
86
86
|
*/
|
|
87
87
|
function startCheckout(plan) {
|
|
88
|
-
if (!
|
|
88
|
+
if (!applicationId)
|
|
89
89
|
return;
|
|
90
90
|
checkout.start({
|
|
91
91
|
url: (0, use_checkout_js_1.buildCheckoutUrl)({
|
|
92
92
|
appUrl,
|
|
93
|
-
|
|
93
|
+
applicationId,
|
|
94
94
|
planId: plan.id,
|
|
95
95
|
subscriptionId: subscription?.subscription?.id ?? null,
|
|
96
96
|
coupon,
|
|
@@ -119,7 +119,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
119
119
|
return null;
|
|
120
120
|
const action = (0, plan_pricing_js_1.resolveAction)(plan, subscription);
|
|
121
121
|
// "Current" belongs to the tier, not just the exact price point: an
|
|
122
|
-
//
|
|
122
|
+
// workspace on Growth annual looking at monthly pricing is still on the
|
|
123
123
|
// Growth tier, and marking only the row it literally subscribes to
|
|
124
124
|
// would leave its own card unmarked half the time.
|
|
125
125
|
const isCurrentTier = subscription?.plan != null &&
|
|
@@ -137,8 +137,8 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
137
137
|
? "primary"
|
|
138
138
|
: "secondary",
|
|
139
139
|
// Disabled while a hand-off is in flight, not just on the
|
|
140
|
-
// card that started it: the
|
|
141
|
-
//
|
|
140
|
+
// card that started it: the workspace has one subscription per
|
|
141
|
+
// application, so a second checkout opened against a different
|
|
142
142
|
// plan would race the first and one of them would be
|
|
143
143
|
// rejected on arrival.
|
|
144
144
|
isDisabled: isCurrent || checkout.status === "waiting", onPress: () => setPending({ plan, action }), children: [plan_pricing_js_1.ACTION_LABEL[action], !isCurrent && (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }) })] }) }, tier.planGroup));
|
|
@@ -33,7 +33,7 @@ function actionLabel(blocker) {
|
|
|
33
33
|
return ACTION_LABELS[blocker.code] ?? "Resolve on Connect";
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Every gate between this workspace and your
|
|
36
|
+
* Every gate between this workspace and your application, with the way out of each.
|
|
37
37
|
*
|
|
38
38
|
* This is the component that keeps a blocked customer from staring at a broken
|
|
39
39
|
* app. `getReadiness` resolves subscription state, KYC/KYB, a chargeable
|
|
@@ -41,7 +41,7 @@ function actionLabel(blocker) {
|
|
|
41
41
|
* `resolveUrl` into the exact Connect screen that fixes it** - so being blocked
|
|
42
42
|
* comes with somewhere to go rather than a refusal.
|
|
43
43
|
*
|
|
44
|
-
* Render it high on any page your
|
|
44
|
+
* Render it high on any page your application gates. It draws nothing at all when
|
|
45
45
|
* the workspace is ready, so it is safe to leave mounted permanently.
|
|
46
46
|
*
|
|
47
47
|
* Two deliberate refusals:
|
|
@@ -76,7 +76,7 @@ function BlockerRow({ blocker }) {
|
|
|
76
76
|
return ((0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: [actionLabel(blocker), (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }))] }));
|
|
77
77
|
}
|
|
78
78
|
function ReadyRow({ className }) {
|
|
79
|
-
return ((0, jsx_runtime_1.jsx)("p", { className: `text-sm text-muted ${className ?? ""}`, children: "This workspace is cleared to use every part of the
|
|
79
|
+
return ((0, jsx_runtime_1.jsx)("p", { className: `text-sm text-muted ${className ?? ""}`, children: "This workspace is cleared to use every part of the application." }));
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* Renders `children` only once the workspace clears every blocking gate.
|
|
@@ -37,7 +37,7 @@ const HEADING = {
|
|
|
37
37
|
* The authoritative price is computed by Connect against the real cart:
|
|
38
38
|
* proration is priced against the actual cycle, a coupon is validated
|
|
39
39
|
* against the plan it is being applied to, trial eligibility depends on
|
|
40
|
-
* whether this
|
|
40
|
+
* whether this workspace has ever subscribed to this application before, and
|
|
41
41
|
* an accumulated credit is deducted. None of that is knowable from the
|
|
42
42
|
* partner's origin, so this component states the list price and says where
|
|
43
43
|
* the final figure comes from rather than quietly guessing at one.
|
|
@@ -50,7 +50,7 @@ function ConnectSubscribeDialog({ plan, action, current, coupon, onClose, onConf
|
|
|
50
50
|
// member and the one most in need of it: the two prices differ by an order
|
|
51
51
|
// of magnitude, and showing only the new one reads as a price rise.
|
|
52
52
|
const isChange = action === "upgrade" || action === "downgrade" || isIntervalSwitch;
|
|
53
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "sm", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, { onPress: onClose }), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: HEADING[action] }) }), plan && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-muted", children: currentPlan.name }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: [(0, plan_pricing_js_1.formatPrice)(currentPlan.price), "/", currentPlan.billingInterval === "annual" ? "yr" : "mo"] })] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate font-medium text-foreground", children: plan.name }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: [(0, plan_pricing_js_1.formatPrice)(plan.price), "/", plan.billingInterval === "annual" ? "yr" : "mo"] })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: plan.description })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-muted", children: [(0, jsx_runtime_1.jsx)("span", { children: plan.name }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(plan.price) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-muted", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["GST (", plan.gstRate, "%)"] }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(withGst(plan.price, plan.gstRate) - plan.price) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Per ", cadenceOf(plan)] }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: ["Works out to ", (0, plan_pricing_js_1.formatPrice)((0, plan_pricing_js_1.monthlyOf)(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] }), plan.trialDays ? ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-accent", children: [plan.trialDays, "-day free trial, if this workspace has not subscribed to this
|
|
53
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { isOpen: plan !== null, onOpenChange: (open) => !open && onClose(), children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "sm", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, { onPress: onClose }), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: HEADING[action] }) }), plan && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-4", children: [isChange && currentPlan ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-muted", children: currentPlan.name }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: [(0, plan_pricing_js_1.formatPrice)(currentPlan.price), "/", currentPlan.billingInterval === "annual" ? "yr" : "mo"] })] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate font-medium text-foreground", children: plan.name }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: [(0, plan_pricing_js_1.formatPrice)(plan.price), "/", plan.billingInterval === "annual" ? "yr" : "mo"] })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "font-medium text-foreground", children: plan.name }), plan.description && (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: plan.description })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1 border-t border-separator pt-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-muted", children: [(0, jsx_runtime_1.jsx)("span", { children: plan.name }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(plan.price) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-muted", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["GST (", plan.gstRate, "%)"] }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(withGst(plan.price, plan.gstRate) - plan.price) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between border-t border-separator pt-1 font-semibold text-foreground", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Per ", cadenceOf(plan)] }), (0, jsx_runtime_1.jsx)("span", { children: (0, plan_pricing_js_1.formatPrice)(withGst(plan.price, plan.gstRate)) })] }), plan.billingInterval === "annual" && ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: ["Works out to ", (0, plan_pricing_js_1.formatPrice)((0, plan_pricing_js_1.monthlyOf)(withGst(plan.price, plan.gstRate), "annual")), " a month, billed yearly."] }))] }), plan.trialDays ? ((0, jsx_runtime_1.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() ? ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: ["Coupon ", (0, jsx_runtime_1.jsx)("span", { className: "font-mono text-foreground", children: coupon.trim() }), " is carried over and validated on Connect."] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2 rounded-lg bg-surface p-3 text-xs text-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1", children: [action === "subscribe" && ((0, jsx_runtime_1.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" && ((0, jsx_runtime_1.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" && ((0, jsx_runtime_1.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 && (
|
|
54
54
|
// Not a proration. The cycle you are on ends, so this is
|
|
55
55
|
// the one action where the amount collected can exceed a
|
|
56
56
|
// single cycle's list price - or be nothing at all, with
|
|
@@ -63,7 +63,7 @@ function useSubscriptionState() {
|
|
|
63
63
|
* Rendered on its own it is just a card; `ConnectSubscriptionGate` is the usual
|
|
64
64
|
* way in, since it picks this only when there is genuinely nothing to show.
|
|
65
65
|
*/
|
|
66
|
-
function ConnectNoSubscriptionCard({ plansHref = DEFAULT_PLANS_HREF, title = "You're not on a plan yet", description = "Pick a plan to unlock the full
|
|
66
|
+
function ConnectNoSubscriptionCard({ plansHref = DEFAULT_PLANS_HREF, title = "You're not on a plan yet", description = "Pick a plan to unlock the full application. You can change or cancel it whenever you like.", className, }) {
|
|
67
67
|
return ((0, jsx_runtime_1.jsxs)(react_1.Card, { className: className, children: [(0, jsx_runtime_1.jsxs)(react_1.Card.Header, { children: [(0, jsx_runtime_1.jsx)(react_1.Card.Title, { children: title }), (0, jsx_runtime_1.jsx)(react_1.Card.Description, { children: description })] }), (0, jsx_runtime_1.jsx)(react_1.Card.Footer, { children: (0, jsx_runtime_1.jsxs)(link_button_js_1.ConnectLinkButton, { href: plansHref, variant: "primary", children: ["See plans", (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { className: "size-3.5" })] }) })] }));
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -19,7 +19,7 @@ function formatDate(iso) {
|
|
|
19
19
|
*
|
|
20
20
|
* Reads the balance Connect actually debits, so the number here and the number
|
|
21
21
|
* `consumeAddon` enforces cannot disagree. Before the `addons:read` endpoint
|
|
22
|
-
* existed this component was impossible to write honestly:
|
|
22
|
+
* existed this component was impossible to write honestly: an application could
|
|
23
23
|
* spend an allowance down on every request and the only thing the API exposed
|
|
24
24
|
* was the list of add-on *ids* on the subscription.
|
|
25
25
|
*
|
|
@@ -105,7 +105,7 @@ function DestinationTile({ icon, label, url, }) {
|
|
|
105
105
|
* in place, which is why it sits alone in the footer rather than as a fourth
|
|
106
106
|
* item in the same list as three navigations.
|
|
107
107
|
*
|
|
108
|
-
* The session list is account-wide, not scoped to this
|
|
108
|
+
* The session list is account-wide, not scoped to this application - see
|
|
109
109
|
* `ConnectUserSession`'s doc comment in `types.ts`. Appearance only renders
|
|
110
110
|
* when both `theme` and `onThemeChange` are supplied.
|
|
111
111
|
*
|
|
@@ -130,13 +130,13 @@ function ConnectUserMenu({ compact = false, onSignOut, className, theme, onTheme
|
|
|
130
130
|
if (!user)
|
|
131
131
|
return null;
|
|
132
132
|
const label = user.displayName ?? user.email ?? "Account";
|
|
133
|
-
const role = session?.
|
|
133
|
+
const role = session?.applicationRole?.role;
|
|
134
134
|
const planChip = subscriptionChip(session?.subscription ?? null);
|
|
135
135
|
return ((0, jsx_runtime_1.jsxs)(react_1.Modal, { children: [(0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", "aria-label": `Account: ${label}`,
|
|
136
136
|
// `pl-1` against the default right padding is optical, not sloppy: a
|
|
137
137
|
// circular avatar needs less inset than a text label to look evenly
|
|
138
138
|
// seated in the button.
|
|
139
|
-
className: `gap-2 pl-1 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "sm" }), !compact && (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-foreground", children: label })] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "md", scroll: "inside", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Account" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: `flex items-start gap-3 rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} p-4`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.email }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.
|
|
139
|
+
className: `gap-2 pl-1 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "sm" }), !compact && (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-foreground", children: label })] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "md", scroll: "inside", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Account" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: `flex items-start gap-3 rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} p-4`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.email }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [(0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.UserRound, { className: surfaces_js_1.ICON, "aria-hidden": "true" }), label: "Profile", url: `${appUrl}/profile` }), (0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.CreditCard, { className: surfaces_js_1.ICON, "aria-hidden": "true" }), label: "Billing", url: `${appUrl}/billing` }), (0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: surfaces_js_1.ICON }), label: "Connect", url: appUrl })] }), theme && onThemeChange && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Appearance" }), (0, jsx_runtime_1.jsx)(react_1.ToggleButtonGroup, { fullWidth: true, "aria-label": "Theme", selectionMode: "single", disallowEmptySelection: true, selectedKeys: [theme], onSelectionChange: (keys) => {
|
|
140
140
|
const [next] = [...keys];
|
|
141
141
|
if (typeof next === "string")
|
|
142
142
|
onThemeChange(next);
|
|
@@ -157,7 +157,7 @@ function ConnectManageSubscriptionButton({ className }) {
|
|
|
157
157
|
* Invoices and payment history, on Connect.
|
|
158
158
|
*
|
|
159
159
|
* A deep link rather than a component that lists them, and that is not a
|
|
160
|
-
* placeholder: nothing in `/api/v1/
|
|
160
|
+
* placeholder: nothing in `/api/v1/application/*` exposes invoices, so an SDK-side
|
|
161
161
|
* list would have nothing to read. It is also the right answer regardless -
|
|
162
162
|
* a GST tax invoice is a legal document with a fixed format, downloadable and
|
|
163
163
|
* emailed from one place, and a partner-rendered copy of one would be a second
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -12,7 +12,7 @@ exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exp
|
|
|
12
12
|
*
|
|
13
13
|
* Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
|
|
14
14
|
* `separator`, ...), never a hardcoded colour - so these components inherit
|
|
15
|
-
* whatever theme the host
|
|
15
|
+
* whatever theme the host application platform already configured, and one theme
|
|
16
16
|
* change lands identically in all of them.
|
|
17
17
|
*/
|
|
18
18
|
var provider_js_1 = require("./provider.js");
|
package/dist/cjs/client/mark.js
CHANGED
|
@@ -7,7 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
7
7
|
* The Connect mark - concentric rings on a solid accent badge.
|
|
8
8
|
*
|
|
9
9
|
* Painted from `--accent` / `--accent-foreground`, the same HeroUI tokens the
|
|
10
|
-
* host app themes, so the badge tracks whatever palette
|
|
10
|
+
* host app themes, so the badge tracks whatever palette an application platform has
|
|
11
11
|
* configured rather than carrying a hardcoded CosmoOps green into someone
|
|
12
12
|
* else's brand.
|
|
13
13
|
*/
|
|
@@ -135,7 +135,7 @@ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri
|
|
|
135
135
|
// error, so a Login button still renders rather than the UI hanging on
|
|
136
136
|
// skeletons forever.
|
|
137
137
|
setResult({
|
|
138
|
-
session: { user: null, subscription: null,
|
|
138
|
+
session: { user: null, subscription: null, applicationRole: null },
|
|
139
139
|
error: cause instanceof Error ? cause.message : "Could not reach Connect",
|
|
140
140
|
});
|
|
141
141
|
}
|
|
@@ -184,7 +184,7 @@ function ConnectProvider({ clientId = "", basePath = "/api/connect", redirectUri
|
|
|
184
184
|
credentials: "same-origin",
|
|
185
185
|
}).catch(() => { });
|
|
186
186
|
cache.current.clear();
|
|
187
|
-
setResult({ session: { user: null, subscription: null,
|
|
187
|
+
setResult({ session: { user: null, subscription: null, applicationRole: null }, error: null });
|
|
188
188
|
}, [basePath, fetchImpl]);
|
|
189
189
|
const value = (0, react_1.useMemo)(() => ({
|
|
190
190
|
clientId,
|
|
@@ -231,28 +231,28 @@ function useConnectEntitlements() {
|
|
|
231
231
|
/**
|
|
232
232
|
* What is left of every consumable add-on on this workspace's subscription.
|
|
233
233
|
*
|
|
234
|
-
* The read half of `client.billing.consumeAddon` -
|
|
234
|
+
* The read half of `client.billing.consumeAddon` - an application that meters its
|
|
235
235
|
* own end users had no way to show them what remained until this existed.
|
|
236
236
|
* Empty `addons` means there is nothing to meter, which is not an error.
|
|
237
237
|
*/
|
|
238
238
|
function useConnectAddonUsage() {
|
|
239
239
|
return useConnectResource("/addons");
|
|
240
240
|
}
|
|
241
|
-
/** This
|
|
241
|
+
/** This application's plan catalog. */
|
|
242
242
|
function useConnectPlans() {
|
|
243
243
|
return useConnectResource("/plans");
|
|
244
244
|
}
|
|
245
|
-
/** The apps this
|
|
245
|
+
/** The apps this workspace can open. */
|
|
246
246
|
function useConnectApps() {
|
|
247
247
|
return useConnectResource("/apps");
|
|
248
248
|
}
|
|
249
|
-
/** Enabled Composio-hosted toolkits and this
|
|
249
|
+
/** Enabled Composio-hosted toolkits and this workspace's connection status for each. */
|
|
250
250
|
function useConnectIntegrations() {
|
|
251
251
|
return useConnectResource("/integrations");
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
254
|
* This Connect account's own login sessions - account-wide, not scoped to
|
|
255
|
-
* this
|
|
255
|
+
* this application, see `SessionsListResponse`'s doc comment. The component
|
|
256
256
|
* that calls this is also the one that must own any revoke actions and call
|
|
257
257
|
* the returned `refetch()` afterward - see `mutateResource`'s doc comment
|
|
258
258
|
* for why nothing does that automatically.
|
|
@@ -157,15 +157,15 @@ function buildCancelUrl({ appUrl, subscriptionId }) {
|
|
|
157
157
|
* The Connect URL that completes a subscribe or a plan change.
|
|
158
158
|
*
|
|
159
159
|
* Both land on a modal Connect renders over the relevant page (an intercepted
|
|
160
|
-
* parallel route - `app/@modal/(.)
|
|
160
|
+
* parallel route - `app/@modal/(.)applications/...` and
|
|
161
161
|
* `app/@modal/(.)billing/...`), so the customer arrives already looking at the
|
|
162
162
|
* confirmation for the plan they picked here, not at a catalogue they have to
|
|
163
163
|
* find it in again.
|
|
164
164
|
*/
|
|
165
|
-
function buildCheckoutUrl({ appUrl,
|
|
165
|
+
function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, }) {
|
|
166
166
|
const path = subscriptionId
|
|
167
167
|
? `/billing/subscriptions/${encodeURIComponent(subscriptionId)}/change-plan/${encodeURIComponent(planId)}`
|
|
168
|
-
: `/
|
|
168
|
+
: `/applications/${encodeURIComponent(applicationId)}/subscribe/${encodeURIComponent(planId)}`;
|
|
169
169
|
const url = new URL(path, appUrl);
|
|
170
170
|
if (coupon?.trim())
|
|
171
171
|
url.searchParams.set("coupon", coupon.trim());
|
package/dist/cjs/errors.js
CHANGED
|
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ConnectError = void 0;
|
|
4
4
|
exports.isConnectError = isConnectError;
|
|
5
5
|
/**
|
|
6
|
-
* Every failure the Connect
|
|
6
|
+
* Every failure the Connect application API can report, as one class.
|
|
7
7
|
*
|
|
8
8
|
* The API speaks two error dialects and this collapses them: the
|
|
9
|
-
* `/api/v1/
|
|
9
|
+
* `/api/v1/application/*` endpoints use `{ error: { code, message } }`, while the
|
|
10
10
|
* three `/sso/*` ones use OAuth2's `{ error, error_description }` (RFC 6749
|
|
11
11
|
* §5.2). Both land here with `code` populated, so integrating code branches on
|
|
12
12
|
* one field either way.
|
|
@@ -45,7 +45,7 @@ class ConnectError extends Error {
|
|
|
45
45
|
* Whether the user's Connect sign-in is over and they need to go back
|
|
46
46
|
* through `identity.authorizeUrl()`. Collapses every session-side failure,
|
|
47
47
|
* matching how the API reports them - unknown, expired, revoked, or issued
|
|
48
|
-
* to a different
|
|
48
|
+
* to a different application all arrive as one `invalid_token`.
|
|
49
49
|
*/
|
|
50
50
|
get isSessionExpired() {
|
|
51
51
|
return this.code === "invalid_token";
|
|
@@ -59,7 +59,7 @@ class ConnectError extends Error {
|
|
|
59
59
|
* guessing keys. These two are the exception: reaching them required
|
|
60
60
|
* presenting a key whose hash matched, so you already hold it, and what you
|
|
61
61
|
* need to know is that no retry and no scope change will fix this - the key
|
|
62
|
-
* has to be reissued from the
|
|
62
|
+
* has to be reissued from the application's Integration tab.
|
|
63
63
|
*
|
|
64
64
|
* Worth alerting on. It is the one failure that will not resolve itself and
|
|
65
65
|
* takes down every call your integration makes.
|
package/dist/cjs/helpers.js
CHANGED
|
@@ -16,7 +16,7 @@ function blockingBlockers(readiness) {
|
|
|
16
16
|
* Coerces either wire shape into `PlanFeature[]`.
|
|
17
17
|
*
|
|
18
18
|
* A bare key becomes `{ key, name: key, description: "" }` - the same
|
|
19
|
-
* fallback the server applies for a plan older than its
|
|
19
|
+
* fallback the server applies for a plan older than its application's feature
|
|
20
20
|
* catalog, so an un-upgraded Connect renders exactly what it always did
|
|
21
21
|
* rather than a column of blanks.
|
|
22
22
|
*
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.normalizePlanFeatures = exports.blockingBlockers = exports.isConnectError = exports.ConnectError = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `@cosmoops/connect-sdk` - the official client for the CosmoOps Connect
|
|
6
|
-
* **
|
|
6
|
+
* **application-partner API** (`/api/v1/application/*`).
|
|
7
7
|
*
|
|
8
8
|
* This root entry is the isomorphic surface: wire types, the error class, and
|
|
9
9
|
* pure helpers. It pulls in neither Node-only code nor React, so it is safe to
|
package/dist/cjs/next/index.js
CHANGED
|
@@ -82,7 +82,7 @@ const handler_js_1 = require("../server/handler.js");
|
|
|
82
82
|
* | `CONNECT_SESSION_COOKIE` | no | session cookie name |
|
|
83
83
|
*
|
|
84
84
|
* Register `{your origin}/api/connect/callback` as a redirect URI in the
|
|
85
|
-
*
|
|
85
|
+
* application's Integration tab. That is the only step this cannot do for you.
|
|
86
86
|
*
|
|
87
87
|
* No `next.config.ts` changes needed either, including `transpilePackages`:
|
|
88
88
|
* `dist/` is already ES2022 with `"use client"` hoisted to the top of every
|
|
@@ -317,7 +317,7 @@ async function getConnectSession(request, options = {}) {
|
|
|
317
317
|
* A thin wrapper, not a framework of its own - reach for `getConnectSession`
|
|
318
318
|
* plus your own check when "not signed in" isn't "redirect to sign-in"
|
|
319
319
|
* (an API route returning 401, say). Role and entitlement checks stay
|
|
320
|
-
* outside this function on purpose: `session.
|
|
320
|
+
* outside this function on purpose: `session.applicationRole.granted` and
|
|
321
321
|
* `connect.entitlements.*` are one line each on top of the session this
|
|
322
322
|
* already gives you, and where that line redirects (a plans page, a
|
|
323
323
|
* "forbidden" screen) is a decision only your app can make - see
|