@growth-operations/ui-shared 1.17.2 → 1.18.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/home/CreditMeter.jsx +69 -36
- package/src/lib/creditDisplay.js +39 -7
package/package.json
CHANGED
package/src/home/CreditMeter.jsx
CHANGED
|
@@ -9,16 +9,27 @@ import { resolveCreditMeter } from "../lib/creditDisplay";
|
|
|
9
9
|
// `credit_meter` block (CONTRACT.md). Renders nothing when there is no credit
|
|
10
10
|
// data to show.
|
|
11
11
|
//
|
|
12
|
-
// All branch math (number precedence, threshold_state → tag/alert
|
|
13
|
-
// pool+bank split) lives in ../lib/creditDisplay so it's
|
|
14
|
-
// component is pure rendering.
|
|
12
|
+
// All branch math (number precedence, threshold_state → tag/alert/bar
|
|
13
|
+
// variants, the pool+bank split) lives in ../lib/creditDisplay so it's
|
|
14
|
+
// unit-testable — this component is pure rendering.
|
|
15
15
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// pool+bank denominator
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
16
|
+
// Stacked-bars bank display: an account with top-up bank credits left (topup
|
|
17
|
+
// billing model) gets TWO bars, one per bucket, and NO combined headline (the
|
|
18
|
+
// combined pool+bank denominator read as one big confusing allowance):
|
|
19
|
+
// 1. Monthly plan credits — a DEPLETION meter (value = pool used of the
|
|
20
|
+
// period grant), color-coded by the account's depletion state: success
|
|
21
|
+
// when healthy, warning at/below the low threshold, danger when
|
|
22
|
+
// depleted (view.barVariant).
|
|
23
|
+
// 2. Additional credits — a LEVEL gauge (value = bank remaining of the
|
|
24
|
+
// bank's granted total), always success: the bank is cumulative and
|
|
25
|
+
// never expires, so a used-of-granted bar would drift to
|
|
26
|
+
// permanently-near-full over multiple purchases; the remaining level
|
|
27
|
+
// refills on purchase and drains on spend.
|
|
28
|
+
// Hosts whose backend predates top_up_bank_granted degrade gracefully: the
|
|
29
|
+
// bank renders as a text-only line (no bar, never "of 0"). An account with
|
|
30
|
+
// NO bank credits left (never topped up, or the bank is spent) renders
|
|
31
|
+
// exactly the legacy view — the "{remaining} of {granted} credits left"
|
|
32
|
+
// headline plus the single color-coded bar.
|
|
22
33
|
//
|
|
23
34
|
// Props:
|
|
24
35
|
// entitlement — the /v1/home entitlement union (uses the credits arm).
|
|
@@ -27,7 +38,7 @@ export function CreditMeter({ entitlement, creditMeter }) {
|
|
|
27
38
|
const view = resolveCreditMeter(entitlement, creditMeter);
|
|
28
39
|
if (!view) return null;
|
|
29
40
|
|
|
30
|
-
const { granted, used, remaining, bank, tagVariant, tagLabel, alert } = view;
|
|
41
|
+
const { granted, used, remaining, bank, tagVariant, tagLabel, barVariant, alert } = view;
|
|
31
42
|
const grantDaysLeft = view.grantExpiresAt ? daysUntil(view.grantExpiresAt) : null;
|
|
32
43
|
|
|
33
44
|
return (
|
|
@@ -38,42 +49,64 @@ export function CreditMeter({ entitlement, creditMeter }) {
|
|
|
38
49
|
<StatusTag variant={tagVariant}>{tagLabel}</StatusTag>
|
|
39
50
|
</Flex>
|
|
40
51
|
|
|
41
|
-
{/* PRIMARY signal — large and obvious. */}
|
|
42
|
-
<Text format={{ fontWeight: "bold", fontSize: "lg" }}>
|
|
43
|
-
{remaining} of {granted} credits left
|
|
44
|
-
</Text>
|
|
45
|
-
|
|
46
52
|
{bank ? (
|
|
47
53
|
<>
|
|
48
|
-
{/*
|
|
49
|
-
denominator (used = granted - remaining). The HubSpot
|
|
50
|
-
ProgressBar is single-value, so the two segments are the
|
|
51
|
-
labeled breakdown below; this bar is the position within the
|
|
52
|
-
whole. */}
|
|
53
|
-
<ProgressBar
|
|
54
|
-
title={`${used} of ${granted} used`}
|
|
55
|
-
value={used}
|
|
56
|
-
maxValue={granted > 0 ? granted : 1}
|
|
57
|
-
showPercentage={true}
|
|
58
|
-
/>
|
|
54
|
+
{/* Pool — depletion meter over the period grant. */}
|
|
59
55
|
<Flex direction="column" gap="extra-small">
|
|
60
56
|
<Text>
|
|
61
57
|
Monthly plan credits: {bank.poolRemaining} of {bank.poolGrant}{" "}
|
|
62
58
|
left
|
|
63
59
|
</Text>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
<ProgressBar
|
|
61
|
+
title={`${bank.poolUsed} of ${bank.poolGrant} used`}
|
|
62
|
+
value={bank.poolUsed}
|
|
63
|
+
maxValue={bank.poolGrant > 0 ? bank.poolGrant : 1}
|
|
64
|
+
showPercentage={true}
|
|
65
|
+
variant={barVariant}
|
|
66
|
+
/>
|
|
68
67
|
</Flex>
|
|
68
|
+
{bank.bankGranted > 0 ? (
|
|
69
|
+
// Bank — level gauge over the granted total (refills on
|
|
70
|
+
// purchase, drains on spend; never expires).
|
|
71
|
+
<Flex direction="column" gap="extra-small">
|
|
72
|
+
<Text>
|
|
73
|
+
Additional credits (never expire): {bank.bankRemaining} of{" "}
|
|
74
|
+
{bank.bankGranted} left
|
|
75
|
+
</Text>
|
|
76
|
+
<ProgressBar
|
|
77
|
+
title={`${bank.bankRemaining} of ${bank.bankGranted} left`}
|
|
78
|
+
value={bank.bankRemaining}
|
|
79
|
+
maxValue={bank.bankGranted > 0 ? bank.bankGranted : 1}
|
|
80
|
+
showPercentage={true}
|
|
81
|
+
variant="success"
|
|
82
|
+
/>
|
|
83
|
+
</Flex>
|
|
84
|
+
) : (
|
|
85
|
+
// Graceful degradation: the host's backend predates
|
|
86
|
+
// top_up_bank_granted, so the bank total is unknown — text
|
|
87
|
+
// only, no bar (never divide by zero, never "of 0").
|
|
88
|
+
<Flex direction="column" gap="extra-small">
|
|
89
|
+
<Text>Additional credits: {bank.bankRemaining} left</Text>
|
|
90
|
+
<Text format={{ fontStyle: "italic" }}>
|
|
91
|
+
Additional credits don't expire.
|
|
92
|
+
</Text>
|
|
93
|
+
</Flex>
|
|
94
|
+
)}
|
|
69
95
|
</>
|
|
70
96
|
) : (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
<>
|
|
98
|
+
{/* PRIMARY signal — large and obvious. */}
|
|
99
|
+
<Text format={{ fontWeight: "bold", fontSize: "lg" }}>
|
|
100
|
+
{remaining} of {granted} credits left
|
|
101
|
+
</Text>
|
|
102
|
+
<ProgressBar
|
|
103
|
+
title={`${used} used`}
|
|
104
|
+
value={used}
|
|
105
|
+
maxValue={granted > 0 ? granted : 1}
|
|
106
|
+
showPercentage={true}
|
|
107
|
+
variant={barVariant}
|
|
108
|
+
/>
|
|
109
|
+
</>
|
|
77
110
|
)}
|
|
78
111
|
|
|
79
112
|
{/* Free-grant countdown: "100 free credits — N days left". */}
|
package/src/lib/creditDisplay.js
CHANGED
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
// incident where a paying, overage account was shown a blocking
|
|
27
27
|
// "Depleted"/"out of credits" panel).
|
|
28
28
|
|
|
29
|
-
// True when the account has (or ever had) a top-up bank
|
|
30
|
-
//
|
|
31
|
-
//
|
|
29
|
+
// True when the account has (or ever had) a top-up bank. Note the CreditMeter
|
|
30
|
+
// bank section renders only while the bank HAS credits left (bankRemaining >
|
|
31
|
+
// 0 — see resolveCreditMeter); this predicate stays the looser "ever had one"
|
|
32
|
+
// for consumers that render history/summary surfaces.
|
|
32
33
|
export function hasTopUpBank(ent) {
|
|
33
34
|
if (!ent) return false;
|
|
34
35
|
return (ent.top_up_bank_remaining ?? 0) > 0 || ent.last_top_up_at != null;
|
|
@@ -38,12 +39,25 @@ export function hasTopUpBank(ent) {
|
|
|
38
39
|
// Returns null when there is no credit data to show (component renders
|
|
39
40
|
// nothing). Otherwise:
|
|
40
41
|
// granted/used/remaining — combined (topup) or raw (legacy) display numbers
|
|
41
|
-
// bank — { bankRemaining,
|
|
42
|
-
// when the account has
|
|
42
|
+
// bank — { bankRemaining, bankGranted, poolGrant,
|
|
43
|
+
// poolRemaining, poolUsed } when the account has bank
|
|
44
|
+
// credits LEFT (bankRemaining > 0), else null — a
|
|
45
|
+
// zero bank renders the plain single-pool bar (with
|
|
46
|
+
// bankRemaining 0 the combined numbers ARE the pool
|
|
47
|
+
// numbers). bankGranted is the bank's lifetime
|
|
48
|
+
// granted total (the bank bar's denominator); 0 on
|
|
49
|
+
// backends that predate top_up_bank_granted — the
|
|
50
|
+
// component degrades to a text-only bank line.
|
|
43
51
|
// thresholdState — the arm's threshold_state, or null on a backend
|
|
44
52
|
// that predates it
|
|
45
53
|
// depleted/overIncluded/low — mutually-exclusive urgency flags
|
|
46
54
|
// tagVariant/tagLabel — StatusTag rendering
|
|
55
|
+
// barVariant — ProgressBar sentiment for the pool/single bar:
|
|
56
|
+
// the tag's depletion mapping minus "info"
|
|
57
|
+
// (ProgressBar only takes success/warning/danger;
|
|
58
|
+
// top_up_pending is calm/positive, so success).
|
|
59
|
+
// The bank bar is always "success" (a never-expiring
|
|
60
|
+
// balance has no negative direction).
|
|
47
61
|
// alert — { variant, title, message } or null
|
|
48
62
|
// grantExpiresAt — pass-through for the free-grant countdown
|
|
49
63
|
export function resolveCreditMeter(entitlement, creditMeter) {
|
|
@@ -82,13 +96,23 @@ export function resolveCreditMeter(entitlement, creditMeter) {
|
|
|
82
96
|
// Bank split. Drawdown is monthly-first, so of the combined `remaining` the
|
|
83
97
|
// bank portion is bankRemaining and the rest is pool remaining; the pool
|
|
84
98
|
// segment's full size is the combined denominator minus the bank.
|
|
99
|
+
//
|
|
100
|
+
// The bank section renders ONLY while the bank has credits left
|
|
101
|
+
// (bankRemaining > 0): a zero bank — never topped up, or fully burned —
|
|
102
|
+
// renders the plain single-pool bar, and with bankRemaining 0 the combined
|
|
103
|
+
// numbers ARE the pool numbers (granted == poolGrant, remaining ==
|
|
104
|
+
// poolRemaining), so nothing is lost.
|
|
85
105
|
let bank = null;
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
const bankRemaining = Math.max(0, ent?.top_up_bank_remaining ?? 0);
|
|
107
|
+
if (bankRemaining > 0) {
|
|
88
108
|
const poolGrant = Math.max(granted - bankRemaining, 0);
|
|
89
109
|
const poolRemaining = Math.min(Math.max(remaining - bankRemaining, 0), poolGrant);
|
|
90
110
|
bank = {
|
|
91
111
|
bankRemaining,
|
|
112
|
+
// The bank's lifetime granted total — the bank bar's denominator. 0 on
|
|
113
|
+
// backends that predate top_up_bank_granted (the component degrades to
|
|
114
|
+
// a text-only bank line; never divide by zero, never "of 0").
|
|
115
|
+
bankGranted: Math.max(0, ent?.top_up_bank_granted ?? 0),
|
|
92
116
|
poolGrant,
|
|
93
117
|
poolRemaining,
|
|
94
118
|
poolUsed: poolGrant - poolRemaining,
|
|
@@ -120,6 +144,13 @@ export function resolveCreditMeter(entitlement, creditMeter) {
|
|
|
120
144
|
tagLabel = "Healthy";
|
|
121
145
|
}
|
|
122
146
|
|
|
147
|
+
// ProgressBar sentiment for the pool bar (and the no-bank single bar):
|
|
148
|
+
// color-coded by depletion — success when healthy, warning at/below the
|
|
149
|
+
// low threshold (and for the other warning-level states), danger when
|
|
150
|
+
// depleted/top-up-failed. ProgressBar has no "info" variant, so the calm
|
|
151
|
+
// top_up_pending state renders success.
|
|
152
|
+
const barVariant = tagVariant === "info" ? "success" : tagVariant;
|
|
153
|
+
|
|
123
154
|
// The inline meter alert mirrors the banner's messaging at the point of
|
|
124
155
|
// glance. Legacy branches keep their pre-top-up copy byte-for-byte.
|
|
125
156
|
let alert = null;
|
|
@@ -171,6 +202,7 @@ export function resolveCreditMeter(entitlement, creditMeter) {
|
|
|
171
202
|
low,
|
|
172
203
|
tagVariant,
|
|
173
204
|
tagLabel,
|
|
205
|
+
barVariant,
|
|
174
206
|
alert,
|
|
175
207
|
grantExpiresAt: ent?.grant_expires_at ?? null,
|
|
176
208
|
};
|