@growth-operations/ui-shared 1.4.0 → 1.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-operations/ui-shared",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Shared HubSpot UI-extension modules (Home, Billing) for Growth Operations apps.",
5
5
  "license": "MIT",
6
6
  "private": false,
package/src/index.js CHANGED
@@ -24,6 +24,9 @@ export { AlertsTab } from "./alerts/AlertsTab";
24
24
  // Usage — the shared credit usage view (per-day LineChart + window stats).
25
25
  export { UsageTab } from "./usage/UsageTab";
26
26
 
27
+ // Sandbox — the shared "Install on a sandbox" action (mints a signed link).
28
+ export { SandboxInstall } from "./sandbox/SandboxInstall";
29
+
27
30
  // Lib helpers.
28
31
  export {
29
32
  fmtDate,
@@ -0,0 +1,89 @@
1
+ // SandboxInstall — the shared "Install on a sandbox" action.
2
+ //
3
+ // The one sanctioned path to a sandbox install (a customer can't self-install on
4
+ // a sandbox from the marketplace — the backend rejects that). From a prod
5
+ // portal, this asks the app's mint endpoint for a signed sandbox install link
6
+ // (which carries sandbox=true + the prod customer to link), then surfaces it for
7
+ // the user to open on their sandbox account.
8
+ //
9
+ // Extensible per app: the caller passes the mint `path` on its OWN backend
10
+ // (base-hosted apps -> /v1/hubspot/app_pages/... no; the install mint lives at
11
+ // /v2/hubspot/install/{app}/sandbox-link on base; self-hosted apps serve their
12
+ // own), plus the portalId. callAppApi targets context.variables.BASE_URL, so
13
+ // each app's BASE_URL routes to the right service. Apps not allowlisted get a
14
+ // 403 from the endpoint, surfaced as a calm message.
15
+ import React, { useState } from "react";
16
+ import { Flex, Heading, Text, Button, Link, Alert } from "@hubspot/ui-extensions";
17
+
18
+ import { callAppApi, AppApiError } from "../sdk/app/base";
19
+
20
+ // context: extension context. path: the mint endpoint on the app's backend
21
+ // (e.g. `/v2/hubspot/install/${appKey}/sandbox-link`). portalId: the prod portal
22
+ // minting the link. appName: copy. heading/description: optional overrides.
23
+ export function SandboxInstall({
24
+ context,
25
+ path,
26
+ portalId,
27
+ appName = "this app",
28
+ heading = "Install on a sandbox",
29
+ description,
30
+ }) {
31
+ const [installUrl, setInstallUrl] = useState(null);
32
+ const [loading, setLoading] = useState(false);
33
+ const [error, setError] = useState(null);
34
+
35
+ const mint = async () => {
36
+ setLoading(true);
37
+ setError(null);
38
+ try {
39
+ const pid = portalId ?? context.portal.id;
40
+ const sep = path.includes("?") ? "&" : "?";
41
+ const res = await callAppApi(context, `${path}${sep}portalId=${pid}`, "POST");
42
+ setInstallUrl(res?.install_url ?? null);
43
+ } catch (err) {
44
+ // The backend 403s apps that don't allow sandbox installs — show its
45
+ // message rather than a generic failure.
46
+ setError(err instanceof AppApiError ? err.message : String(err));
47
+ } finally {
48
+ setLoading(false);
49
+ }
50
+ };
51
+
52
+ return (
53
+ <Flex direction="column" gap="small">
54
+ <Heading>{heading}</Heading>
55
+ <Text>
56
+ {description ??
57
+ `Spin up ${appName} on a HubSpot sandbox account, linked to your billing. ` +
58
+ `Generate a link, then open it while signed in to your sandbox.`}
59
+ </Text>
60
+
61
+ {error && (
62
+ <Alert title="Couldn't create a sandbox link" variant="warning">
63
+ <Text>{error}</Text>
64
+ </Alert>
65
+ )}
66
+
67
+ {installUrl ? (
68
+ <Flex direction="column" gap="extra-small">
69
+ <Link href={{ url: installUrl, external: true }}>
70
+ Open the sandbox install →
71
+ </Link>
72
+ <Text variant="microcopy">
73
+ Open this while signed in to the sandbox account you want to install on.
74
+ </Text>
75
+ </Flex>
76
+ ) : (
77
+ <Button
78
+ variant="secondary"
79
+ disabled={loading}
80
+ onClick={mint}
81
+ >
82
+ {loading ? "Generating…" : "Generate sandbox install link"}
83
+ </Button>
84
+ )}
85
+ </Flex>
86
+ );
87
+ }
88
+
89
+ export default SandboxInstall;