From 8d9f785dc7c9d9468c7db375c8fd1ef99e12a175 Mon Sep 17 00:00:00 2001 From: Jonathan Singer Date: Mon, 20 Jul 2026 12:02:10 -0400 Subject: [PATCH] Add the persistent sidebar and shared Pro upsell tiles --- strix/viewer_src/src/components/ProCta.tsx | 148 ++++++++++++ strix/viewer_src/src/components/Sidebar.tsx | 218 ++++++++++++++++++ .../vulnerability/VulnerabilityDetail.tsx | 31 ++- 3 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 strix/viewer_src/src/components/ProCta.tsx create mode 100644 strix/viewer_src/src/components/Sidebar.tsx diff --git a/strix/viewer_src/src/components/ProCta.tsx b/strix/viewer_src/src/components/ProCta.tsx new file mode 100644 index 00000000..bd205ee5 --- /dev/null +++ b/strix/viewer_src/src/components/ProCta.tsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; +import { ArrowUpRight } from "lucide-react"; +import { SIGNUP_URL, trackCta } from "@/lib/cta"; + +/** + * Shared Pro CTA primitives. Every Pro item is a direct link-out to the cloud + * sign-up in a new tab with a hover tooltip one-liner (no modal, no lock icon). + * Built once here and reused by the sidebar Platform section, the top upsell + * row, and the inline CTAs in the tabs. + */ + +/** Small "Pro" pill. Deliberately not a padlock. */ +export function ProTag({ className = "" }: { className?: string }) { + return ( + + Pro + + ); +} + +/** + * Lightweight hover tooltip. Wraps a trigger and reveals `text` above it on + * hover/focus. Plain Tailwind + local state (no radix vendored). + */ +export function Tooltip({ + text, + children, + className = "", +}: { + text: string; + children: React.ReactNode; + className?: string; +}) { + const [open, setOpen] = useState(false); + return ( + setOpen(true)} + onMouseLeave={() => setOpen(false)} + onFocus={() => setOpen(true)} + onBlur={() => setOpen(false)} + > + {children} + {open && ( + + {text} + + )} + + ); +} + +export interface ProItem { + title: string; + desc: string; + slug: string; + icon: React.ElementType; +} + +/** + * Card-style Pro feature tile: icon + name + one-liner + Pro tag + arrow. + * Used in the top upsell row and inline CTA grids. + */ +export function ProTile({ item }: { item: ProItem }) { + const Icon = item.icon; + return ( + trackCta(item.slug)} + title={item.desc} + className="group block cursor-pointer rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-4 text-left transition-colors hover:border-[#444]" + > +
+
+

{item.title}

+

{item.desc}

+
+ ); +} + +/** + * Sidebar-row Pro item: icon + label + Pro tag, with the one-liner as a hover + * tooltip. Link-out to sign-up in a new tab. + */ +export function ProNavItem({ item }: { item: ProItem }) { + const Icon = item.icon; + return ( + + trackCta(item.slug)} + className="group flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-sm text-[#888] transition-colors hover:bg-[rgba(255,255,255,0.06)] hover:text-white" + > + + + ); +} + +/** + * Inline Pro CTA button (compact). Used in the finding detail and per-surface + * rows where a full card is too heavy. + */ +export function ProInlineCta({ + label, + desc, + slug, + icon: Icon, +}: { + label: string; + desc: string; + slug: string; + icon: React.ElementType; +}) { + return ( + + trackCta(slug)} + className="group inline-flex items-center gap-2 rounded-lg border border-[#222] bg-[rgba(255,255,255,0.02)] px-3 py-2 text-sm text-[#aaa] transition-colors hover:border-[#444] hover:text-white" + > + + + ); +} diff --git a/strix/viewer_src/src/components/Sidebar.tsx b/strix/viewer_src/src/components/Sidebar.tsx new file mode 100644 index 00000000..b27d1d32 --- /dev/null +++ b/strix/viewer_src/src/components/Sidebar.tsx @@ -0,0 +1,218 @@ +import React from "react"; +import { + FileText, + Bug, + Waypoints, + History, + Mail, + GitPullRequest, + Search, + Layers, + Globe, + Puzzle, + Users, + LayoutDashboard, + AlertTriangle, + MessageSquare, + Network, + Database, + ArrowUpRight, + LogOut, + ShieldCheck, +} from "lucide-react"; +import { SIGNUP_URL, trackCta } from "@/lib/cta"; +import { ProNavItem, type ProItem } from "@/components/ProCta"; +import type { View } from "@/App"; + +/** + * Persistent left rail. Mirrors the cloud app's nav: real in-page content + * ("This run"), local email-gated features ("Your runs"), and org-oriented + * Pro link-outs ("Platform"). Matches App.tsx's dark palette. + */ + +// Org-signal items lead the list (PR Reviews, Repositories, Domains, +// Integrations, Members), then the rest of the platform surface. +const PLATFORM_ITEMS: ProItem[] = [ + { title: "PR Reviews", desc: "Pentest every pull request your team opens.", slug: "pr_reviews", icon: GitPullRequest }, + { title: "Repositories", desc: "Connect your org's repositories and domains.", slug: "repositories", icon: Layers }, + { title: "Domains", desc: "Connect your org's repositories and domains.", slug: "domains", icon: Globe }, + { title: "Integrations", desc: "Two-way sync findings to Jira, Linear, and Slack.", slug: "integrations", icon: Puzzle }, + { title: "Members", desc: "Invite your team and set roles.", slug: "members", icon: Users }, + { title: "Pentests", desc: "Launch deeper pentests on managed infra.", slug: "pentests", icon: Search }, + { title: "Dashboard", desc: "See every project and finding in one place.", slug: "dashboard", icon: LayoutDashboard }, + { title: "Issues", desc: "Track and triage findings across your org.", slug: "platform_issues", icon: AlertTriangle }, + { title: "Chat", desc: "Ask the agents about any finding.", slug: "chat", icon: MessageSquare }, + { title: "Networks", desc: "Map and monitor your network exposure.", slug: "networks", icon: Network }, + { title: "Knowledge", desc: "Give agents context about your systems.", slug: "knowledge", icon: Database }, +]; + +interface SidebarProps { + view: View; + onSelectView: (view: View) => void; + issuesCount: number; + agentCount: number; + runCount: number; + verified: boolean; + email: string | null; + onOpenEmail: () => void; + onOpenHistory: () => void; + onForget: () => void; +} + +export default function Sidebar({ + view, + onSelectView, + issuesCount, + agentCount, + runCount, + verified, + email, + onOpenEmail, + onOpenHistory, + onForget, +}: SidebarProps) { + return ( + + ); +} + +function Section({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+

+ {label} +

+
{children}
+
+ ); +} + +function NavItem({ + icon: Icon, + label, + count, + active, + onClick, +}: { + icon: React.ElementType; + label: string; + count?: number; + active?: boolean; + onClick: () => void; +}) { + return ( + + ); +} diff --git a/strix/viewer_src/src/components/vulnerability/VulnerabilityDetail.tsx b/strix/viewer_src/src/components/vulnerability/VulnerabilityDetail.tsx index a3312023..9bff0c65 100644 --- a/strix/viewer_src/src/components/vulnerability/VulnerabilityDetail.tsx +++ b/strix/viewer_src/src/components/vulnerability/VulnerabilityDetail.tsx @@ -1,7 +1,8 @@ "use client"; import React, { useState } from "react"; -import { Clock, CheckCircle2, Ban, History, BellOff } from "lucide-react"; +import { Clock, CheckCircle2, Ban, History, BellOff, Wrench, GitMerge, GitPullRequest } from "lucide-react"; +import { ProInlineCta } from "@/components/ProCta"; import { Vulnerability, VulnerabilityStatus, SEVERITY_COLORS, STATUS_META, isSeverityOverridden } from "@/types/issues"; import { formatTimeAgo } from "@/lib/utils"; import { getSeverityDot } from "@/lib/vulnerability-utils"; @@ -228,6 +229,34 @@ export default function VulnerabilityDetail({ vulnerability }: VulnerabilityDeta /> + + {/* Team-workflow CTAs (Pro). Highest-intent surface: act on this finding. */} +
+

Ship the fix with your team

+

+ Take this finding into your team's workflow. +

+
+ + + +
+
); }