@growth-operations/ui-shared 1.6.3 → 1.6.5
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/BillingTab.jsx +36 -0
- package/src/billing/PlanGrid.jsx +61 -12
package/package.json
CHANGED
|
@@ -180,6 +180,23 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
|
|
|
180
180
|
`&return_url=${encodeURIComponent(returnUrl)}`
|
|
181
181
|
: null;
|
|
182
182
|
|
|
183
|
+
// Tier picker — shown ONLY while trialing (the lifecycle: trial starts on the
|
|
184
|
+
// entry tier at install; while trialing the customer may upgrade to a higher
|
|
185
|
+
// tier in-app; once active, plan changes go through support). We show the FULL
|
|
186
|
+
// ladder for context so the customer sees where they sit:
|
|
187
|
+
// current tier -> "Current plan" (disabled marker; PlanCard keys off
|
|
188
|
+
// plan.current, stamped server-side on the account's tier).
|
|
189
|
+
// higher tiers -> the actionable upgrade (CTA targets /v1/billing/upgrade/
|
|
190
|
+
// start, swapping the trialing sub's item — no new sub).
|
|
191
|
+
// lower tiers -> shown disabled with a Talk-to-sales CTA (downgrades aren't
|
|
192
|
+
// self-serve). PlanCard derives this from currentOrder.
|
|
193
|
+
const isTrialing = ent.status === "trialing";
|
|
194
|
+
const plans = state?.plans ?? [];
|
|
195
|
+
const currentOrder = plans.find((p) => p.current)?.tier_order;
|
|
196
|
+
// Render the picker only while trialing AND once we know the current tier
|
|
197
|
+
// (currentOrder drives upgrade-vs-downgrade per card).
|
|
198
|
+
const showPicker = isTrialing && currentOrder != null && plans.length > 0;
|
|
199
|
+
|
|
183
200
|
return (
|
|
184
201
|
<Flex direction="column" gap="medium">
|
|
185
202
|
{/* PRIMARY CTA at the top. Direct external link to the billing-service
|
|
@@ -198,6 +215,25 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
|
|
|
198
215
|
Billing is managed in Stripe across all Growth Operations apps. Use the
|
|
199
216
|
link above to update your plan, payment method, or view invoices.
|
|
200
217
|
</Text>
|
|
218
|
+
|
|
219
|
+
{/* Trial-only tier ladder: current (marked), higher (upgradeable), lower
|
|
220
|
+
(disabled, Talk-to-sales). Hidden once active. Upgrading swaps the
|
|
221
|
+
trialing sub onto the higher tier, keeping the trial end date. The full
|
|
222
|
+
`plans` list is passed; PlanCard decides each card's state from
|
|
223
|
+
plan.current + currentOrder. */}
|
|
224
|
+
{showPicker && (
|
|
225
|
+
<PlanGrid
|
|
226
|
+
context={context}
|
|
227
|
+
state={state}
|
|
228
|
+
appKey={appKey}
|
|
229
|
+
plans={plans}
|
|
230
|
+
currentOrder={currentOrder}
|
|
231
|
+
endpoint="upgrade/start"
|
|
232
|
+
ctaLabel="Upgrade to"
|
|
233
|
+
heading="Your plan"
|
|
234
|
+
footnote="Upgrade any time during your trial — your trial end date stays the same, and the new tier applies when it converts. To move to a lower tier, talk to sales."
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
201
237
|
</Flex>
|
|
202
238
|
);
|
|
203
239
|
}
|
package/src/billing/PlanGrid.jsx
CHANGED
|
@@ -48,12 +48,35 @@ function PlanCard({
|
|
|
48
48
|
returnUrl,
|
|
49
49
|
supportUrl,
|
|
50
50
|
maxFeatures = 0,
|
|
51
|
+
// Billing-service redirect the "Choose" button targets. "checkout/start" (the
|
|
52
|
+
// default) starts a NEW subscription via hosted Checkout — for free/credit
|
|
53
|
+
// first purchase. "upgrade/start" swaps the EXISTING trialing/active sub's item
|
|
54
|
+
// to this tier (the trial-archetype in-app upgrade) — no new sub.
|
|
55
|
+
endpoint = "checkout/start",
|
|
56
|
+
ctaLabel = "Choose",
|
|
57
|
+
// Trial-picker only: tier_order of the customer's current tier. When set, a
|
|
58
|
+
// card ranked BELOW it is a downgrade — shown for context but not self-serve
|
|
59
|
+
// (downgrades go through support), so it renders disabled with a Talk-to-sales
|
|
60
|
+
// CTA. Cards ranked ABOVE are upgrades (the normal CTA). null/undefined => the
|
|
61
|
+
// credit first-purchase grid, where this relation doesn't apply.
|
|
62
|
+
currentOrder = null,
|
|
51
63
|
}) {
|
|
52
64
|
// Default this card to annual when it offers annual (cheaper-per-month story);
|
|
53
65
|
// monthly-only tiers default to monthly. Both legs → show the toggle.
|
|
54
66
|
const hasMonthly = !!plan.monthly;
|
|
55
67
|
const hasAnnual = !!plan.annual;
|
|
56
|
-
|
|
68
|
+
// A lower-ranked tier than the customer's current one: a downgrade. Not
|
|
69
|
+
// self-serve (changes go through support) — show it for context, disabled,
|
|
70
|
+
// with a Talk-to-sales CTA. Only meaningful in the trial picker (currentOrder
|
|
71
|
+
// set); never flag the current tier itself as a downgrade.
|
|
72
|
+
const isDowngrade =
|
|
73
|
+
currentOrder != null &&
|
|
74
|
+
!plan.current &&
|
|
75
|
+
plan.tier_order != null &&
|
|
76
|
+
plan.tier_order < currentOrder;
|
|
77
|
+
// The interval toggle is only useful when the card can be acted on; a current
|
|
78
|
+
// or downgrade card is read-only, so hide its toggle.
|
|
79
|
+
const canToggle = hasMonthly && hasAnnual && !plan.current && !isDowngrade;
|
|
57
80
|
const [interval, setInterval] = useState(hasAnnual ? "annual" : "monthly");
|
|
58
81
|
const leg = interval === "annual" ? plan.annual : plan.monthly;
|
|
59
82
|
const periodLabel = interval === "annual" ? "/yr" : "/mo";
|
|
@@ -80,7 +103,7 @@ function PlanCard({
|
|
|
80
103
|
// price) — the button is disabled until then.
|
|
81
104
|
const startUrl =
|
|
82
105
|
billingBaseUrl && appKey && portalId && leg?.price_id
|
|
83
|
-
? `${billingBaseUrl}/v1/billing
|
|
106
|
+
? `${billingBaseUrl}/v1/billing/${endpoint}` +
|
|
84
107
|
`?app_key=${encodeURIComponent(appKey)}` +
|
|
85
108
|
`&portal_id=${encodeURIComponent(portalId)}` +
|
|
86
109
|
`&price_id=${encodeURIComponent(leg.price_id)}` +
|
|
@@ -146,10 +169,20 @@ function PlanCard({
|
|
|
146
169
|
<Text key={`pad-${i}`}>{" "}</Text>
|
|
147
170
|
))}
|
|
148
171
|
|
|
149
|
-
{/* CTA. Current tier => disabled marker.
|
|
150
|
-
|
|
172
|
+
{/* CTA. Current tier => disabled marker. Downgrade (lower than current,
|
|
173
|
+
trial picker) => disabled + Talk-to-sales (not self-serve).
|
|
174
|
+
talk_to_sales tier => contact link. Otherwise pre-create-then-link
|
|
175
|
+
checkout/upgrade for the selected interval. */}
|
|
151
176
|
{plan.current ? (
|
|
152
177
|
<Button disabled>Current plan</Button>
|
|
178
|
+
) : isDowngrade ? (
|
|
179
|
+
<Button
|
|
180
|
+
href={supportUrl ? { url: supportUrl, external: true } : undefined}
|
|
181
|
+
disabled={!supportUrl}
|
|
182
|
+
variant="secondary"
|
|
183
|
+
>
|
|
184
|
+
Talk to sales to switch
|
|
185
|
+
</Button>
|
|
153
186
|
) : plan.talk_to_sales ? (
|
|
154
187
|
<Button
|
|
155
188
|
href={supportUrl ? { url: supportUrl, external: true } : undefined}
|
|
@@ -175,7 +208,7 @@ function PlanCard({
|
|
|
175
208
|
disabled={!startUrl}
|
|
176
209
|
variant="primary"
|
|
177
210
|
>
|
|
178
|
-
|
|
211
|
+
{ctaLabel} {plan.name ?? plan.tier}
|
|
179
212
|
</Button>
|
|
180
213
|
)}
|
|
181
214
|
</Flex>
|
|
@@ -183,8 +216,24 @@ function PlanCard({
|
|
|
183
216
|
);
|
|
184
217
|
}
|
|
185
218
|
|
|
186
|
-
export function PlanGrid({
|
|
187
|
-
|
|
219
|
+
export function PlanGrid({
|
|
220
|
+
context,
|
|
221
|
+
state,
|
|
222
|
+
appKey,
|
|
223
|
+
// Customization for non-default uses. The credit-app first-purchase grid uses
|
|
224
|
+
// all defaults; the trial-archetype in-app UPGRADE picker passes:
|
|
225
|
+
// plans={higherTiersOnly} endpoint="upgrade/start" ctaLabel="Upgrade to"
|
|
226
|
+
// heading="Upgrade your plan" footnote={trial copy}
|
|
227
|
+
plans: plansOverride,
|
|
228
|
+
endpoint = "checkout/start",
|
|
229
|
+
ctaLabel = "Choose",
|
|
230
|
+
heading = "Plans",
|
|
231
|
+
footnote = "Billing is managed in Stripe across all Growth Operations apps. Your new credits are added as soon as payment completes.",
|
|
232
|
+
// Trial-picker only: tier_order of the current tier, so cards below it render
|
|
233
|
+
// as disabled downgrades (Talk-to-sales). Omitted for the credit grid.
|
|
234
|
+
currentOrder = null,
|
|
235
|
+
}) {
|
|
236
|
+
const plans = plansOverride ?? state?.plans ?? [];
|
|
188
237
|
|
|
189
238
|
if (plans.length === 0) return null;
|
|
190
239
|
|
|
@@ -205,7 +254,7 @@ export function PlanGrid({ context, state, appKey }) {
|
|
|
205
254
|
|
|
206
255
|
return (
|
|
207
256
|
<Flex direction="column" gap="medium">
|
|
208
|
-
<Heading>
|
|
257
|
+
<Heading>{heading}</Heading>
|
|
209
258
|
|
|
210
259
|
{/* AutoGrid(flexible): equal-width columns that wrap responsively. Card
|
|
211
260
|
height is equalized via the maxFeatures padding in PlanCard. Each card
|
|
@@ -221,14 +270,14 @@ export function PlanGrid({ context, state, appKey }) {
|
|
|
221
270
|
returnUrl={returnUrl}
|
|
222
271
|
supportUrl={supportUrl}
|
|
223
272
|
maxFeatures={maxFeatures}
|
|
273
|
+
endpoint={endpoint}
|
|
274
|
+
ctaLabel={ctaLabel}
|
|
275
|
+
currentOrder={currentOrder}
|
|
224
276
|
/>
|
|
225
277
|
))}
|
|
226
278
|
</AutoGrid>
|
|
227
279
|
|
|
228
|
-
<Text format={{ fontStyle: "italic" }}>
|
|
229
|
-
Billing is managed in Stripe across all Growth Operations apps. Your new
|
|
230
|
-
credits are added as soon as payment completes.
|
|
231
|
-
</Text>
|
|
280
|
+
<Text format={{ fontStyle: "italic" }}>{footnote}</Text>
|
|
232
281
|
</Flex>
|
|
233
282
|
);
|
|
234
283
|
}
|