@growth-operations/ui-shared 1.13.1 → 1.14.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/package.json +1 -1
- package/src/billing/BillingBanner.jsx +88 -0
- package/src/home/CreditMeter.jsx +34 -9
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Flex, Text, Alert, Button } from "@hubspot/ui-extensions";
|
|
3
|
+
|
|
4
|
+
// Shared app-wide billing banner for the credit archetype (CreditState.
|
|
5
|
+
// to_entitlement's credits arm). Meant to sit in the app's layout/shell
|
|
6
|
+
// (rendered above every route), NOT inside the Billing tab itself — it's the
|
|
7
|
+
// "surface this everywhere, not just on the Billing page" nudge.
|
|
8
|
+
//
|
|
9
|
+
// A paid plan is never blocked or "running low" the way the free tier is —
|
|
10
|
+
// exceeding the included bucket is overage, not an emergency (see
|
|
11
|
+
// common.entitlements.credits.to_entitlement's `over_included` field). Only
|
|
12
|
+
// the free tier gets the blocking/urgent banners; a paid account in overage
|
|
13
|
+
// gets a calm informational one instead.
|
|
14
|
+
//
|
|
15
|
+
// Originally copy-pasted per app (a local `billingBannerFor` in each
|
|
16
|
+
// Pages.jsx) — moved here after the SAME bug (independently re-deriving
|
|
17
|
+
// urgency from raw `remaining`, no plan-awareness) had to be hand-fixed in
|
|
18
|
+
// 3 places (confirmed live 2026-09-02, file-attachment-manager: a paid
|
|
19
|
+
// account correctly kept working while this banner still showed the
|
|
20
|
+
// blocking "out of credits" copy). Consume this instead of re-implementing
|
|
21
|
+
// it — the next app in the wave should need zero new banner code.
|
|
22
|
+
//
|
|
23
|
+
// Props:
|
|
24
|
+
// state — the /v1/home payload (entitlement union). Renders nothing
|
|
25
|
+
// outside the credits archetype or before install completes.
|
|
26
|
+
// actionLabel — short gerund phrase for the depleted message, e.g.
|
|
27
|
+
// "attaching and updating files" / "completing, reassigning,
|
|
28
|
+
// and deleting tasks". Falls back to "going" if omitted.
|
|
29
|
+
// actionsNoun — short noun phrase for the running-low message, e.g.
|
|
30
|
+
// "file actions" / "task actions". Falls back to "actions".
|
|
31
|
+
// onNavigate — in-app PageLink-equivalent callback (the host owns the
|
|
32
|
+
// router — see ui-shared's OnboardingChecklist for the same
|
|
33
|
+
// pattern). Renders a "Go to Billing" button when provided;
|
|
34
|
+
// omit it to render the banner with no button.
|
|
35
|
+
export function BillingBanner({ state, actionLabel, actionsNoun, onNavigate }) {
|
|
36
|
+
const ent = state?.entitlement;
|
|
37
|
+
if (!ent || ent.mode !== "credits" || ent.status === "not_installed") {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const onPaidPlan = !!ent.plan;
|
|
42
|
+
let banner = null;
|
|
43
|
+
|
|
44
|
+
if (!onPaidPlan && (ent.depleted === true || ent.entitled === false)) {
|
|
45
|
+
banner = {
|
|
46
|
+
variant: "error",
|
|
47
|
+
title: "You're out of credits",
|
|
48
|
+
message: `Your credits are used up. Choose a plan in Billing to keep ${actionLabel ?? "going"}.`,
|
|
49
|
+
};
|
|
50
|
+
} else if (onPaidPlan && ent.over_included === true) {
|
|
51
|
+
banner = {
|
|
52
|
+
variant: "warning",
|
|
53
|
+
title: "You've used your included credits",
|
|
54
|
+
message:
|
|
55
|
+
"Extra usage this period is billed as overage at your plan's per-credit rate.",
|
|
56
|
+
};
|
|
57
|
+
} else if (
|
|
58
|
+
!onPaidPlan &&
|
|
59
|
+
typeof ent.remaining === "number" &&
|
|
60
|
+
typeof ent.low_threshold === "number" &&
|
|
61
|
+
ent.remaining <= ent.low_threshold
|
|
62
|
+
) {
|
|
63
|
+
banner = {
|
|
64
|
+
variant: "warning",
|
|
65
|
+
title: "Running low on credits",
|
|
66
|
+
message: `You're almost out of credits. Pick a plan in Billing so your ${
|
|
67
|
+
actionsNoun ?? "actions"
|
|
68
|
+
} don't get interrupted.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!banner) return null;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Alert title={banner.title} variant={banner.variant}>
|
|
76
|
+
<Flex direction="column" gap="extra-small">
|
|
77
|
+
<Text>{banner.message}</Text>
|
|
78
|
+
{onNavigate && (
|
|
79
|
+
<Button variant="secondary" onClick={() => onNavigate("/billing")}>
|
|
80
|
+
Go to Billing →
|
|
81
|
+
</Button>
|
|
82
|
+
)}
|
|
83
|
+
</Flex>
|
|
84
|
+
</Alert>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default BillingBanner;
|
package/src/home/CreditMeter.jsx
CHANGED
|
@@ -24,14 +24,31 @@ export function CreditMeter({ entitlement, creditMeter }) {
|
|
|
24
24
|
const used = meter?.used ?? ent?.used ?? 0;
|
|
25
25
|
const remaining = meter?.remaining ?? ent?.remaining ?? Math.max(granted - used, 0);
|
|
26
26
|
|
|
27
|
+
// A paid plan is NEVER blocked — exceeding the included bucket is overage,
|
|
28
|
+
// not depletion (action_allowed_by_portal/consume_credits never gate a
|
|
29
|
+
// paid plan; see common.entitlements.credits.to_entitlement). So `depleted`
|
|
30
|
+
// must not fall back to raw `remaining <= 0` on a paid plan the way it does
|
|
31
|
+
// on the free tier — that raw check is exactly what showed a paying,
|
|
32
|
+
// already-subscribed customer a blocking "Depleted"/"out of credits" panel
|
|
33
|
+
// (confirmed live 2026-09-02). `over_included` is the distinct paid-plan
|
|
34
|
+
// signal for "you've used your included credits, extra usage is overage".
|
|
35
|
+
const onPaidPlan = !!ent?.plan;
|
|
27
36
|
const lowThreshold = ent?.low_threshold ?? 0;
|
|
28
|
-
const depleted = ent?.depleted === true || remaining <= 0;
|
|
29
|
-
const
|
|
37
|
+
const depleted = !onPaidPlan && (ent?.depleted === true || remaining <= 0);
|
|
38
|
+
const overIncluded =
|
|
39
|
+
onPaidPlan && (ent?.over_included === true || used > granted);
|
|
40
|
+
const low = !depleted && !overIncluded && remaining <= lowThreshold;
|
|
30
41
|
|
|
31
|
-
// healthy green / low yellow / depleted red.
|
|
32
|
-
const variant = depleted ? "error" : low ? "warning" : "success";
|
|
33
|
-
const tagVariant = depleted ? "danger" : low ? "warning" : "success";
|
|
34
|
-
const tagLabel = depleted
|
|
42
|
+
// healthy green / low or overage yellow / depleted red.
|
|
43
|
+
const variant = depleted ? "error" : overIncluded || low ? "warning" : "success";
|
|
44
|
+
const tagVariant = depleted ? "danger" : overIncluded || low ? "warning" : "success";
|
|
45
|
+
const tagLabel = depleted
|
|
46
|
+
? "Depleted"
|
|
47
|
+
: overIncluded
|
|
48
|
+
? "Over included"
|
|
49
|
+
: low
|
|
50
|
+
? "Running low"
|
|
51
|
+
: "Healthy";
|
|
35
52
|
|
|
36
53
|
const grantDaysLeft = ent?.grant_expires_at ? daysUntil(ent.grant_expires_at) : null;
|
|
37
54
|
|
|
@@ -65,15 +82,23 @@ export function CreditMeter({ entitlement, creditMeter }) {
|
|
|
65
82
|
</Text>
|
|
66
83
|
)}
|
|
67
84
|
|
|
68
|
-
{(depleted || low) && (
|
|
85
|
+
{(depleted || overIncluded || low) && (
|
|
69
86
|
<Alert
|
|
70
|
-
title={
|
|
87
|
+
title={
|
|
88
|
+
depleted
|
|
89
|
+
? "You're out of credits"
|
|
90
|
+
: overIncluded
|
|
91
|
+
? "You've used your included credits"
|
|
92
|
+
: "Running low on credits"
|
|
93
|
+
}
|
|
71
94
|
variant={variant}
|
|
72
95
|
>
|
|
73
96
|
<Text>
|
|
74
97
|
{depleted
|
|
75
98
|
? "Pick a plan or buy more credits to keep going."
|
|
76
|
-
:
|
|
99
|
+
: overIncluded
|
|
100
|
+
? "Extra usage this period is billed as overage at your plan's per-credit rate."
|
|
101
|
+
: "You're getting close to your limit — pick a plan to top up."}
|
|
77
102
|
</Text>
|
|
78
103
|
</Alert>
|
|
79
104
|
)}
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { HelpfulLinks } from "./home/HelpfulLinks";
|
|
|
17
17
|
// Billing.
|
|
18
18
|
export { BillingTab } from "./billing/BillingTab";
|
|
19
19
|
export { PlanGrid } from "./billing/PlanGrid";
|
|
20
|
+
export { BillingBanner } from "./billing/BillingBanner";
|
|
20
21
|
|
|
21
22
|
// Alerts — the shared customer alerts table (level filter, sort, dismiss).
|
|
22
23
|
export { AlertsTab } from "./alerts/AlertsTab";
|