@cosmoops/connect-sdk 0.3.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/CHANGELOG.md +125 -0
- package/README.md +215 -6
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +162 -0
- package/dist/cjs/client/components/account-sessions.js +24 -22
- package/dist/cjs/client/components/app-shell.js +157 -0
- package/dist/cjs/client/components/apps-grid.js +4 -3
- package/dist/cjs/client/components/feature-gate.js +2 -2
- package/dist/cjs/client/components/integrations-modal.js +95 -0
- package/dist/cjs/client/components/logo.js +3 -3
- package/dist/cjs/client/components/motion.js +27 -0
- package/dist/cjs/client/components/nav.js +97 -0
- package/dist/cjs/client/components/plan-pricing.js +1 -1
- package/dist/cjs/client/components/plans.js +42 -40
- package/dist/cjs/client/components/readiness.js +6 -4
- package/dist/cjs/client/components/side-nav.js +58 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +3 -2
- package/dist/cjs/client/components/surfaces.js +54 -0
- package/dist/cjs/client/components/usage-meter.js +4 -3
- package/dist/cjs/client/components/user-menu.js +33 -6
- package/dist/cjs/client/index.js +30 -2
- package/dist/cjs/client/mark.js +1 -1
- package/dist/cjs/client/provider.js +60 -8
- package/dist/cjs/client/use-active-path.js +77 -0
- 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 +96 -15
- package/dist/cjs/server/client.js +22 -14
- package/dist/cjs/server/handler.js +99 -16
- package/dist/cjs/server/http.js +1 -1
- package/dist/cjs/server/index.js +8 -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 +66 -0
- 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 +66 -0
- package/dist/cjs/server/resources/logs.js +3 -3
- package/dist/cjs/server/resources/sessions.js +6 -6
- package/dist/cjs/server/webhooks.js +127 -0
- 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 +158 -0
- package/dist/esm/client/components/account-sessions.js +24 -22
- package/dist/esm/client/components/app-shell.js +154 -0
- package/dist/esm/client/components/apps-grid.js +4 -3
- package/dist/esm/client/components/feature-gate.js +2 -2
- package/dist/esm/client/components/integrations-modal.js +92 -0
- package/dist/esm/client/components/logo.js +3 -3
- package/dist/esm/client/components/motion.js +21 -0
- package/dist/esm/client/components/nav.js +89 -0
- package/dist/esm/client/components/plan-pricing.js +1 -1
- package/dist/esm/client/components/plans.js +42 -40
- package/dist/esm/client/components/readiness.js +6 -4
- package/dist/esm/client/components/side-nav.js +53 -0
- package/dist/esm/client/components/subscribe-dialog.js +2 -2
- package/dist/esm/client/components/subscription.js +3 -2
- package/dist/esm/client/components/surfaces.js +51 -0
- package/dist/esm/client/components/usage-meter.js +4 -3
- package/dist/esm/client/components/user-menu.js +34 -7
- package/dist/esm/client/index.js +16 -2
- package/dist/esm/client/mark.js +1 -1
- package/dist/esm/client/provider.js +59 -8
- package/dist/esm/client/use-active-path.js +74 -0
- 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 +95 -15
- package/dist/esm/server/client.js +22 -14
- package/dist/esm/server/handler.js +99 -16
- package/dist/esm/server/http.js +1 -1
- package/dist/esm/server/index.js +5 -0
- 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 +62 -0
- 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 +62 -0
- package/dist/esm/server/resources/logs.js +3 -3
- package/dist/esm/server/resources/sessions.js +6 -6
- package/dist/esm/server/webhooks.js +123 -0
- package/dist/esm/types.js +1 -1
- package/dist/theme.css +28 -0
- package/dist/types/client/components/access-gate.d.ts +79 -0
- package/dist/types/client/components/account-sessions.d.ts +1 -1
- package/dist/types/client/components/app-shell.d.ts +115 -0
- 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 +21 -0
- package/dist/types/client/components/logo.d.ts +5 -5
- package/dist/types/client/components/motion.d.ts +22 -0
- package/dist/types/client/components/nav.d.ts +110 -0
- 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/side-nav.d.ts +31 -0
- package/dist/types/client/components/subscribe-dialog.d.ts +3 -3
- package/dist/types/client/components/surfaces.d.ts +51 -0
- 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 +13 -2
- package/dist/types/client/mark.d.ts +1 -1
- package/dist/types/client/provider.d.ts +44 -9
- package/dist/types/client/use-active-path.d.ts +8 -0
- 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 +40 -0
- package/dist/types/server/client.d.ts +17 -11
- package/dist/types/server/handler.d.ts +2 -2
- package/dist/types/server/index.d.ts +7 -0
- 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 +31 -0
- 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 +14 -4
- package/dist/types/server/resources/integrations.d.ts +40 -0
- 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 +67 -0
- package/dist/types/types.d.ts +220 -39
- package/package.json +10 -4
|
@@ -8,6 +8,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
8
8
|
const react_2 = require("react");
|
|
9
9
|
const provider_js_1 = require("../provider.js");
|
|
10
10
|
const use_checkout_js_1 = require("../use-checkout.js");
|
|
11
|
+
const motion_js_1 = require("./motion.js");
|
|
11
12
|
const subscribe_dialog_js_1 = require("./subscribe-dialog.js");
|
|
12
13
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
13
14
|
/**
|
|
@@ -46,7 +47,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
46
47
|
/** The plan under review in the confirmation dialog, or `null` when it is closed. */
|
|
47
48
|
const [pending, setPending] = (0, react_2.useState)(null);
|
|
48
49
|
const subscription = entitlements?.subscription ?? null;
|
|
49
|
-
const
|
|
50
|
+
const applicationId = subscription?.applicationId ?? null;
|
|
50
51
|
const allPlans = (0, react_2.useMemo)(() => {
|
|
51
52
|
const plans = catalog?.plans ?? [];
|
|
52
53
|
if (!planIds)
|
|
@@ -84,12 +85,12 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
84
85
|
* then would misreport a completed checkout as an abandoned one.
|
|
85
86
|
*/
|
|
86
87
|
function startCheckout(plan) {
|
|
87
|
-
if (!
|
|
88
|
+
if (!applicationId)
|
|
88
89
|
return;
|
|
89
90
|
checkout.start({
|
|
90
91
|
url: (0, use_checkout_js_1.buildCheckoutUrl)({
|
|
91
92
|
appUrl,
|
|
92
|
-
|
|
93
|
+
applicationId,
|
|
93
94
|
planId: plan.id,
|
|
94
95
|
subscriptionId: subscription?.subscription?.id ?? null,
|
|
95
96
|
coupon,
|
|
@@ -107,41 +108,41 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
107
108
|
if (error) {
|
|
108
109
|
return (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: "Could not load plans." });
|
|
109
110
|
}
|
|
110
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-6 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), (0, jsx_runtime_1.jsx)(react_1.Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: (0, jsx_runtime_1.jsx)(react_1.Switch.Content, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Control, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Thumb, {}) }) }) }), (0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)(react_1.Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, plan_pricing_js_1.discountLabel)(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
111
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-6 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), (0, jsx_runtime_1.jsx)(react_1.Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: (0, jsx_runtime_1.jsx)(react_1.Switch.Content, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Control, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Thumb, {}) }) }) }), (0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)(react_1.Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, plan_pricing_js_1.discountLabel)(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
|
|
112
|
+
? ` - code ${subscription.subscription.appliedDiscount.code}`
|
|
113
|
+
: ""] })] })), (0, jsx_runtime_1.jsx)(ConnectCheckoutStatusRow, { checkout: checkout }), (0, jsx_runtime_1.jsx)(motion_js_1.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) => {
|
|
114
|
+
// The price point this card is showing. A tier missing the toggled
|
|
115
|
+
// cadence falls back to the one it does sell rather than dropping
|
|
116
|
+
// out of the grid, and says so under the price.
|
|
117
|
+
const plan = pickTierPlan(tier, showAnnual);
|
|
118
|
+
if (!plan)
|
|
119
|
+
return null;
|
|
120
|
+
const action = (0, plan_pricing_js_1.resolveAction)(plan, subscription);
|
|
121
|
+
// "Current" belongs to the tier, not just the exact price point: an
|
|
122
|
+
// workspace on Growth annual looking at monthly pricing is still on the
|
|
123
|
+
// Growth tier, and marking only the row it literally subscribes to
|
|
124
|
+
// would leave its own card unmarked half the time.
|
|
125
|
+
const isCurrentTier = subscription?.plan != null &&
|
|
126
|
+
(tier.monthly?.id === subscription.plan.id || tier.annual?.id === subscription.plan.id);
|
|
127
|
+
const isCurrent = action === "current";
|
|
128
|
+
const cadenceUnavailable = showAnnual ? tier.annual === null : tier.monthly === null;
|
|
129
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { variants: { hidden: { opacity: 0, y: 12 }, show: { opacity: 1, y: 0, transition: motion_js_1.ENTER } }, children: (0, jsx_runtime_1.jsxs)(react_1.Card, { className: isCurrentTier ? "h-full border-accent" : "h-full", children: [(0, jsx_runtime_1.jsxs)(react_1.Card.Header, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Card.Title, { children: tier.name }), (0, jsx_runtime_1.jsxs)("div", { className: "flex shrink-0 items-center gap-1.5", children: [tier.annualSavingPercent !== null && showAnnual && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save ", tier.annualSavingPercent, "%"] })), isCurrentTier && (0, jsx_runtime_1.jsx)(react_1.Chip, { className: "bg-accent-soft text-accent", children: "Current" })] })] }), tier.description && (0, jsx_runtime_1.jsx)(react_1.Card.Description, { children: tier.description })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Content, { className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(motion_js_1.AnimatePresence, { mode: "popLayout", initial: false, children: (0, jsx_runtime_1.jsxs)(motion_js_1.m.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.15 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "text-2xl font-semibold text-foreground", children: (0, plan_pricing_js_1.formatPrice)(plan.price) }), (0, jsx_runtime_1.jsxs)("span", { className: "text-sm text-muted", children: ["/", plan.billingInterval === "annual" ? "year" : "month"] })] }, plan.billingInterval) }), (0, jsx_runtime_1.jsxs)("p", { className: "mt-1 text-xs text-muted", children: ["+", plan.gstRate, "% GST"] }), cadenceUnavailable && ((0, jsx_runtime_1.jsxs)("p", { className: "mt-1 text-xs text-muted", children: ["Only sold ", plan.billingInterval === "annual" ? "annually" : "monthly", "."] }))] }), plan.trialDays ? ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs font-medium text-accent", children: [plan.trialDays, "-day free trial"] })) : null, plan.features.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "flex flex-col gap-1.5", children: plan.features.map((feature) => (
|
|
130
|
+
// `name`, not `key` - the key is an identifier
|
|
131
|
+
// ("campaign-module"), and printing it here is what this
|
|
132
|
+
// card used to do. The description rides along as a
|
|
133
|
+
// title so a feature that needs explaining can have one
|
|
134
|
+
// without a second line in every card.
|
|
135
|
+
(0, jsx_runtime_1.jsxs)("li", { className: "flex items-start gap-2 text-sm text-foreground", title: feature.description || undefined, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "mt-0.5 size-4 shrink-0 text-accent", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { children: feature.name })] }, feature.key))) })), plan.addons.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 border-t border-separator pt-3", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs font-medium uppercase tracking-wide text-muted", children: "Add-ons" }), plan.addons.map((addon) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-baseline justify-between gap-2 text-sm", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-foreground", children: [addon.name, addon.consumable && ((0, jsx_runtime_1.jsxs)("span", { className: "text-muted", children: [" ", "- ", addon.consumable.includedPerCycle.toLocaleString("en-IN"), " ", addon.consumable.unit, "/cycle"] }))] }), (0, jsx_runtime_1.jsx)("span", { className: "shrink-0 text-muted", children: (0, plan_pricing_js_1.formatPrice)(addon.price) })] }, addon.id)))] })), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: [plan.seats.model === "unlimited" && "Unlimited seats", plan.seats.model === "fixed" && `${plan.seats.included} seats included`, plan.seats.model === "per-seat" &&
|
|
136
|
+
`${plan.seats.included ?? 0} seats included, then ${(0, plan_pricing_js_1.formatPrice)(plan.seats.pricePerExtraSeat ?? 0)} each`] })] }), (0, jsx_runtime_1.jsx)(react_1.Card.Footer, { children: (0, jsx_runtime_1.jsxs)(react_1.Button, { className: "w-full", variant: action === "upgrade" || action === "subscribe" || action === "switch-to-annual"
|
|
137
|
+
? "primary"
|
|
138
|
+
: "secondary",
|
|
139
|
+
// Disabled while a hand-off is in flight, not just on the
|
|
140
|
+
// card that started it: the workspace has one subscription per
|
|
141
|
+
// application, so a second checkout opened against a different
|
|
142
|
+
// plan would race the first and one of them would be
|
|
143
|
+
// rejected on arrival.
|
|
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));
|
|
145
|
+
}) }), subscription?.subscription && ((0, jsx_runtime_1.jsx)(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), (0, jsx_runtime_1.jsx)(subscribe_dialog_js_1.ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan) })] }) }));
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
147
148
|
* What the page says while the customer is off in Connect, and after.
|
|
@@ -153,8 +154,9 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
153
154
|
* read as one.
|
|
154
155
|
*/
|
|
155
156
|
function ConnectCheckoutStatusRow({ checkout, }) {
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.AnimatePresence, { mode: "wait", initial: false, children: checkout.status !== "idle" && ((0, jsx_runtime_1.jsx)(motion_js_1.m.div, { initial: { opacity: 0, y: 4 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -4 }, transition: { duration: 0.18 }, children: (0, jsx_runtime_1.jsx)(CheckoutStatusRowContent, { checkout: checkout }) }, checkout.status)) }));
|
|
158
|
+
}
|
|
159
|
+
function CheckoutStatusRowContent({ checkout, }) {
|
|
158
160
|
if (checkout.status === "waiting") {
|
|
159
161
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg border border-separator px-3 py-2 text-sm text-muted", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { children: "Finish up in the Connect window. This page updates on its own." })] }));
|
|
160
162
|
}
|
|
@@ -8,6 +8,7 @@ const react_1 = require("@heroui/react");
|
|
|
8
8
|
const lucide_react_1 = require("lucide-react");
|
|
9
9
|
const helpers_js_1 = require("../../helpers.js");
|
|
10
10
|
const provider_js_1 = require("../provider.js");
|
|
11
|
+
const motion_js_1 = require("./motion.js");
|
|
11
12
|
/**
|
|
12
13
|
* Codes that mean "somebody at Connect is looking at it" - there is no action
|
|
13
14
|
* for the customer to take, so these get an informational treatment and no
|
|
@@ -26,12 +27,13 @@ const ACTION_LABELS = {
|
|
|
26
27
|
verification_rejected: "Review verification",
|
|
27
28
|
verification_expired: "Renew verification",
|
|
28
29
|
seat_limit_reached: "Manage seats",
|
|
30
|
+
payment_retry_pending: "Resolve payment",
|
|
29
31
|
};
|
|
30
32
|
function actionLabel(blocker) {
|
|
31
33
|
return ACTION_LABELS[blocker.code] ?? "Resolve on Connect";
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
|
-
* Every gate between this workspace and your
|
|
36
|
+
* Every gate between this workspace and your application, with the way out of each.
|
|
35
37
|
*
|
|
36
38
|
* This is the component that keeps a blocked customer from staring at a broken
|
|
37
39
|
* app. `getReadiness` resolves subscription state, KYC/KYB, a chargeable
|
|
@@ -39,7 +41,7 @@ function actionLabel(blocker) {
|
|
|
39
41
|
* `resolveUrl` into the exact Connect screen that fixes it** - so being blocked
|
|
40
42
|
* comes with somewhere to go rather than a refusal.
|
|
41
43
|
*
|
|
42
|
-
* Render it high on any page your
|
|
44
|
+
* Render it high on any page your application gates. It draws nothing at all when
|
|
43
45
|
* the workspace is ready, so it is safe to leave mounted permanently.
|
|
44
46
|
*
|
|
45
47
|
* Two deliberate refusals:
|
|
@@ -66,7 +68,7 @@ function ConnectReadinessBanner({ includeAdvisory = false, hideWhenReady = true,
|
|
|
66
68
|
const blockers = includeAdvisory ? data.readiness.readiness.blockers : (0, helpers_js_1.blockingBlockers)(data.readiness);
|
|
67
69
|
if (blockers.length === 0)
|
|
68
70
|
return hideWhenReady ? null : (0, jsx_runtime_1.jsx)(ReadyRow, { className: className });
|
|
69
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
71
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: `flex flex-col gap-2 ${className ?? ""}`, role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: blockers.map((blocker) => ((0, jsx_runtime_1.jsx)(BlockerRow, { blocker: blocker }, `${blocker.code}-${blocker.message}`))) }) }));
|
|
70
72
|
}
|
|
71
73
|
function BlockerRow({ blocker }) {
|
|
72
74
|
const inProgress = IN_PROGRESS_CODES.has(blocker.code);
|
|
@@ -74,7 +76,7 @@ function BlockerRow({ blocker }) {
|
|
|
74
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" })] }))] }));
|
|
75
77
|
}
|
|
76
78
|
function ReadyRow({ className }) {
|
|
77
|
-
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." }));
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
82
|
* Renders `children` only once the workspace clears every blocking gate.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.defaultRenderLink = void 0;
|
|
5
|
+
exports.ConnectSideNav = ConnectSideNav;
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("@heroui/react");
|
|
8
|
+
const lucide_react_1 = require("lucide-react");
|
|
9
|
+
const use_active_path_js_1 = require("../use-active-path.js");
|
|
10
|
+
const nav_js_1 = require("./nav.js");
|
|
11
|
+
/** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
|
|
12
|
+
const defaultRenderLink = ({ children, ...props }) => (0, jsx_runtime_1.jsx)("a", { ...props, children: children });
|
|
13
|
+
exports.defaultRenderLink = defaultRenderLink;
|
|
14
|
+
const ROW = "group relative flex items-center rounded-lg text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus";
|
|
15
|
+
function rowClass(isActive, collapsed, depth) {
|
|
16
|
+
const shape = collapsed ? "size-10 justify-center" : `min-h-9 justify-start gap-3 px-3 py-2 ${depth > 0 ? "ml-4" : ""}`;
|
|
17
|
+
const tone = isActive
|
|
18
|
+
? "bg-accent-soft font-medium text-accent"
|
|
19
|
+
: "text-foreground hover:bg-surface-secondary";
|
|
20
|
+
return `${ROW} ${shape} ${tone}`;
|
|
21
|
+
}
|
|
22
|
+
function NavRow({ item, isActive, hasActiveChild, collapsed, depth, renderLink, onNavigate, }) {
|
|
23
|
+
const linkProps = {
|
|
24
|
+
href: item.href,
|
|
25
|
+
className: rowClass(isActive, collapsed, depth),
|
|
26
|
+
"aria-label": item.label,
|
|
27
|
+
"aria-current": isActive ? "page" : undefined,
|
|
28
|
+
onClick: onNavigate,
|
|
29
|
+
...(item.external ? { target: "_blank", rel: "noopener noreferrer" } : {}),
|
|
30
|
+
children: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "relative flex size-5 shrink-0 items-center justify-center", children: item.icon }), !collapsed && (0, jsx_runtime_1.jsx)("span", { className: "relative flex-1 truncate", children: item.label }), !collapsed && item.badge, !collapsed && item.external && ((0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { className: "size-3.5 shrink-0 text-muted", "aria-hidden": "true" })), collapsed && hasActiveChild && !isActive && ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "absolute bottom-1 size-1 rounded-full bg-accent" }))] })),
|
|
31
|
+
};
|
|
32
|
+
// The tooltip stays mounted across expand/collapse and only toggles
|
|
33
|
+
// `isDisabled`. Branching into two trees instead re-creates every item's
|
|
34
|
+
// overlay and focus machinery on each toggle, which is what makes the width
|
|
35
|
+
// animation stutter.
|
|
36
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Tooltip, { delay: 200, isDisabled: !collapsed, children: [(0, jsx_runtime_1.jsx)(react_1.Tooltip.Trigger, { "aria-label": item.label, children: renderLink(linkProps) }), (0, jsx_runtime_1.jsx)(react_1.Tooltip.Content, { placement: "right", children: item.label })] }));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The sidebar's nav column on its own - sections, items, one level of
|
|
40
|
+
* children, and the active highlight.
|
|
41
|
+
*
|
|
42
|
+
* `ConnectAppShell` renders this inside its rail and its mobile drawer, and it
|
|
43
|
+
* is exported for a host that wants the shell's navigation without its
|
|
44
|
+
* chrome: the same items, the same active resolution, dropped into a layout
|
|
45
|
+
* you already have.
|
|
46
|
+
*/
|
|
47
|
+
function ConnectSideNav({ nav, activePath, renderLink = exports.defaultRenderLink, collapsed = false, onNavigate, className, }) {
|
|
48
|
+
const pathname = (0, use_active_path_js_1.useActivePath)(activePath);
|
|
49
|
+
const active = (0, nav_js_1.resolveActivePath)(pathname, nav);
|
|
50
|
+
const sections = (0, nav_js_1.normalizeNav)(nav);
|
|
51
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, children: sections.map((section, index) => ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": section.title ?? "Main", className: `flex flex-col gap-1 ${collapsed ? "items-center" : ""}`, children: [section.title && !collapsed && ((0, jsx_runtime_1.jsx)("h2", { className: "px-3 pb-1 text-xs font-semibold uppercase tracking-wider text-muted", children: section.title })), section.title && collapsed && index > 0 && (0, jsx_runtime_1.jsx)("span", { className: "my-1 h-px w-6 bg-separator", "aria-hidden": "true" }), section.items.map((item) => {
|
|
52
|
+
const isActive = (0, nav_js_1.isNavItemActive)(item, active);
|
|
53
|
+
const activeChild = (item.items ?? []).some((child) => (0, nav_js_1.isNavItemActive)(child, active));
|
|
54
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-1 ${collapsed ? "" : "w-full"}`, children: [(0, jsx_runtime_1.jsx)(NavRow, { item: item, isActive: isActive, hasActiveChild: activeChild, collapsed: collapsed, depth: 0, renderLink: renderLink, onNavigate: onNavigate }), !collapsed &&
|
|
55
|
+
(isActive || activeChild) &&
|
|
56
|
+
item.items?.map((child) => ((0, jsx_runtime_1.jsx)(NavRow, { item: child, isActive: (0, nav_js_1.isNavItemActive)(child, active), hasActiveChild: false, collapsed: false, depth: 1, renderLink: renderLink, onNavigate: onNavigate }, child.href)))] }, item.href));
|
|
57
|
+
})] }, section.title ?? index))) }));
|
|
58
|
+
}
|
|
@@ -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
|
|
@@ -11,6 +11,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
11
11
|
const react_1 = require("@heroui/react");
|
|
12
12
|
const lucide_react_1 = require("lucide-react");
|
|
13
13
|
const provider_js_1 = require("../provider.js");
|
|
14
|
+
const motion_js_1 = require("./motion.js");
|
|
14
15
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
15
16
|
const link_button_js_1 = require("./link-button.js");
|
|
16
17
|
/** Where the plan catalogue lives in the host app. Overridable, because not every app mounts it at `/plans`. */
|
|
@@ -62,7 +63,7 @@ function useSubscriptionState() {
|
|
|
62
63
|
* Rendered on its own it is just a card; `ConnectSubscriptionGate` is the usual
|
|
63
64
|
* way in, since it picks this only when there is genuinely nothing to show.
|
|
64
65
|
*/
|
|
65
|
-
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, }) {
|
|
66
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" })] }) })] }));
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
@@ -84,7 +85,7 @@ function ConnectSubscriptionCard({ plansHref, className }) {
|
|
|
84
85
|
return (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: `h-48 rounded-xl ${className ?? ""}` });
|
|
85
86
|
if (!detail || !plan)
|
|
86
87
|
return (0, jsx_runtime_1.jsx)(ConnectNoSubscriptionCard, { plansHref: plansHref, className: className });
|
|
87
|
-
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.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Card.Title, { children: plan.name }), (0, jsx_runtime_1.jsx)(SubscriptionChip, { state: state })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Description, { children: [(0, plan_pricing_js_1.formatPrice)(plan.price), " + ", plan.gstRate, "% GST per", " ", plan.billingInterval === "annual" ? "year" : "month"] })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Content, { className: "flex flex-col gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)(NextEvent, { detail: detail, state: state }), (0, jsx_runtime_1.jsx)(ConnectSeatUsage, { plan: plan, detail: detail })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Footer, { className: "flex flex-wrap gap-2", children: [(0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(`${appUrl}/billing/subscriptions/${detail.id}`, "_blank", "noopener,noreferrer"), children: ["Manage on Connect", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }), (0, jsx_runtime_1.jsx)(link_button_js_1.ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Change plan" })] })] }));
|
|
88
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.2 }, children: (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.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Card.Title, { children: plan.name }), (0, jsx_runtime_1.jsx)(SubscriptionChip, { state: state })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Description, { children: [(0, plan_pricing_js_1.formatPrice)(plan.price), " + ", plan.gstRate, "% GST per", " ", plan.billingInterval === "annual" ? "year" : "month"] })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Content, { className: "flex flex-col gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)(NextEvent, { detail: detail, state: state }), (0, jsx_runtime_1.jsx)(ConnectSeatUsage, { plan: plan, detail: detail })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Footer, { className: "flex flex-wrap gap-2", children: [(0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(`${appUrl}/billing/subscriptions/${detail.id}`, "_blank", "noopener,noreferrer"), children: ["Manage on Connect", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }), (0, jsx_runtime_1.jsx)(link_button_js_1.ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Change plan" })] })] }) }) }));
|
|
88
89
|
}
|
|
89
90
|
function SubscriptionChip({ state }) {
|
|
90
91
|
switch (state) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ICON = exports.MEDALLION = exports.PANEL_FILL = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The one elevation model every panel inside a Connect modal follows.
|
|
6
|
+
*
|
|
7
|
+
* This exists because "just use `bg-surface`" is wrong here, and wrong in a
|
|
8
|
+
* way that is invisible until you look at the tokens. `Modal.Dialog` paints
|
|
9
|
+
* itself `bg-overlay`, and in the shared theme (`theme.css`) `--overlay` and
|
|
10
|
+
* `--surface` sit at the *same lightness* in both schemes - 100% against 100%
|
|
11
|
+
* in light, 21% against 21% in dark. A `bg-surface` panel dropped on a modal
|
|
12
|
+
* therefore has no fill contrast at all; only its border separates it from
|
|
13
|
+
* the dialog behind it.
|
|
14
|
+
*
|
|
15
|
+
* That is how the account modal ended up looking half-finished: the identity
|
|
16
|
+
* card used `bg-surface-secondary` and read as a solid block, while the
|
|
17
|
+
* destination tiles and the session list used `bg-surface` and read as
|
|
18
|
+
* hairline outlines floating on nothing. Same nesting depth, two different
|
|
19
|
+
* physical materials, for no reason a user could infer.
|
|
20
|
+
*
|
|
21
|
+
* Three levels, and nesting one inside another always steps the fill:
|
|
22
|
+
*
|
|
23
|
+
* - **L0 - the dialog itself.** `bg-overlay`, owned by HeroUI. Not ours.
|
|
24
|
+
* - **L1 - a panel on the dialog.** `PANEL_FILL` + `border-border`. The
|
|
25
|
+
* identity card, each destination tile, the session list.
|
|
26
|
+
* - **L2 - a medallion inside a panel.** `bg-surface`, which is L0's value
|
|
27
|
+
* and therefore reads as *inset* against L1 rather than raised. Light:
|
|
28
|
+
* white on grey. Dark: near-black on charcoal. Both directions legible.
|
|
29
|
+
*
|
|
30
|
+
* The inversion at L2 is deliberate and is why this is written down. Reaching
|
|
31
|
+
* for a "lighter" fill one step down works in light mode and quietly fails in
|
|
32
|
+
* dark; stepping *back to the dialog's own value* is the trick that reads in
|
|
33
|
+
* both, because the contrast comes from the gap between L1 and L0 rather than
|
|
34
|
+
* from a direction of travel.
|
|
35
|
+
*/
|
|
36
|
+
exports.PANEL_FILL = "bg-surface-secondary";
|
|
37
|
+
/**
|
|
38
|
+
* The icon well shared by destination tiles and session rows: a 40px box
|
|
39
|
+
* holding a 20px glyph.
|
|
40
|
+
*
|
|
41
|
+
* Half the well, which is the point. Both surfaces used to run a `size-4`
|
|
42
|
+
* (16px) icon inside a `size-9` (36px) well - a 44% fill that reads as an
|
|
43
|
+
* icon someone forgot to finish, and the specific thing that made the
|
|
44
|
+
* Profile/Billing/Connect tiles look under-drawn. They also disagreed on
|
|
45
|
+
* shape (a circle in the tiles, a rounded square in the session rows) for
|
|
46
|
+
* what is the same component role, so the two surfaces never looked related.
|
|
47
|
+
*
|
|
48
|
+
* Pair with `ICON` on the glyph inside. Session rows override the well to
|
|
49
|
+
* `size-9` for density but keep the same `ICON`, so glyph size stays constant
|
|
50
|
+
* across the whole modal even where the well does not.
|
|
51
|
+
*/
|
|
52
|
+
exports.MEDALLION = "size-10";
|
|
53
|
+
/** The glyph inside a `MEDALLION`. Constant across every well in the modal - see `MEDALLION`. */
|
|
54
|
+
exports.ICON = "size-5";
|
|
@@ -6,6 +6,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
6
6
|
const react_1 = require("@heroui/react");
|
|
7
7
|
const lucide_react_1 = require("lucide-react");
|
|
8
8
|
const provider_js_1 = require("../provider.js");
|
|
9
|
+
const motion_js_1 = require("./motion.js");
|
|
9
10
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
10
11
|
function formatUnits(value, unit) {
|
|
11
12
|
return `${value.toLocaleString("en-IN")} ${unit}`;
|
|
@@ -18,7 +19,7 @@ function formatDate(iso) {
|
|
|
18
19
|
*
|
|
19
20
|
* Reads the balance Connect actually debits, so the number here and the number
|
|
20
21
|
* `consumeAddon` enforces cannot disagree. Before the `addons:read` endpoint
|
|
21
|
-
* existed this component was impossible to write honestly:
|
|
22
|
+
* existed this component was impossible to write honestly: an application could
|
|
22
23
|
* spend an allowance down on every request and the only thing the API exposed
|
|
23
24
|
* was the list of add-on *ids* on the subscription.
|
|
24
25
|
*
|
|
@@ -53,7 +54,7 @@ function ConnectUsageMeter({ addonIds, hideWhenEmpty = true, title = "Usage this
|
|
|
53
54
|
return null;
|
|
54
55
|
return ((0, jsx_runtime_1.jsx)(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: "No metered add-ons on this plan." })] }) }));
|
|
55
56
|
}
|
|
56
|
-
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 }), data.resetsAt && (0, jsx_runtime_1.jsxs)(react_1.Card.Description, { children: ["Resets on ", formatDate(data.resetsAt), "."] })] }), (0, jsx_runtime_1.jsx)(react_1.Card.Content, { className: "flex flex-col gap-4", children: addons.map((addon) => ((0, jsx_runtime_1.jsx)(AddonRow, { addon: addon }, addon.addonId))) })] }));
|
|
57
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (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 }), data.resetsAt && (0, jsx_runtime_1.jsxs)(react_1.Card.Description, { children: ["Resets on ", formatDate(data.resetsAt), "."] })] }), (0, jsx_runtime_1.jsx)(react_1.Card.Content, { className: "flex flex-col gap-4", children: addons.map((addon) => ((0, jsx_runtime_1.jsx)(AddonRow, { addon: addon }, addon.addonId))) })] }) }));
|
|
57
58
|
}
|
|
58
59
|
function AddonRow({ addon }) {
|
|
59
60
|
const overdrawn = addon.balance < 0;
|
|
@@ -64,5 +65,5 @@ function AddonRow({ addon }) {
|
|
|
64
65
|
const carried = addon.balance > addon.includedPerCycle ? addon.balance - addon.includedPerCycle : 0;
|
|
65
66
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-baseline justify-between gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-medium text-foreground", children: addon.name }), (0, jsx_runtime_1.jsxs)("span", { className: overdrawn ? "text-danger" : "text-muted", children: [overdrawn
|
|
66
67
|
? `${formatUnits(Math.abs(addon.balance), addon.unit)} over`
|
|
67
|
-
: `${formatUnits(addon.balance, addon.unit)} left`, (0, jsx_runtime_1.jsxs)("span", { className: "text-muted", children: [" of ", formatUnits(addon.includedPerCycle, addon.unit)] })] })] }), (0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)(
|
|
68
|
+
: `${formatUnits(addon.balance, addon.unit)} left`, (0, jsx_runtime_1.jsxs)("span", { className: "text-muted", children: [" of ", formatUnits(addon.includedPerCycle, addon.unit)] })] })] }), (0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: `h-full rounded-full ${overdrawn ? "bg-danger" : "bg-accent"}`, initial: { width: 0 }, animate: { width: `${overdrawn ? 100 : fraction * 100}%` }, transition: motion_js_1.METER_SPRING }) }), carried > 0 && ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-muted", children: ["Includes ", formatUnits(carried, addon.unit), " carried over from last cycle."] })), overdrawn && addon.overagePricePerUnit !== null && ((0, jsx_runtime_1.jsxs)("p", { className: "flex items-center gap-1.5 text-xs text-danger", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Billed at ", (0, plan_pricing_js_1.formatPrice)(addon.overagePricePerUnit), " per ", addon.unit, " beyond your allowance."] })), !overdrawn && addon.overagePolicy === "reject" && addon.balance === 0 && ((0, jsx_runtime_1.jsxs)("p", { className: "flex items-center gap-1.5 text-xs text-danger", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), "Nothing left this cycle - further use is blocked until it resets."] }))] }));
|
|
68
69
|
}
|
|
@@ -13,6 +13,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
13
13
|
const provider_js_1 = require("../provider.js");
|
|
14
14
|
const mark_js_1 = require("../mark.js");
|
|
15
15
|
const account_sessions_js_1 = require("./account-sessions.js");
|
|
16
|
+
const surfaces_js_1 = require("./surfaces.js");
|
|
16
17
|
const THEME_OPTIONS = [
|
|
17
18
|
{ value: "light", label: "Light", icon: lucide_react_1.Sun },
|
|
18
19
|
{ value: "dark", label: "Dark", icon: lucide_react_1.Moon },
|
|
@@ -26,6 +27,20 @@ function initials(name, email) {
|
|
|
26
27
|
function openExternal(url) {
|
|
27
28
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The member's photo, with their initials underneath it.
|
|
32
|
+
*
|
|
33
|
+
* Both halves always render, which is the whole trick: `<Avatar.Fallback>` is
|
|
34
|
+
* what shows while `src` is still loading and what stays if it 404s or the
|
|
35
|
+
* user has no photo at all, so there is no `photoUrl &&` branch anywhere and
|
|
36
|
+
* no flash of an empty circle on a cold cache. This component previously
|
|
37
|
+
* rendered *only* the fallback - `ConnectUser` carried no photo field at all,
|
|
38
|
+
* so every Connect account showed initials no matter what picture the member
|
|
39
|
+
* had set on their profile.
|
|
40
|
+
*/
|
|
41
|
+
function UserAvatar({ user, size, }) {
|
|
42
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Avatar, { size: size, color: "accent", variant: "soft", children: [user.photoUrl && (0, jsx_runtime_1.jsx)(react_1.Avatar.Image, { alt: "", src: user.photoUrl }), (0, jsx_runtime_1.jsx)(react_1.Avatar.Fallback, { children: initials(user.displayName, user.email) })] }));
|
|
43
|
+
}
|
|
29
44
|
/** `dispatch_admin` / `billing-viewer` -> `Dispatch admin`. */
|
|
30
45
|
function humanizeRole(role) {
|
|
31
46
|
const words = role.replace(/[_-]+/g, " ").trim();
|
|
@@ -66,9 +81,17 @@ function subscriptionChip(subscription) {
|
|
|
66
81
|
* `w-full` is load-bearing, not decoration: HeroUI's `.button` base is
|
|
67
82
|
* `w-fit`, so a tile dropped into a grid cell shrinks to its own label and
|
|
68
83
|
* the three of them come out ragged and unequal.
|
|
84
|
+
*
|
|
85
|
+
* On the box: `p-3` all round rather than the `px-2 py-4` this used to carry.
|
|
86
|
+
* That old pair was both lopsided (16px of vertical air against 8px of
|
|
87
|
+
* horizontal) and too tall for what it held, so a 16px glyph sat marooned in
|
|
88
|
+
* a ~92px tile. Even padding plus the shared `size-10`/`size-5` medallion
|
|
89
|
+
* (see `MEDALLION`) puts the icon at half the well instead of a stranded
|
|
90
|
+
* 44%, and takes the tile down to ~88px while making its contents read
|
|
91
|
+
* bigger, not smaller.
|
|
69
92
|
*/
|
|
70
93
|
function DestinationTile({ icon, label, url, }) {
|
|
71
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", "aria-label": `${label} (opens in a new tab)`, className:
|
|
94
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", "aria-label": `${label} (opens in a new tab)`, className: `relative h-auto w-full flex-col items-center justify-center gap-2 rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} p-3 hover:border-accent hover:bg-surface-secondary`, onPress: () => openExternal(url), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "absolute end-2 top-2 size-3.5 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { className: `flex ${surfaces_js_1.MEDALLION} items-center justify-center rounded-xl bg-surface text-muted`, children: icon }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-foreground", children: label })] }));
|
|
72
95
|
}
|
|
73
96
|
/**
|
|
74
97
|
* Avatar and name in the trigger; an identity card, the three Connect
|
|
@@ -82,7 +105,7 @@ function DestinationTile({ icon, label, url, }) {
|
|
|
82
105
|
* in place, which is why it sits alone in the footer rather than as a fourth
|
|
83
106
|
* item in the same list as three navigations.
|
|
84
107
|
*
|
|
85
|
-
* The session list is account-wide, not scoped to this
|
|
108
|
+
* The session list is account-wide, not scoped to this application - see
|
|
86
109
|
* `ConnectUserSession`'s doc comment in `types.ts`. Appearance only renders
|
|
87
110
|
* when both `theme` and `onThemeChange` are supplied.
|
|
88
111
|
*
|
|
@@ -107,13 +130,17 @@ function ConnectUserMenu({ compact = false, onSignOut, className, theme, onTheme
|
|
|
107
130
|
if (!user)
|
|
108
131
|
return null;
|
|
109
132
|
const label = user.displayName ?? user.email ?? "Account";
|
|
110
|
-
const role = session?.
|
|
133
|
+
const role = session?.applicationRole?.role;
|
|
111
134
|
const planChip = subscriptionChip(session?.subscription ?? null);
|
|
112
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Modal, { children: [(0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", "aria-label": `Account: ${label}`,
|
|
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
|
+
// `pl-1` against the default right padding is optical, not sloppy: a
|
|
137
|
+
// circular avatar needs less inset than a text label to look evenly
|
|
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.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) => {
|
|
113
140
|
const [next] = [...keys];
|
|
114
141
|
if (typeof next === "string")
|
|
115
142
|
onThemeChange(next);
|
|
116
|
-
}, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => ((0, jsx_runtime_1.jsxs)(react_1.ToggleButton, { id: value, children: [index > 0 && (0, jsx_runtime_1.jsx)(react_1.ToggleButtonGroup.Separator, {}), (0, jsx_runtime_1.jsx)(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), (0, jsx_runtime_1.jsx)(
|
|
143
|
+
}, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => ((0, jsx_runtime_1.jsxs)(react_1.ToggleButton, { id: value, children: [index > 0 && (0, jsx_runtime_1.jsx)(react_1.ToggleButtonGroup.Separator, {}), (0, jsx_runtime_1.jsx)(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), (0, jsx_runtime_1.jsx)(account_sessions_js_1.ConnectAccountSessions, {})] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Footer, { children: (0, jsx_runtime_1.jsxs)(react_1.Button, { slot: "close", fullWidth: true, variant: "tertiary", className: "text-danger", onPress: () => {
|
|
117
144
|
void signOut().then(onSignOut);
|
|
118
145
|
}, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { className: "size-4 shrink-0" }), "Sign out"] }) })] }) }) })] }));
|
|
119
146
|
}
|
|
@@ -130,7 +157,7 @@ function ConnectManageSubscriptionButton({ className }) {
|
|
|
130
157
|
* Invoices and payment history, on Connect.
|
|
131
158
|
*
|
|
132
159
|
* A deep link rather than a component that lists them, and that is not a
|
|
133
|
-
* placeholder: nothing in `/api/v1/
|
|
160
|
+
* placeholder: nothing in `/api/v1/application/*` exposes invoices, so an SDK-side
|
|
134
161
|
* list would have nothing to read. It is also the right answer regardless -
|
|
135
162
|
* a GST tax invoice is a legal document with a fixed format, downloadable and
|
|
136
163
|
* emailed from one place, and a partner-rendered copy of one would be a second
|