From 718277a934f8d44e632dd5b883c06b34e33c99e5 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:08:45 +0000 Subject: [PATCH] Portal home: trim to onboarding + processor flow, secondary Set up buttons (#7111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies the processor home page: - Removes everything below the processor flow (processing status strip, recent activity, quick actions, policy summary), leaving just the onboarding hero and processor flow. - Changes the policy "Set up" buttons (Security/Classification) from primary to secondary variant. Screenshot 2026-07-20 at 7 27
56 PM --- .../public/locales/en-US/translation.toml | 50 ----- .../src/portal/components/PolicySummary.css | 76 -------- .../components/PolicySummary.stories.tsx | 48 ----- .../src/portal/components/PolicySummary.tsx | 183 ------------------ .../components/ProcessingStatusStrip.css | 43 ---- .../ProcessingStatusStrip.stories.tsx | 36 ---- .../components/ProcessingStatusStrip.tsx | 52 ----- .../src/portal/components/RecentActivity.css | 138 ------------- .../components/RecentActivity.stories.tsx | 41 ---- .../src/portal/components/RecentActivity.tsx | 127 ------------ .../processor-flow/FlowPolicies.tsx | 4 +- frontend/editor/src/portal/views/Home.tsx | 133 ------------- 12 files changed, 3 insertions(+), 928 deletions(-) delete mode 100644 frontend/editor/src/portal/components/PolicySummary.css delete mode 100644 frontend/editor/src/portal/components/PolicySummary.stories.tsx delete mode 100644 frontend/editor/src/portal/components/PolicySummary.tsx delete mode 100644 frontend/editor/src/portal/components/ProcessingStatusStrip.css delete mode 100644 frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx delete mode 100644 frontend/editor/src/portal/components/ProcessingStatusStrip.tsx delete mode 100644 frontend/editor/src/portal/components/RecentActivity.css delete mode 100644 frontend/editor/src/portal/components/RecentActivity.stories.tsx delete mode 100644 frontend/editor/src/portal/components/RecentActivity.tsx diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index b9e1f6b969..a17b6583f9 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -6744,22 +6744,6 @@ title = "Invite teammates" blurb = "{{active}} active · {{recommended}} recommended" title = "Confirm your policies" -[portal.home.quickActions] -subtitle = "Top tasks for today" -title = "Quick actions" - -[portal.home.quickActions.buildPipeline] -blurb = "3-step composer over the typed op library" -title = "Build a pipeline" - -[portal.home.quickActions.connectSource] -blurb = "S3, agents, webhooks, watched folders" -title = "Connect a source" - -[portal.home.quickActions.issueApiKey] -blurb = "Scoped key with rate limits and IP allowlist" -title = "Issue an API key" - [portal.infrastructure] manageEditorDeployment = "Manage Editor deployment" sectionsAriaLabel = "Infrastructure sections" @@ -7531,35 +7515,9 @@ setUp = "Set up {{category}} policy" [portal.policies.wizard.workflow] description = "Choose what this policy does to every document it processes." -[portal.policySummary] -activeSummary = "{{active}} / {{total}} active" -noRule = "No rule enforced yet" -subtitle = "Standing automations every document passes through, regardless of which pipeline handles it." -title = "What runs on your PDFs" - [portal.policySummary.action] -comingSoon = "Upgrade to Enterprise" -configure = "Configure" setUp = "Set up" -[portal.policySummary.column] -activeRule = "Active rule" -policy = "Policy" -status = "Status" - -[portal.policySummary.empty] -description = "Once policies are configured, the categories appear here." -title = "No policies yet" - -[portal.policySummary.state] -active = "Active" -off = "Off" -soon = "Soon" - -[portal.processingStatus] -managePlan = "Manage plan" -volumeSuffix = "PDFs processed · last 30 days" - [portal.processorFlow] footnote = "Counts are over the last 24 hours. Flow speed is illustrative." liveBadge = "Live" @@ -7878,14 +7836,6 @@ homeBody = "Committed volume pricing, org-wide SSO + SCIM + RBAC, 90-day immutab homeCta = "Start Trial →" homeHeadline = "Process millions of PDFs." -[portal.recentActivity] -title = "Recent activity" -viewAll = "View all" - -[portal.recentActivity.empty] -description = "Pipeline runs, deploys and agent events will appear here." -title = "Nothing here yet" - [portal.search] ariaLabel = "Search" placeholder = "Search Stirling — endpoints, pipelines, docs…" diff --git a/frontend/editor/src/portal/components/PolicySummary.css b/frontend/editor/src/portal/components/PolicySummary.css deleted file mode 100644 index 9b4e475895..0000000000 --- a/frontend/editor/src/portal/components/PolicySummary.css +++ /dev/null @@ -1,76 +0,0 @@ -.portal-policysum__head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 0.75rem; - padding: 1rem 1.125rem 0.875rem; - border-bottom: 1px solid var(--color-border-light); -} - -.portal-policysum__title { - margin: 0; - font-size: 0.9375rem; - font-weight: 600; - color: var(--color-text-1); -} - -.portal-policysum__sub { - margin: 0.125rem 0 0; - font-size: 0.75rem; - color: var(--color-text-4); - max-width: 34rem; -} - -.portal-policysum__cat { - display: flex; - align-items: center; - gap: 0.625rem; -} - -.portal-policysum__icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.75rem; - height: 1.75rem; - flex: none; - border-radius: var(--radius-md); - background: var(--color-bg-subtle); - border: 1px solid var(--color-border-light); - font-size: 0.9375rem; -} - -.portal-policysum__cat-text { - display: flex; - flex-direction: column; - min-width: 0; -} - -.portal-policysum__cat-text strong { - font-size: 0.8125rem; - font-weight: 600; - color: var(--color-text-1); -} - -.portal-policysum__cat-text span { - font-size: 0.6875rem; - color: var(--color-text-4); -} - -.portal-policysum__rule { - font-size: 0.75rem; - color: var(--color-text-3); -} - -.portal-policysum__loading { - display: flex; - flex-direction: column; - gap: 0.75rem; - padding: 1rem 1.125rem; -} - -.portal-policysum__loading-row { - display: flex; - flex-direction: column; - gap: 0.375rem; -} diff --git a/frontend/editor/src/portal/components/PolicySummary.stories.tsx b/frontend/editor/src/portal/components/PolicySummary.stories.tsx deleted file mode 100644 index 6de9f437ac..0000000000 --- a/frontend/editor/src/portal/components/PolicySummary.stories.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { http, HttpResponse, delay } from "msw"; -import { PolicySummary } from "@portal/components/PolicySummary"; - -const meta: Meta = { - title: "Portal/Home/PolicySummary", - component: PolicySummary, - parameters: { layout: "padded" }, - decorators: [ - (S) => ( -
- -
- ), - ], -}; -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const Loading: Story = { - parameters: { - msw: { - handlers: [ - http.get("/api/v1/policies", async () => { - await delay("infinite"); - return HttpResponse.json({}); - }), - ], - }, - }, -}; - -export const Empty: Story = { - parameters: { - msw: { - handlers: [ - http.get("/api/v1/policies", () => - HttpResponse.json({ - summary: { active: 0, paused: 0, categories: 0, docsEnforced: 0 }, - catalogue: [], - }), - ), - ], - }, - }, -}; diff --git a/frontend/editor/src/portal/components/PolicySummary.tsx b/frontend/editor/src/portal/components/PolicySummary.tsx deleted file mode 100644 index 9979783536..0000000000 --- a/frontend/editor/src/portal/components/PolicySummary.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { - Button, - Card, - EmptyState, - Skeleton, - StatusBadge, - Table, - type TableColumn, -} from "@app/ui"; -import { useView } from "@portal/contexts/ViewContext"; -import { useAsync, useSectionFlags } from "@portal/hooks/useAsync"; -import { - fetchPolicies, - type CatalogueEntry, - type PoliciesResponse, -} from "@portal/api/policies"; -import { PolicyCategoryBadge } from "@portal/components/policies/PolicyCategoryIcon"; -import "@portal/components/PolicySummary.css"; - -/** - * Each row's display state collapses a category's facts into one of three - * mutually-exclusive shapes: - * - * - `locked` — a coming-soon category; show a "Soon" affordance - * - `active` — a configured policy that's enabled; offer "Configure" - * - `off` — available but not set up (or paused); offer "Set up" - */ -type RowState = "locked" | "active" | "off"; - -interface PolicyRow { - entry: CatalogueEntry; - state: RowState; -} - -const STATE_BADGE: Record< - RowState, - { tone: "success" | "neutral" | "info"; labelKey: string } -> = { - active: { tone: "success", labelKey: "portal.policySummary.state.active" }, - off: { tone: "neutral", labelKey: "portal.policySummary.state.off" }, - locked: { tone: "info", labelKey: "portal.policySummary.state.soon" }, -}; - -function toRow(entry: CatalogueEntry): PolicyRow { - if (entry.category.comingSoon) return { entry, state: "locked" }; - const active = entry.policy?.state.status === "active"; - return { entry, state: active ? "active" : "off" }; -} - -export function PolicySummary() { - const { t } = useTranslation(); - const { setActiveView } = useView(); - const state = useAsync(() => fetchPolicies(), []); - const { data } = state; - const { isLoading, isEmpty } = useSectionFlags(state); - - const goToPolicies = () => setActiveView("policies"); - - const columns: TableColumn[] = [ - { - key: "category", - header: t("portal.policySummary.column.policy"), - render: ({ entry }) => ( -
- -
- {t(entry.category.label)} - {t(entry.category.desc)} -
-
- ), - }, - { - key: "status", - header: t("portal.policySummary.column.status"), - width: "7rem", - render: ({ state }) => { - const badge = STATE_BADGE[state]; - return ( - - {t(badge.labelKey)} - - ); - }, - }, - { - key: "rule", - header: t("portal.policySummary.column.activeRule"), - render: ({ entry, state }) => ( - - {state === "active" - ? t(entry.config.summary) - : t("portal.policySummary.noRule")} - - ), - }, - { - key: "action", - header: "", - align: "right", - width: "9rem", - render: ({ state }) => { - if (state === "locked") { - return ( - - ); - } - return ( - - ); - }, - }, - ]; - - const rows: PolicyRow[] = data?.catalogue?.map(toRow) ?? []; - - return ( -
- -
-
-

- {t("portal.policySummary.title")} -

-

- {t("portal.policySummary.subtitle")} -

-
- {data?.summary && ( - - {t("portal.policySummary.activeSummary", { - active: data.summary.active, - total: data.summary.categories, - })} - - )} -
- - {isLoading && ( -
- {Array.from({ length: 5 }).map((_, i) => ( -
- - -
- ))} -
- )} - - {isEmpty && ( - - )} - - {data && rows.length > 0 && ( - r.entry.category.id} - onRowClick={goToPolicies} - /> - )} - - - ); -} diff --git a/frontend/editor/src/portal/components/ProcessingStatusStrip.css b/frontend/editor/src/portal/components/ProcessingStatusStrip.css deleted file mode 100644 index 63341798f6..0000000000 --- a/frontend/editor/src/portal/components/ProcessingStatusStrip.css +++ /dev/null @@ -1,43 +0,0 @@ -/* Single inline row: plan + real processed-PDF volume + manage-plan shortcut. */ -.portal-statusstrip--paid { - display: flex; - align-items: center; - gap: 0.625rem; - padding: 0.625rem 0.875rem; - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); -} - -.portal-statusstrip__plan { - display: inline-flex; - align-items: center; - gap: 0.4375rem; - font-size: 0.8125rem; - font-weight: 600; - color: var(--color-text-1); -} - -.portal-statusstrip__dot { - width: 0.5rem; - height: 0.5rem; - border-radius: 50%; -} - -.portal-statusstrip__sep { - color: var(--color-text-5); -} - -.portal-statusstrip__volume { - font-size: 0.8125rem; - color: var(--color-text-3); -} - -.portal-statusstrip__volume strong { - color: var(--color-text-1); - font-weight: 600; -} - -.portal-statusstrip__manage { - margin-left: auto; -} diff --git a/frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx b/frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx deleted file mode 100644 index c5df1139d5..0000000000 --- a/frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { http, HttpResponse, delay } from "msw"; -import { ProcessingStatusStrip } from "@portal/components/ProcessingStatusStrip"; - -const meta: Meta = { - title: "Portal/Home/ProcessingStatusStrip", - component: ProcessingStatusStrip, - parameters: { layout: "padded" }, - decorators: [ - (S) => ( -
- -
- ), - ], -}; -export default meta; -type Story = StoryObj; - -/** Switch the Tier toolbar to compare the free meter vs the paid plan row. */ -export const Default: Story = {}; - -/** Free tier pushed near its cap so the meter and upgrade nudge turn amber. */ -export const FreeNearCap: Story = { - globals: { tier: "free" }, - parameters: { - msw: { - handlers: [ - http.get("/v1/home/kpis", async () => { - await delay(80); - return HttpResponse.json([{ value: "472 / 500" }]); - }), - ], - }, - }, -}; diff --git a/frontend/editor/src/portal/components/ProcessingStatusStrip.tsx b/frontend/editor/src/portal/components/ProcessingStatusStrip.tsx deleted file mode 100644 index 2f4602b11d..0000000000 --- a/frontend/editor/src/portal/components/ProcessingStatusStrip.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { Button, Skeleton } from "@app/ui"; -import { TIER_INFO, useTier } from "@portal/contexts/TierContext"; -import { useView } from "@portal/contexts/ViewContext"; -import { useAsync } from "@portal/hooks/useAsync"; -import { fetchFleetStats, type FleetStats } from "@portal/api/fleetStats"; -import "@portal/components/ProcessingStatusStrip.css"; - -/** - * A thin one-line header above the home content: current plan + the real 30-day - * processed-PDF volume (from the fleet-usage endpoint), with a shortcut to the - * Usage page. Renders "—" while loading or when the backend can't compute the - * figure (e.g. EE auditing disabled) — never a fabricated number. - */ -export function ProcessingStatusStrip() { - const { t } = useTranslation(); - const { tier } = useTier(); - const { setActiveView } = useView(); - const { data, loading } = useAsync(() => fetchFleetStats(), []); - - return ( -
- - - {t(TIER_INFO[tier].labelKey)} - - - · - - - {loading ? ( - - ) : ( - {data?.pdfsProcessed?.toLocaleString() ?? "—"} - )}{" "} - {t("portal.processingStatus.volumeSuffix")} - - -
- ); -} diff --git a/frontend/editor/src/portal/components/RecentActivity.css b/frontend/editor/src/portal/components/RecentActivity.css deleted file mode 100644 index 9fceadfe84..0000000000 --- a/frontend/editor/src/portal/components/RecentActivity.css +++ /dev/null @@ -1,138 +0,0 @@ -.portal-activity { - display: flex; - flex-direction: column; -} - -.portal-activity__head { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.875rem 1rem; - border-bottom: 1px solid var(--color-border-light); -} - -.portal-activity__title { - margin: 0; - font-size: 0.9375rem; - font-weight: 600; - color: var(--color-text-1); -} - -.portal-activity__more { - font-size: 0.75rem; - font-weight: 500; - color: var(--color-blue); - padding: 0.25rem 0.5rem; - border-radius: var(--radius-sm); - transition: background var(--motion-fast); -} - -.portal-activity__more:hover { - background: var(--color-bg-hover); -} - -.portal-activity__list { - list-style: none; - margin: 0; - padding: 0; -} - -.portal-activity__item { - display: flex; - gap: 0.75rem; - padding: 0.75rem 1rem; - border-bottom: 1px solid var(--color-border-light); - transition: background var(--motion-fast); -} - -.portal-activity__item:last-child { - border-bottom: none; -} - -.portal-activity__item:hover { - background: var(--color-bg-hover); -} - -.portal-activity__rail { - width: 0.1875rem; - border-radius: var(--radius-pill); - flex-shrink: 0; -} - -.portal-activity__body { - flex: 1 1 auto; - min-width: 0; -} - -.portal-activity__row { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 0.5rem; -} - -.portal-activity__action { - font-size: 0.8125rem; - font-weight: 600; - color: var(--color-text-1); -} - -.portal-activity__time { - font-size: 0.6875rem; - color: var(--color-text-5); -} - -.portal-activity__subject { - font-size: 0.75rem; - color: var(--color-text-3); - margin-top: 0.0625rem; -} - -.portal-activity__detail-row { - margin-top: 0.375rem; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.5rem; -} - -.portal-activity__detail { - font-size: 0.75rem; - color: var(--color-text-4); - font-family: var(--font-mono); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.portal-activity__item--skeleton { - cursor: default; -} - -.portal-activity__item--skeleton .portal-activity__rail { - background: var(--color-border-light); -} - -.portal-activity__skel { - height: 0.75rem; - border-radius: var(--radius-pill); - background: linear-gradient( - 90deg, - var(--color-bg-muted) 0%, - var(--color-bg-hover) 50%, - var(--color-bg-muted) 100% - ); - background-size: 200% 100%; - animation: shimmer 1.4s linear infinite; - margin-bottom: 0.375rem; -} - -.portal-activity__skel--md { - width: 70%; -} - -.portal-activity__skel--sm { - width: 50%; - height: 0.625rem; -} diff --git a/frontend/editor/src/portal/components/RecentActivity.stories.tsx b/frontend/editor/src/portal/components/RecentActivity.stories.tsx deleted file mode 100644 index 5b33617ea4..0000000000 --- a/frontend/editor/src/portal/components/RecentActivity.stories.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { http, HttpResponse, delay } from "msw"; -import { RecentActivity } from "@portal/components/RecentActivity"; - -const meta: Meta = { - title: "Portal/Home/RecentActivity", - component: RecentActivity, - parameters: { layout: "padded" }, - decorators: [ - (S) => ( -
- -
- ), - ], -}; -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const Loading: Story = { - parameters: { - msw: { - handlers: [ - http.get("/v1/activity", async () => { - await delay("infinite"); - return HttpResponse.json([]); - }), - ], - }, - }, -}; - -export const Empty: Story = { - parameters: { - msw: { - handlers: [http.get("/v1/activity", () => HttpResponse.json([]))], - }, - }, -}; diff --git a/frontend/editor/src/portal/components/RecentActivity.tsx b/frontend/editor/src/portal/components/RecentActivity.tsx deleted file mode 100644 index 3ebd940151..0000000000 --- a/frontend/editor/src/portal/components/RecentActivity.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { Button, Card, EmptyState, Skeleton, StatusBadge } from "@app/ui"; -import { useTier } from "@portal/contexts/TierContext"; -import { useView } from "@portal/contexts/ViewContext"; -import { useAsync } from "@portal/hooks/useAsync"; -import { - fetchAuditLog, - type AuditLogResponse, - type AuditStatus, -} from "@portal/api/infrastructure"; -import { - AUDIT_CAT_LABEL, - AUDIT_STATUS_LABEL, - AUDIT_TONE, -} from "@portal/components/infrastructure/infraFormat"; -import "@portal/components/RecentActivity.css"; - -/** Rail colour by outcome — mirrors the audit status tones. */ -const STATUS_COLOUR: Record = { - success: "var(--color-green)", - warning: "var(--color-amber)", - danger: "var(--color-red)", - info: "var(--color-blue)", -}; - -/** How many of the most recent audit events the home card shows. */ -const MAX_EVENTS = 6; - -/** - * The home "Recent activity" card, backed by the real audit log (the same - * endpoint the Infrastructure → Audit tab reads). Shows the latest few events; - * "View all" jumps to the full audit view. Failures and an empty log both fall - * through to the empty state — never an error banner on the dashboard. - */ -export function RecentActivity() { - const { t } = useTranslation(); - const { tier } = useTier(); - const { setActiveView } = useView(); - const { data, loading } = useAsync( - () => fetchAuditLog(tier), - [tier], - ); - - const events = data?.events.slice(0, MAX_EVENTS) ?? []; - const isLoading = loading && data === null; - const isEmpty = !isLoading && events.length === 0; - - return ( - -
-

- {t("portal.recentActivity.title")} -

- -
- - {isLoading && ( -
    - {Array.from({ length: 5 }).map((_, i) => ( -
  • - -
    - - -
    -
  • - ))} -
- )} - - {isEmpty && ( - - )} - - {events.length > 0 && ( -
    - {events.map((event) => ( -
  1. - -
    -
    - - {event.action} - - - {event.timestamp} - -
    -
    {event.target}
    -
    - - {t(AUDIT_CAT_LABEL[event.category])} · {event.actor} - - - {t(AUDIT_STATUS_LABEL[event.status])} - -
    -
    -
  2. - ))} -
- )} -
- ); -} diff --git a/frontend/editor/src/portal/components/processor-flow/FlowPolicies.tsx b/frontend/editor/src/portal/components/processor-flow/FlowPolicies.tsx index f01d51c44d..50b439368f 100644 --- a/frontend/editor/src/portal/components/processor-flow/FlowPolicies.tsx +++ b/frontend/editor/src/portal/components/processor-flow/FlowPolicies.tsx @@ -59,8 +59,10 @@ export function FlowPolicies({ ) : policy.state === "off" ? ( - ))} - - - ); -} - /* ──────────────────────────────────────────────────────────────────────── */ /* Home view */ /* ──────────────────────────────────────────────────────────────────────── */ @@ -144,15 +20,6 @@ export function Home() { deal is underway (a bolt-on to any tier), otherwise the setup checklist. */} - - {/* One unified layout across tiers: real processed-PDF volume, real audit - activity, quick actions, and the standing-policy summary. */} - -
- - -
- ); }