Skip to content

Commit a8bda92

Browse files
authored
feat(portal): replace free-tier carousel with static welcome hero (Stirling-Tools#6901)
## What this PR does Redesigns the portal home to match the marketing demo, across all tiers. - Swapped the old rotating welcome carousel for a static **welcome hero** on the free tier - Subscribed (Processor) + enterprise get a **deployed-editor hero** — shows the live instance (host, version, active users) with an *Open in browser* button, pulled from the real editor-deployment API (same data the Editor admin page uses) - Added a time-of-day **greeting** on the paid tiers - Rebuilt the **"Finish setting up" checklist** so it's real: the counts and tick-offs come from the actual policies + sources (a step is done when there's at least one), not hardcoded numbers - *Download the PDF Editor* → `https://stirling.com/download`; the other steps deep-link to Policies / Sources - **Procurement is now a bolt-on to any tier** — if a deal's in flight the deal-status hero drops into the hero's footer, otherwise you get the setup checklist - All new copy is translated (en-US) and it reuses the shared UI kit, icons and design tokens ## Tidy-ups / fixes found along the way - The subscribed hero was shadowing the real `/v1/editor/deployment` endpoint (broke the Editor admin page) — now reuses it - Renamed the hero's CSS namespace to `.portal-welcome` so it stops clashing with the procurement hero's `.portal-hero` - Refactored the merged procurement component into a shared `useProcurement` hook + banner + flow, so the deal hero can live inside the tier hero — `/procurement` route unchanged ## Screenshots <img width="1258" height="1338" alt="pr-free" src="https://github.com/user-attachments/assets/9bb7db44-5f8e-4388-857a-7f113c2d7d82" /> <img width="1258" height="862" alt="pr-enterprise" src="https://github.com/user-attachments/assets/f1f36faa-1467-404e-9036-dab12e3d0b54" /> <img width="1258" height="944" alt="pr-subscribed" src="https://github.com/user-attachments/assets/775f1228-f182-47b5-82ff-7ac6d5932bd9" /> --- ## Checklist ### General - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### UI Changes - [x] Screenshots demonstrating the UI changes are attached ### Testing - [x] Portal typecheck, ESLint and Prettier pass on the changed files - [x] Verified all states in Storybook and ran the app locally via `task dev:portal`
1 parent 0692058 commit a8bda92

29 files changed

Lines changed: 2177 additions & 1070 deletions

frontend/editor/public/locales/en-US/translation.toml

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6739,6 +6739,23 @@ ready = "Ready to deploy"
67396739
description = "Once documents are processed, your 30-day usage appears here."
67406740
title = "No usage yet"
67416741

6742+
[portal.home.editor]
6743+
activeUsers = "{{n}} active"
6744+
invite = "Invite teammates"
6745+
name = "Stirling PDF Editor"
6746+
open = "Open in browser"
6747+
updated = "updated {{time}}"
6748+
6749+
[portal.home.editor.target]
6750+
cloud = "Managed Cloud"
6751+
docker = "Self-hosted · Docker"
6752+
kubernetes = "Self-hosted · Kubernetes"
6753+
6754+
[portal.home.greeting]
6755+
afternoon = "Good afternoon"
6756+
evening = "Good evening"
6757+
morning = "Good morning"
6758+
67426759
[portal.home.kpis.enterprise]
67436760
docs30d = "Docs / 30d"
67446761
evalPassRate = "Eval pass rate"
@@ -6759,15 +6776,31 @@ evalPassRate = "Eval pass rate"
67596776
pipelines = "Pipelines"
67606777

67616778
[portal.home.onboarding]
6762-
progress = "{{done}} / {{total}} done"
6763-
runAgain = "Run again"
6764-
start = "Start"
6765-
subtitle = "Four steps to a production-shaped Stirling project."
6766-
title = "Get to value"
6767-
6768-
[portal.home.onboarding.empty]
6769-
description = "Onboarding tasks will appear here once your workspace is set up."
6770-
title = "No onboarding steps yet"
6779+
dismiss = "Dismiss setup"
6780+
notStarted = "Not started"
6781+
progress = "{{done}} of {{total}} done"
6782+
title = "Finish setting up"
6783+
6784+
[portal.home.onboarding.enterprise]
6785+
body = "Org-wide SSO + SCIM + RBAC, committed volume pricing, and air-gapped deployment."
6786+
cta = "Start Trial"
6787+
ctaQuote = "Get Quote"
6788+
lead = "For 250+ employees."
6789+
tag = "Enterprise"
6790+
6791+
[portal.home.onboarding.steps.editor]
6792+
blurb = "Deploy the desktop app, free forever."
6793+
title = "Download the PDF Editor"
6794+
6795+
[portal.home.onboarding.steps.policies]
6796+
blurb = "{{active}} active · {{recommended}} recommended"
6797+
chip = "{{active}} active"
6798+
title = "Turn on policies"
6799+
6800+
[portal.home.onboarding.steps.sources]
6801+
blurb = "{{connected}} connected · every PDF governed where it lands"
6802+
chip = "{{connected}} connected"
6803+
title = "Connect your sources"
67716804

67726805
[portal.home.productGrid]
67736806
ariaLabel = "Process PDFs at scale"
@@ -8015,36 +8048,14 @@ status = "Status"
80158048
suspend = "Suspend"
80168049

80178050
[portal.welcome]
8018-
ariaLabel = "Stirling product highlights"
8019-
pagination = "Carousel pagination"
8020-
slideLabel = "Slide {{number}}: {{title}}"
8021-
8022-
[portal.welcome.ornament.editor]
8023-
critical = "Critical"
8024-
ocrClean = "OCR-clean"
8025-
schemaMatch = "schema match 0.97"
8026-
signed = "signed"
8027-
8028-
[portal.welcome.slides.agents]
8029-
eyebrow = "AI Agents"
8030-
primary = "Try PDF Processor"
8031-
secondary = "View MCP docs"
8032-
sub = "Wire your agent via MCP, REST or tool definitions. Deterministic operations and guardrails — test with scenarios and evals before you ship."
8033-
title = "PDF Processor for AI Agents"
8034-
8035-
[portal.welcome.slides.editor]
8036-
eyebrow = "PDF Editor"
8037-
primary = "Install PDF Editor"
8038-
secondary = "Connect an instance"
8039-
sub = "Annotate, sign, redact, and review locally or in the cloud. Brought to the platform as the credibility anchor of the Stirling control plane."
8040-
title = "The #1 PDF Editor on GitHub"
8041-
8042-
[portal.welcome.slides.platform]
8043-
eyebrow = "Platform"
8044-
primary = "Try a PDF operation"
8045-
secondary = "Get an API key"
8046-
sub = "Ingest from agents, APIs and connectors. Run composable pipelines with evals and golden sets. Land in a vault with zero-standing-access controls."
8047-
title = "PDF Infrastructure for Developers"
8051+
ariaLabel = "Welcome to Stirling PDF"
8052+
badge = "Open-source"
8053+
installEditor = "Install the Editor"
8054+
inviteTeammates = "Invite teammates"
8055+
perks = "Free forever · Self-hostable"
8056+
subtitle = "The world's most secure PDF Editor is free for teams of all sizes. Includes 60+ PDF operations and SSO."
8057+
title = "Welcome to"
8058+
titleAccent = "Stirling PDF"
80488059

80498060
[printFile]
80508061
title = "Print File"

frontend/editor/src/portal/MOCKS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ non-2xx). Views consume via `useAsync()` + `useSectionFlags()` (`hooks/useAsync.
5252
| Home | `GET /v1/analytics/usage` || `fetchUsageSeries` | `UsageSeriesResponse` |
5353
| Home | `GET /v1/activity` || `fetchRecentActivity` | `ActivityEvent[]` |
5454
| Home | `GET /v1/regions/health` || `fetchRegionHealth` | `RegionHealth[]` |
55-
| Home | `GET /v1/onboarding` || `fetchOnboarding` | `OnboardingStep[]` |
5655
| Users | `GET /v1/users` | `tier` | `fetchUsers` | `UsersResponse` |
5756
| Documents | `GET /v1/documents` | `tier` | `fetchDocuments` | `DocumentsResponse` |
5857
| Pipelines | `GET /v1/pipelines` · `POST /v1/pipelines/:id/promote-to-policy` | `tier` | `fetchPipelines` · `promoteToPolicy` | `PipelinesResponse` |

frontend/editor/src/portal/api/home.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { apiClient } from "@portal/api/http";
22
import type {
33
ActivityEvent,
44
KpiEntry,
5-
OnboardingStep,
65
RegionHealth,
76
UsageSeriesResponse,
87
} from "@portal/mocks/home";
@@ -12,7 +11,6 @@ export type {
1211
ActivityEvent,
1312
ActivityKind,
1413
KpiEntry,
15-
OnboardingStep,
1614
PipelineStage,
1715
PipelineTemplate,
1816
RegionHealth,
@@ -44,8 +42,3 @@ export async function fetchHomeKpis(tier: Tier): Promise<KpiEntry[]> {
4442
export async function fetchRegionHealth(): Promise<RegionHealth[]> {
4543
return apiClient.local.json<RegionHealth[]>("/v1/regions/health");
4644
}
47-
48-
/** GET /v1/onboarding (Free) */
49-
export async function fetchOnboarding(): Promise<OnboardingStep[]> {
50-
return apiClient.local.json<OnboardingStep[]>("/v1/onboarding");
51-
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* ──────────────────────────────────────────────────────────────────────── */
2+
/* Subscribed-tier deployed-Editor hero */
3+
/* ──────────────────────────────────────────────────────────────────────── */
4+
5+
.portal-editor-hero {
6+
border-radius: var(--radius-xl);
7+
border: 1px solid var(--color-border-input);
8+
background: var(--color-surface);
9+
box-shadow: var(--shadow-sm);
10+
overflow: hidden;
11+
}
12+
13+
.portal-editor-hero__row {
14+
display: flex;
15+
align-items: center;
16+
gap: 1.25rem;
17+
padding: 1.25rem 1.5rem;
18+
}
19+
20+
.portal-editor-hero__logo {
21+
width: 3.5rem;
22+
height: 3.5rem;
23+
border-radius: 0.875rem;
24+
overflow: hidden;
25+
flex-shrink: 0;
26+
box-shadow:
27+
0 2px 8px rgba(0, 0, 0, 0.1),
28+
0 0 0 1px rgba(0, 0, 0, 0.06);
29+
}
30+
.portal-editor-hero__mark {
31+
width: 100%;
32+
height: 100%;
33+
display: block;
34+
}
35+
36+
.portal-editor-hero__info {
37+
flex: 1;
38+
min-width: 0;
39+
display: flex;
40+
flex-direction: column;
41+
gap: 0.5rem;
42+
}
43+
44+
.portal-editor-hero__title-row {
45+
display: flex;
46+
align-items: center;
47+
gap: 0.5625rem;
48+
flex-wrap: wrap;
49+
}
50+
51+
.portal-editor-hero__name {
52+
font-size: 1.125rem;
53+
font-weight: 700;
54+
color: var(--color-text-1);
55+
margin-right: 0.125rem;
56+
}
57+
58+
.portal-editor-hero__chip {
59+
display: inline-flex;
60+
align-items: center;
61+
gap: 0.3125rem;
62+
padding: 0.3125rem 0.75rem;
63+
border-radius: var(--radius-pill);
64+
border: 1px solid var(--color-border-input);
65+
background: var(--color-bg-code);
66+
color: var(--color-text-2);
67+
font-size: 0.71875rem;
68+
font-weight: 600;
69+
cursor: pointer;
70+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
71+
transition:
72+
background var(--motion-fast),
73+
box-shadow var(--motion-fast),
74+
transform var(--motion-fast);
75+
}
76+
.portal-editor-hero__chip:hover {
77+
background: var(--color-surface);
78+
box-shadow: 0 2px 5px rgba(15, 23, 42, 0.1);
79+
transform: translateY(-1px);
80+
}
81+
.portal-editor-hero__chip svg {
82+
color: var(--color-text-4);
83+
}
84+
85+
.portal-editor-hero__meta {
86+
display: flex;
87+
align-items: center;
88+
gap: 0.5rem;
89+
flex-wrap: wrap;
90+
font-size: 0.75rem;
91+
color: var(--color-text-5);
92+
}
93+
.portal-editor-hero__host {
94+
color: var(--color-text-2);
95+
font-family: var(--font-mono);
96+
font-weight: 500;
97+
}
98+
.portal-editor-hero__meta-sep {
99+
color: var(--color-border-input);
100+
}
101+
102+
.portal-editor-hero__action {
103+
flex-shrink: 0;
104+
}
105+
106+
@media (max-width: 48rem) {
107+
.portal-editor-hero__row {
108+
flex-wrap: wrap;
109+
}
110+
.portal-editor-hero__action {
111+
width: 100%;
112+
}
113+
}
114+
115+
/* Attached footer strip (setup checklist). */
116+
.portal-editor-hero__footer {
117+
border-top: 1px solid var(--color-border-light);
118+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
import { http, HttpResponse } from "msw";
3+
import { EditorStatusCard } from "@portal/components/EditorStatusCard";
4+
import { SetupChecklist } from "@portal/components/SetupChecklist";
5+
6+
const meta: Meta<typeof EditorStatusCard> = {
7+
title: "Portal/Home/EditorStatusCard",
8+
component: EditorStatusCard,
9+
parameters: { layout: "padded" },
10+
globals: { tier: "pro" },
11+
decorators: [
12+
(S) => (
13+
<div style={{ maxWidth: "72rem" }}>
14+
<S />
15+
</div>
16+
),
17+
],
18+
};
19+
export default meta;
20+
type Story = StoryObj<typeof EditorStatusCard>;
21+
22+
/** The deployed-Editor status card on its own. */
23+
export const Default: Story = {};
24+
25+
/** As it renders on the subscribed home: the setup checklist attached as the footer. */
26+
export const WithSetupChecklist: Story = {
27+
args: {
28+
footer: <SetupChecklist />,
29+
},
30+
};
31+
32+
/**
33+
* Backend without the editor-deployment endpoint (404): the status row is
34+
* skipped and the card falls back to just the footer (setup checklist). It
35+
* lights up automatically once /v1/editor/deployment is served.
36+
*/
37+
export const DeploymentUnavailable: Story = {
38+
args: {
39+
footer: <SetupChecklist />,
40+
},
41+
parameters: {
42+
msw: {
43+
handlers: [
44+
http.get("/v1/editor/deployment", () =>
45+
HttpResponse.json({ message: "not found" }, { status: 404 }),
46+
),
47+
],
48+
},
49+
},
50+
};

0 commit comments

Comments
 (0)