@growth-operations/ui-shared 1.6.3 → 1.6.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-operations/ui-shared",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "Shared HubSpot UI-extension modules (Home, Billing) for Growth Operations apps.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -180,6 +180,22 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
180
180
  `&return_url=${encodeURIComponent(returnUrl)}`
181
181
  : null;
182
182
 
183
+ // Upgrade-only tier picker — shown ONLY while trialing (the lifecycle: trial
184
+ // starts on the entry tier at install; while trialing the customer may upgrade
185
+ // to a higher tier; once active, plan changes go through support, so no picker).
186
+ // The cards target /v1/billing/upgrade/start (swap the trialing sub's item, no
187
+ // new sub). "Higher tiers" = plans ranked above the current tier by tier_order;
188
+ // the backend stamps Plan.current on the account's tier.
189
+ const isTrialing = ent.status === "trialing";
190
+ const plans = state?.plans ?? [];
191
+ const currentOrder = plans.find((p) => p.current)?.tier_order;
192
+ const upgradePlans =
193
+ isTrialing && currentOrder != null
194
+ ? plans.filter(
195
+ (p) => !p.talk_to_sales && p.tier_order != null && p.tier_order > currentOrder
196
+ )
197
+ : [];
198
+
183
199
  return (
184
200
  <Flex direction="column" gap="medium">
185
201
  {/* PRIMARY CTA at the top. Direct external link to the billing-service
@@ -198,6 +214,22 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
198
214
  Billing is managed in Stripe across all Growth Operations apps. Use the
199
215
  link above to update your plan, payment method, or view invoices.
200
216
  </Text>
217
+
218
+ {/* Trial-only upgrade path. Hidden once active (no plans pass the filter
219
+ when not trialing). Upgrading swaps the trialing sub onto the higher
220
+ tier, keeping the trial end date. */}
221
+ {upgradePlans.length > 0 && (
222
+ <PlanGrid
223
+ context={context}
224
+ state={state}
225
+ appKey={appKey}
226
+ plans={upgradePlans}
227
+ endpoint="upgrade/start"
228
+ ctaLabel="Upgrade to"
229
+ heading="Upgrade your plan"
230
+ footnote="Upgrade any time during your trial — your trial end date stays the same, and the new tier applies when it converts."
231
+ />
232
+ )}
201
233
  </Flex>
202
234
  );
203
235
  }
@@ -48,6 +48,12 @@ 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",
51
57
  }) {
52
58
  // Default this card to annual when it offers annual (cheaper-per-month story);
53
59
  // monthly-only tiers default to monthly. Both legs → show the toggle.
@@ -80,7 +86,7 @@ function PlanCard({
80
86
  // price) — the button is disabled until then.
81
87
  const startUrl =
82
88
  billingBaseUrl && appKey && portalId && leg?.price_id
83
- ? `${billingBaseUrl}/v1/billing/checkout/start` +
89
+ ? `${billingBaseUrl}/v1/billing/${endpoint}` +
84
90
  `?app_key=${encodeURIComponent(appKey)}` +
85
91
  `&portal_id=${encodeURIComponent(portalId)}` +
86
92
  `&price_id=${encodeURIComponent(leg.price_id)}` +
@@ -175,7 +181,7 @@ function PlanCard({
175
181
  disabled={!startUrl}
176
182
  variant="primary"
177
183
  >
178
- Choose {plan.name ?? plan.tier}
184
+ {ctaLabel} {plan.name ?? plan.tier}
179
185
  </Button>
180
186
  )}
181
187
  </Flex>
@@ -183,8 +189,21 @@ function PlanCard({
183
189
  );
184
190
  }
185
191
 
186
- export function PlanGrid({ context, state, appKey }) {
187
- const plans = state?.plans ?? [];
192
+ export function PlanGrid({
193
+ context,
194
+ state,
195
+ appKey,
196
+ // Customization for non-default uses. The credit-app first-purchase grid uses
197
+ // all defaults; the trial-archetype in-app UPGRADE picker passes:
198
+ // plans={higherTiersOnly} endpoint="upgrade/start" ctaLabel="Upgrade to"
199
+ // heading="Upgrade your plan" footnote={trial copy}
200
+ plans: plansOverride,
201
+ endpoint = "checkout/start",
202
+ ctaLabel = "Choose",
203
+ heading = "Plans",
204
+ footnote = "Billing is managed in Stripe across all Growth Operations apps. Your new credits are added as soon as payment completes.",
205
+ }) {
206
+ const plans = plansOverride ?? state?.plans ?? [];
188
207
 
189
208
  if (plans.length === 0) return null;
190
209
 
@@ -205,7 +224,7 @@ export function PlanGrid({ context, state, appKey }) {
205
224
 
206
225
  return (
207
226
  <Flex direction="column" gap="medium">
208
- <Heading>Plans</Heading>
227
+ <Heading>{heading}</Heading>
209
228
 
210
229
  {/* AutoGrid(flexible): equal-width columns that wrap responsively. Card
211
230
  height is equalized via the maxFeatures padding in PlanCard. Each card
@@ -221,14 +240,13 @@ export function PlanGrid({ context, state, appKey }) {
221
240
  returnUrl={returnUrl}
222
241
  supportUrl={supportUrl}
223
242
  maxFeatures={maxFeatures}
243
+ endpoint={endpoint}
244
+ ctaLabel={ctaLabel}
224
245
  />
225
246
  ))}
226
247
  </AutoGrid>
227
248
 
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>
249
+ <Text format={{ fontStyle: "italic" }}>{footnote}</Text>
232
250
  </Flex>
233
251
  );
234
252
  }