@growth-operations/ui-shared 1.6.5 → 1.6.7

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.5",
3
+ "version": "1.6.7",
4
4
  "description": "Shared HubSpot UI-extension modules (Home, Billing) for Growth Operations apps.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -138,7 +138,7 @@ function StatusPanel({ entitlement }) {
138
138
  );
139
139
  }
140
140
 
141
- function TrialSubscriptionBilling({ context, state, appKey }) {
141
+ function TrialSubscriptionBilling({ context, state, appKey, openIframe = null }) {
142
142
  const ent = state?.entitlement;
143
143
 
144
144
  // Install not provisioned with a Stripe customer yet — calm "finish setup"
@@ -232,13 +232,14 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
232
232
  ctaLabel="Upgrade to"
233
233
  heading="Your plan"
234
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
+ openIframe={openIframe}
235
236
  />
236
237
  )}
237
238
  </Flex>
238
239
  );
239
240
  }
240
241
 
241
- function CreditsBilling({ context, state, appKey }) {
242
+ function CreditsBilling({ context, state, appKey, openIframe = null }) {
242
243
  const onPaidPlan = !!state?.entitlement?.plan;
243
244
 
244
245
  // Direct external link to the billing service's GET /v1/billing/portal/start,
@@ -301,7 +302,7 @@ function CreditsBilling({ context, state, appKey }) {
301
302
  entitlement={state?.entitlement}
302
303
  creditMeter={state?.credit_meter}
303
304
  />
304
- <PlanGrid context={context} state={state} appKey={appKey} />
305
+ <PlanGrid context={context} state={state} appKey={appKey} openIframe={openIframe} />
305
306
  {!hasPlans && (
306
307
  <LoadingButton
307
308
  href={
@@ -345,7 +346,21 @@ function LegacyBilling({ state }) {
345
346
  );
346
347
  }
347
348
 
348
- export function BillingTab({ context, state, appKey }) {
349
+ function CompedBilling() {
350
+ return (
351
+ <Alert variant="success">
352
+ <Flex direction="column" gap="extra-small">
353
+ <Text format={{ fontWeight: "bold" }}>Free access</Text>
354
+ <Text>
355
+ Your account has been granted complimentary access as a Growth Operations partner or
356
+ team member. No payment method or subscription is required.
357
+ </Text>
358
+ </Flex>
359
+ </Alert>
360
+ );
361
+ }
362
+
363
+ export function BillingTab({ context, state, appKey, openIframe = null }) {
349
364
  const mode = state?.entitlement?.mode;
350
365
 
351
366
  return (
@@ -354,9 +369,11 @@ export function BillingTab({ context, state, appKey }) {
354
369
  {mode === "legacy" ? (
355
370
  <LegacyBilling state={state} />
356
371
  ) : mode === "credits" ? (
357
- <CreditsBilling context={context} state={state} appKey={appKey} />
372
+ <CreditsBilling context={context} state={state} appKey={appKey} openIframe={openIframe} />
373
+ ) : mode === "comped" ? (
374
+ <CompedBilling />
358
375
  ) : (
359
- <TrialSubscriptionBilling context={context} state={state} appKey={appKey} />
376
+ <TrialSubscriptionBilling context={context} state={state} appKey={appKey} openIframe={openIframe} />
360
377
  )}
361
378
  </Flex>
362
379
  );
@@ -60,6 +60,13 @@ function PlanCard({
60
60
  // CTA. Cards ranked ABOVE are upgrades (the normal CTA). null/undefined => the
61
61
  // credit first-purchase grid, where this relation doesn't apply.
62
62
  currentOrder = null,
63
+ // actions.openIframeModal from the card root (threaded BillingTab → PlanGrid).
64
+ // When present, a talk_to_sales tier with plan.meetings_url opens the meetings
65
+ // scheduler in an IN-CARD modal (the Anvil "Meet with Andrew" experience).
66
+ // Absent (or no meetings_url) → fall back to a new-tab href. UI Extensions
67
+ // can't read whether the external meetings page is CSP-frameable, so the modal
68
+ // is best-effort; the href fallback always works.
69
+ openIframe = null,
63
70
  }) {
64
71
  // Default this card to annual when it offers annual (cheaper-per-month story);
65
72
  // monthly-only tiers default to monthly. Both legs → show the toggle.
@@ -184,13 +191,39 @@ function PlanCard({
184
191
  Talk to sales to switch
185
192
  </Button>
186
193
  ) : plan.talk_to_sales ? (
187
- <Button
188
- href={supportUrl ? { url: supportUrl, external: true } : undefined}
189
- disabled={!supportUrl}
190
- variant="secondary"
191
- >
192
- Contact sales
193
- </Button>
194
+ // Talk-to-sales tier (e.g. Enterprise+): no price/checkout. If we have
195
+ // a meetings link AND the openIframeModal action, open the scheduler in
196
+ // an in-card modal (Anvil "Meet with Andrew"). Otherwise a new-tab href
197
+ // to the meetings link (preferred) or support page.
198
+ openIframe && plan.meetings_url ? (
199
+ <Button
200
+ variant="secondary"
201
+ onClick={() =>
202
+ openIframe({
203
+ uri: plan.meetings_url,
204
+ title: `Talk to sales — ${plan.name ?? plan.tier}`,
205
+ width: 1100,
206
+ height: 760,
207
+ })
208
+ }
209
+ >
210
+ Talk to sales
211
+ </Button>
212
+ ) : (
213
+ <Button
214
+ href={
215
+ plan.meetings_url
216
+ ? { url: plan.meetings_url, external: true }
217
+ : supportUrl
218
+ ? { url: supportUrl, external: true }
219
+ : undefined
220
+ }
221
+ disabled={!plan.meetings_url && !supportUrl}
222
+ variant="secondary"
223
+ >
224
+ Talk to sales
225
+ </Button>
226
+ )
194
227
  ) : !leg ? (
195
228
  // No price for the selected interval (e.g. annual not offered) — guide
196
229
  // the customer to the other toggle rather than dead-ending.
@@ -232,6 +265,9 @@ export function PlanGrid({
232
265
  // Trial-picker only: tier_order of the current tier, so cards below it render
233
266
  // as disabled downgrades (Talk-to-sales). Omitted for the credit grid.
234
267
  currentOrder = null,
268
+ // actions.openIframeModal (from the hosting card root) — lets a talk_to_sales
269
+ // tier open its meetings_url scheduler in an in-card modal. Optional.
270
+ openIframe = null,
235
271
  }) {
236
272
  const plans = plansOverride ?? state?.plans ?? [];
237
273
 
@@ -273,6 +309,7 @@ export function PlanGrid({
273
309
  endpoint={endpoint}
274
310
  ctaLabel={ctaLabel}
275
311
  currentOrder={currentOrder}
312
+ openIframe={openIframe}
276
313
  />
277
314
  ))}
278
315
  </AutoGrid>
@@ -50,6 +50,17 @@ export function HealthHero({ appName, state }) {
50
50
  ) : null;
51
51
  }
52
52
 
53
+ if (ent.mode === "comped") {
54
+ return (
55
+ <Flex direction="column" gap="small">
56
+ {name && <Heading>{name}</Heading>}
57
+ <Alert title="Free access" variant="success">
58
+ <Text>Partner or team member access — no subscription required.</Text>
59
+ </Alert>
60
+ </Flex>
61
+ );
62
+ }
63
+
53
64
  if (ent.mode === "credits") {
54
65
  const remaining = ent.remaining ?? 0;
55
66
  const depleted = ent.depleted === true || remaining <= 0;