@growth-operations/ui-shared 1.23.0 → 1.25.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/AppHome.jsx +7 -41
- package/src/home/FeatureUsage.jsx +36 -15
- package/src/lib/featureUsage.js +6 -0
package/package.json
CHANGED
package/src/home/AppHome.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Flex,
|
|
2
|
+
import { Flex, LoadingSpinner } from "@hubspot/ui-extensions";
|
|
3
3
|
import { InstallProgress } from "./InstallProgress";
|
|
4
4
|
import { HealthHero } from "./HealthHero";
|
|
5
5
|
import { OnboardingChecklist } from "./OnboardingChecklist";
|
|
@@ -10,50 +10,16 @@ import { PeriodComparison } from "./PeriodComparison";
|
|
|
10
10
|
import { FeatureNudges } from "./FeatureNudges";
|
|
11
11
|
import { HelpfulLinks } from "./HelpfulLinks";
|
|
12
12
|
|
|
13
|
-
// Minimal inline ATS tile (the `pipeline` block). Kept tiny here rather than a
|
|
14
|
-
// separate file — it's the simplest of the three archetype tiles. Renders
|
|
15
|
-
// nothing when there's no pipeline block.
|
|
16
|
-
function PipelineTile({ pipeline }) {
|
|
17
|
-
if (!pipeline) return null;
|
|
18
|
-
const byStage = pipeline.applications_by_stage ?? {};
|
|
19
|
-
return (
|
|
20
|
-
<Tile>
|
|
21
|
-
<Flex direction="column" gap="small">
|
|
22
|
-
<Text format={{ fontWeight: "bold" }}>Pipeline</Text>
|
|
23
|
-
<Flex direction="row" gap="large">
|
|
24
|
-
<Flex direction="column" gap="extra-small">
|
|
25
|
-
<Text format={{ fontWeight: "demibold" }}>Open jobs</Text>
|
|
26
|
-
<Text>{pipeline.open_jobs ?? 0}</Text>
|
|
27
|
-
</Flex>
|
|
28
|
-
<Flex direction="column" gap="extra-small">
|
|
29
|
-
<Text format={{ fontWeight: "demibold" }}>Outstanding scorecards</Text>
|
|
30
|
-
<Text>{pipeline.outstanding_scorecards ?? 0}</Text>
|
|
31
|
-
</Flex>
|
|
32
|
-
</Flex>
|
|
33
|
-
{Object.keys(byStage).length > 0 && (
|
|
34
|
-
<Flex direction="row" gap="medium">
|
|
35
|
-
{Object.entries(byStage).map(([stage, count]) => (
|
|
36
|
-
<Flex key={stage} direction="column" gap="extra-small">
|
|
37
|
-
<Text format={{ fontWeight: "demibold" }}>{stage}</Text>
|
|
38
|
-
<Text>{count}</Text>
|
|
39
|
-
</Flex>
|
|
40
|
-
))}
|
|
41
|
-
</Flex>
|
|
42
|
-
)}
|
|
43
|
-
</Flex>
|
|
44
|
-
</Tile>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
13
|
// Picks the single archetype activity tile (CONTRACT: at most ONE present).
|
|
49
|
-
// sync_pulse -> SyncPulse
|
|
14
|
+
// sync_pulse -> SyncPulse. The credit meter is
|
|
50
15
|
// deliberately NOT on Home anymore (Andrew's 2026-10 feedback) — it lives at
|
|
51
16
|
// the top of the Usage tab; Home's credit story is the top BillingBanner's
|
|
52
17
|
// trial states plus the post-checklist period comparison. Renders nothing
|
|
53
|
-
// when no tile applies.
|
|
18
|
+
// when no tile applies. (The ATS `pipeline` tile was retired 2026-10-14 —
|
|
19
|
+
// product decision: it duplicated the dedicated Applications/Jobs/Scorecards
|
|
20
|
+
// tabs and its cold-started counters read wrong until churn.)
|
|
54
21
|
function ArchetypeTile({ state }) {
|
|
55
22
|
if (state.sync_pulse) return <SyncPulse syncPulse={state.sync_pulse} />;
|
|
56
|
-
if (state.pipeline) return <PipelineTile pipeline={state.pipeline} />;
|
|
57
23
|
return null;
|
|
58
24
|
}
|
|
59
25
|
|
|
@@ -67,7 +33,7 @@ function ArchetypeTile({ state }) {
|
|
|
67
33
|
// plan_fit says so)
|
|
68
34
|
// + <PlanFitBanner> (upgrade_recommended only)
|
|
69
35
|
// + <OnboardingChecklist> (only if not complete)
|
|
70
|
-
// + the archetype activity tile (
|
|
36
|
+
// + the archetype activity tile (sync_pulse)
|
|
71
37
|
// + <PeriodComparison> (post-checklist, when the
|
|
72
38
|
// backend has a full closed period)
|
|
73
39
|
// + <FeatureUsage> (once the non-billing setup steps
|
|
@@ -159,7 +125,7 @@ export function AppHome({ state, onNavigate, appName, onRetry, helpfulLinks }) {
|
|
|
159
125
|
on the non-billing setup steps (NOT the paid-gated plan step) so a
|
|
160
126
|
fresh install sees setup first but free/trial installs aren't
|
|
161
127
|
locked out. */}
|
|
162
|
-
{setupStepsComplete && <FeatureUsage state={state} />}
|
|
128
|
+
{setupStepsComplete && <FeatureUsage state={state} onNavigate={onNavigate} />}
|
|
163
129
|
|
|
164
130
|
<FeatureNudges nudges={state.nudges} onNavigate={onNavigate} />
|
|
165
131
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Flex, Tile, Text, Link } from "@hubspot/ui-extensions";
|
|
2
|
+
import { Flex, Tile, Text, Link, Button } from "@hubspot/ui-extensions";
|
|
3
3
|
import {
|
|
4
4
|
featureUsageOf,
|
|
5
5
|
pickDailyRecommendations,
|
|
@@ -46,7 +46,7 @@ function MostUsedTile({ mostUsed }) {
|
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function RecommendedTile({ notInUse }) {
|
|
49
|
+
function RecommendedTile({ notInUse, isSuperAdmin, onNavigate }) {
|
|
50
50
|
if (notInUse.length === 0) return null;
|
|
51
51
|
// Long lists (ATS: 13 features) render as a rotating daily slice instead of
|
|
52
52
|
// a wall — the whole list still arrives in the payload; the window is a
|
|
@@ -59,17 +59,34 @@ function RecommendedTile({ notInUse }) {
|
|
|
59
59
|
<Tile>
|
|
60
60
|
<Flex direction="column" gap="small">
|
|
61
61
|
<Text format={{ fontWeight: "bold" }}>Recommended to explore</Text>
|
|
62
|
-
{picks.map((f) =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
{picks.map((f) => {
|
|
63
|
+
// Setup CTAs point at admin-only surfaces (the layout editor above
|
|
64
|
+
// all) — show them to super admins only; everyone else keeps the
|
|
65
|
+
// docs link (a non-admin clicking into a HubSpot permission wall is
|
|
66
|
+
// worse than no link).
|
|
67
|
+
const showCta = isSuperAdmin && f.ctaLabel && (f.ctaUrl || f.ctaRoute);
|
|
68
|
+
return (
|
|
69
|
+
<Flex key={f.key} direction="column" gap="extra-small">
|
|
70
|
+
<Text format={{ fontWeight: "demibold" }}>{f.name}</Text>
|
|
71
|
+
{f.rationale ? <Text>{f.rationale}</Text> : null}
|
|
72
|
+
{showCta && f.ctaUrl ? (
|
|
73
|
+
<Link href={{ url: f.ctaUrl, external: true }}>
|
|
74
|
+
{f.ctaLabel} →
|
|
75
|
+
</Link>
|
|
76
|
+
) : null}
|
|
77
|
+
{showCta && !f.ctaUrl && f.ctaRoute && onNavigate ? (
|
|
78
|
+
<Button variant="secondary" onClick={() => onNavigate(f.ctaRoute)}>
|
|
79
|
+
{f.ctaLabel}
|
|
80
|
+
</Button>
|
|
81
|
+
) : null}
|
|
82
|
+
{f.docsUrl ? (
|
|
83
|
+
<Link href={{ url: f.docsUrl, external: true }}>
|
|
84
|
+
Learn more →
|
|
85
|
+
</Link>
|
|
86
|
+
) : null}
|
|
87
|
+
</Flex>
|
|
88
|
+
);
|
|
89
|
+
})}
|
|
73
90
|
{recommendationsRotated(notInUse) ? (
|
|
74
91
|
<Text variant="microcopy">
|
|
75
92
|
New picks daily — {notInUse.length} features to explore in all.
|
|
@@ -80,13 +97,17 @@ function RecommendedTile({ notInUse }) {
|
|
|
80
97
|
);
|
|
81
98
|
}
|
|
82
99
|
|
|
83
|
-
export function FeatureUsage({ state }) {
|
|
100
|
+
export function FeatureUsage({ state, onNavigate }) {
|
|
84
101
|
const view = featureUsageOf(state);
|
|
85
102
|
if (!view) return null;
|
|
86
103
|
return (
|
|
87
104
|
<>
|
|
88
105
|
<MostUsedTile mostUsed={view.mostUsed} />
|
|
89
|
-
<RecommendedTile
|
|
106
|
+
<RecommendedTile
|
|
107
|
+
notInUse={view.notInUse}
|
|
108
|
+
isSuperAdmin={state?.user?.is_super_admin === true}
|
|
109
|
+
onNavigate={onNavigate}
|
|
110
|
+
/>
|
|
90
111
|
</>
|
|
91
112
|
);
|
|
92
113
|
}
|
package/src/lib/featureUsage.js
CHANGED
|
@@ -39,6 +39,12 @@ export function resolveFeatureUsage(featureUsage) {
|
|
|
39
39
|
name: f.name,
|
|
40
40
|
docsUrl: typeof f.docs_url === "string" && f.docs_url ? f.docs_url : null,
|
|
41
41
|
rationale: typeof f.rationale === "string" ? f.rationale : "",
|
|
42
|
+
// Optional SETUP action (the same cta_* vocabulary as nudges): the "go
|
|
43
|
+
// do the thing" deep link for an unused feature — layout editor, store
|
|
44
|
+
// listing, or in-app route. Absent on backends that predate it.
|
|
45
|
+
ctaLabel: typeof f.cta_label === "string" && f.cta_label ? f.cta_label : null,
|
|
46
|
+
ctaUrl: typeof f.cta_url === "string" && f.cta_url ? f.cta_url : null,
|
|
47
|
+
ctaRoute: typeof f.cta_route === "string" && f.cta_route ? f.cta_route : null,
|
|
42
48
|
}));
|
|
43
49
|
|
|
44
50
|
if (mostUsed.length === 0 && notInUse.length === 0) return null;
|