A project's sidebar lists what is coming as rows of its own: Analytics, Firewall and Flags sit among Deployments, Observability and Security as faint rows with Soon, each leading to its page of what it will do, instead of hiding as tabs inside Insights, Security and Deployments. They show to members only, as the pages they will become are for the people running the project. Environments, Uptime, Delivery and Costs stay Soon tabs of the pages they belong to. A Soon page's breadcrumb names the feature rather than "Soon", and "Also coming" lists the other two in the sidebar's order. Checked: the site's typecheck and its 913 tests, and the real shell and Soon route rendered at 1440 and 390 px for a member and a visitor (each row current on its own page only, Soon pills aligned with the counts).
3 files+46−240/3 viewed
| 1 | − | import { Activity, BarChart3, Building2, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Compass, CreditCard, Fingerprint, Gauge, GitBranch, GitPullRequest, Globe, History, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, Lock, LogOut, Mail, MonitorCog, MonitorDown, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Star, House, Ticket, TrendingUp, UserPlus, UserRoundKey, Users, UsersRound, Webhook, X } from "lucide-react"; | |
| 1 | + | import { Activity, BarChart3, BrickWall, Building2, ChartLine, Flag, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Compass, CreditCard, Fingerprint, Gauge, GitBranch, GitPullRequest, Globe, History, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, Lock, LogOut, Mail, MonitorCog, MonitorDown, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Star, House, Ticket, TrendingUp, UserPlus, UserRoundKey, Users, UsersRound, Webhook, X } from "lucide-react"; | |
| 2 | 2 | import { Fragment, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| 3 | 3 | import { Link, useFetcher, useLocation, useNavigation, useRouteLoaderData, useSubmit } from "react-router"; | |
| 4 | 4 | ||
| ⋯ | |||
| 740 | 740 | const INSIGHTS = roadmapItem("insights"); | |
| 741 | 741 | ||
| 742 | 742 | /** | |
| 743 | + | * A page a project will have that is a page of its own, not a tab: its own | |
| 744 | + | * faint row in the project's list, leading to what it will be. | |
| 745 | + | */ | |
| 746 | + | function ProjectSoonRow({ base, feature, icon }: { base: string; feature: string; icon: ReactNode }) { | |
| 747 | + | const item = roadmapItem(feature); | |
| 748 | + | if (!item) return null; | |
| 749 | + | return ( | |
| 750 | + | <SidebarSoonLink to={`${base}/soon/${item.key}`} icon={icon} about={item.summary}> | |
| 751 | + | {item.title} | |
| 752 | + | </SidebarSoonLink> | |
| 753 | + | ); | |
| 754 | + | } | |
| 755 | + | ||
| 756 | + | /** | |
| 743 | 757 | * What the repository menu needs. Known from the address before the page's | |
| 744 | 758 | * data arrives, so the menu is right from the first frame; the counts fill | |
| 745 | 759 | * in once it does. | |
| ⋯ | |||
| 835 | 849 | Deployments | |
| 836 | 850 | </SidebarLink> | |
| 837 | 851 | ) : null} | |
| 852 | + | {repo.member && <ProjectSoonRow base={base} feature="analytics" icon={<ChartLine size={15} />} />} | |
| 838 | 853 | {shows.has("observability") && ( | |
| 839 | 854 | <SidebarLink to={`${base}/observability`} also={soonPaths(base, "Observability")} icon={<Activity size={15} />}> | |
| 840 | 855 | Observability | |
| 841 | 856 | </SidebarLink> | |
| 842 | 857 | )} | |
| 858 | + | {repo.member && <ProjectSoonRow base={base} feature="firewall" icon={<BrickWall size={15} />} />} | |
| 859 | + | {repo.member && <ProjectSoonRow base={base} feature="flags" icon={<Flag size={15} />} />} | |
| 843 | 860 | {shows.has("security") ? ( | |
| 844 | 861 | <SidebarLink to={`${base}/security`} also={soonPaths(base, "Security")} icon={<ShieldCheck size={15} />}> | |
| 845 | 862 | Security | |
| ⋯ | |||
| 1617 | 1634 | if (third === "pull" && fourth) trail.push({ label: `Pull request #${fourth}`, short: `#${fourth}`, to: `${repo}/pull/${fourth}` }); | |
| 1618 | 1635 | else if (third === "issues" && fourth && fourth !== "new") trail.push({ label: `Issue #${fourth}`, short: `#${fourth}`, to: `${repo}/issues/${fourth}` }); | |
| 1619 | 1636 | else if (third === "commit" && fourth) trail.push({ label: fourth.slice(0, 7), to: `${repo}/commit/${fourth}`, mono: true }); | |
| 1637 | + | // A Soon page names what is coming. | |
| 1638 | + | else if (third === "soon" && fourth && roadmapItem(fourth)) trail.push({ label: roadmapItem(fourth)!.title, to: `${repo}/soon/${fourth}` }); | |
| 1620 | 1639 | else if (third && SECTIONS[third]) { | |
| 1621 | 1640 | trail.push({ label: SECTIONS[third]!, to: `${repo}/${third}` }); | |
| 1622 | 1641 | // A settings page names which one: Settings / Secrets and variables. | |
| 12 | 12 | key: string; | |
| 13 | 13 | title: string; | |
| 14 | 14 | /** | |
| 15 | − | * Where it lives: a project page whose tabs it joins, or the workspace, | |
| 16 | − | * for what spans projects (Insights). | |
| 15 | + | * Where it lives: a project page whose tabs it joins, a page of its own | |
| 16 | + | * in the project's sidebar ("Project"), or the workspace, for what spans | |
| 17 | + | * projects (Insights). | |
| 17 | 18 | */ | |
| 18 | − | section: "Code" | "Issues" | "Deployments" | "Observability" | "Security" | "Insights" | "Workspace"; | |
| 19 | + | section: "Code" | "Issues" | "Deployments" | "Observability" | "Security" | "Insights" | "Project" | "Workspace"; | |
| 19 | 20 | /** One line, for the menu's tooltip and the page's lead. */ | |
| 20 | 21 | summary: string; | |
| 21 | 22 | /** Why it matters, in two or three sentences. */ | |
| ⋯ | |||
| 59 | 60 | ], | |
| 60 | 61 | today: { label: "Deployments", path: "deployments" }, | |
| 61 | 62 | }, | |
| 63 | + | ||
| 64 | + | // --- Pages of their own in the project's sidebar ---------------------- | |
| 65 | + | { | |
| 66 | + | key: "analytics", | |
| 67 | + | title: "Analytics", | |
| 68 | + | section: "Project", | |
| 69 | + | summary: "Who visits the deployed apps, without cookies.", | |
| 70 | + | why: "Visits, pages and referrers for each app, private by design, from Cloudflare's own analytics.", | |
| 71 | + | plans: ["Visits, pages, referrers and countries", "No cookies, no personal data", "Per deployment and per preview"], | |
| 72 | + | }, | |
| 62 | 73 | { | |
| 74 | + | key: "firewall", | |
| 75 | + | title: "Firewall", | |
| 76 | + | section: "Project", | |
| 77 | + | summary: "Rules for who may reach the project's deployed apps.", | |
| 78 | + | why: "Deployed apps run on Cloudflare. Rate limits, bot protection and IP rules, per app and environment.", | |
| 79 | + | plans: ["Rate limits and bot protection", "IP and country rules", "Password-protected previews"], | |
| 80 | + | today: { label: "Deployments", path: "deployments" }, | |
| 81 | + | }, | |
| 82 | + | { | |
| 63 | 83 | key: "flags", | |
| 64 | − | title: "Feature flags", | |
| 65 | − | section: "Deployments", | |
| 84 | + | title: "Flags", | |
| 85 | + | section: "Project", | |
| 66 | 86 | summary: "Turn features on per environment or per user, without a deploy.", | |
| 67 | 87 | why: "Ship code dark and turn it on when ready, for some users first, at the edge, with no deploy.", | |
| 68 | 88 | plans: ["Flags read at the edge by deployed apps", "Rollouts by percentage, user or environment", "Reminders to clean up flags once fully on"], | |
| ⋯ | |||
| 72 | 92 | // --- Security ------------------------------------------------------------- | |
| 73 | 93 | // The overview, secret scanning, code scanning, vulnerabilities and the | |
| 74 | 94 | // dependency graph are built: see routes/repo/security-*.tsx. | |
| 75 | − | { | |
| 76 | − | key: "firewall", | |
| 77 | − | title: "Firewall", | |
| 78 | − | section: "Security", | |
| 79 | − | summary: "Rules for who may reach the project's deployed apps.", | |
| 80 | − | why: "Deployed apps run on Cloudflare. Rate limits, bot protection and IP rules, per app and environment.", | |
| 81 | − | plans: ["Rate limits and bot protection", "IP and country rules", "Password-protected previews"], | |
| 82 | − | today: { label: "Deployments", path: "deployments" }, | |
| 83 | − | }, | |
| 84 | 95 | ||
| 85 | 96 | // --- Observe ------------------------------------------------------------ | |
| 86 | 97 | { | |
| ⋯ | |||
| 90 | 101 | summary: "Checks that the app answers, from around the world.", | |
| 91 | 102 | why: "Know the app is down before your users tell you, and who was told.", | |
| 92 | 103 | plans: ["Checks from many places", "Alerts by email and webhook", "A public status page"], | |
| 93 | − | }, | |
| 94 | − | { | |
| 95 | − | key: "analytics", | |
| 96 | − | title: "Web analytics", | |
| 97 | − | section: "Insights", | |
| 98 | − | summary: "Who visits the deployed apps, without cookies.", | |
| 99 | − | why: "Visits, pages and referrers for each app, private by design, from Cloudflare's own analytics.", | |
| 100 | − | plans: ["Visits, pages, referrers and countries", "No cookies, no personal data", "Per deployment and per preview"], | |
| 101 | 104 | }, | |
| 102 | 105 | ||
| 103 | 106 | // --- Insights ------------------------------------------------------------- | |
| 143 | 143 | {siblings.length > 0 && ( | |
| 144 | 144 | <section className="mt-10"> | |
| 145 | 145 | <h2 className="text-sm font-medium text-muted"> | |
| 146 | − | Also coming {inWorkspace ? "across projects" : `to ${item.section}`} | |
| 146 | + | Also coming {inWorkspace ? "across projects" : item.section === "Project" ? "to every project" : `to ${item.section}`} | |
| 147 | 147 | </h2> | |
| 148 | 148 | <ul className="mt-3 grid gap-3 sm:grid-cols-2"> | |
| 149 | 149 | {siblings.map((other) => ( |